Jelajahi Sumber

further improving windows mount options

Armin Schrenk 5 tahun lalu
induk
melakukan
57d3f788e6

+ 118 - 8
main/ui/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java

@@ -3,26 +3,38 @@ package org.cryptomator.ui.vaultoptions;
 import javafx.beans.binding.BooleanBinding;
 import javafx.beans.property.BooleanProperty;
 import javafx.beans.property.SimpleBooleanProperty;
-import javafx.event.ActionEvent;
 import javafx.fxml.FXML;
 import javafx.scene.control.CheckBox;
+import javafx.scene.control.ChoiceBox;
 import javafx.scene.control.RadioButton;
 import javafx.scene.control.TextField;
 import javafx.scene.control.ToggleGroup;
+import javafx.stage.DirectoryChooser;
+import javafx.stage.Stage;
+import javafx.util.StringConverter;
 import org.apache.commons.lang3.SystemUtils;
 import org.cryptomator.common.settings.Settings;
 import org.cryptomator.common.settings.VolumeImpl;
 import org.cryptomator.common.vaults.Vault;
+import org.cryptomator.common.vaults.WindowsDriveLetters;
 import org.cryptomator.ui.common.FxController;
 
 import javax.inject.Inject;
+import java.io.File;
+import java.nio.file.Path;
+import java.util.Comparator;
+import java.util.ResourceBundle;
+import java.util.Set;
 
 @VaultOptionsScoped
 public class MountOptionsController implements FxController {
 
+	private final Stage window;
 	private final Vault vault;
 	private final BooleanProperty osIsWindows = new SimpleBooleanProperty(SystemUtils.IS_OS_WINDOWS);
 	private final BooleanBinding adapterIsDokan;
+	private final WindowsDriveLetters windowsDriveLetters;
+	private final ResourceBundle resourceBundle;
 	private final ToggleGroup toggleGroup;
 	public TextField driveName;
 	public CheckBox readOnlyCheckbox;
@@ -31,11 +43,15 @@ public class MountOptionsController implements FxController {
 	public RadioButton automaticDriveLetter;
 	public RadioButton specificDriveLetter;
 	public RadioButton specificDirectory;
+	public ChoiceBox<Path> driveLetterSelection;
 
 	@Inject
-	MountOptionsController(@VaultOptionsWindow Vault vault, Settings settings) {
+	MountOptionsController(@VaultOptionsWindow Stage window, @VaultOptionsWindow Vault vault, Settings settings, WindowsDriveLetters windowsDriveLetters, ResourceBundle resourceBundle) {
+		this.window = window;
 		this.vault = vault;
 		this.adapterIsDokan = settings.preferredVolumeImpl().isEqualTo(VolumeImpl.DOKANY);
+		this.windowsDriveLetters = windowsDriveLetters;
+		this.resourceBundle = resourceBundle;
 		this.toggleGroup = new ToggleGroup();
 	}
 
@@ -55,7 +71,17 @@ public class MountOptionsController implements FxController {
 		}
 
 		toggleGroup.getToggles().addAll(automaticDriveLetter, specificDriveLetter, specificDirectory);
+		initDriveLetterSelection();
+	}
 
+	private void initDriveLetterSelection() {
+		driveLetterSelection.setConverter(new WinDriveLetterLabelConverter());
+		Set<Path> freeLetters = windowsDriveLetters.getAvailableDriveLetters();
+		driveLetterSelection.getItems().addAll(freeLetters);
+		driveLetterSelection.getItems().sort(new WinDriveLetterComparator());
+		chooseSelectedDriveLetter();
+		//TODO: check if we should write only the letter or the path to the settings!!
+		driveLetterSelection.getSelectionModel().selectedItemProperty().addListener(p -> vault.getVaultSettings().winDriveLetter().set(p.toString()));
 	}
 
 	@FXML
@@ -76,11 +102,24 @@ public class MountOptionsController implements FxController {
 	public void changeMountPointForWindows() {
 		assert osIsWindows.get();
 		if (specificDriveLetter.isSelected()) {
-			//TODO: set any default free drive letter
-		} else if (specificDirectory.isSelected()) {
 			vault.getVaultSettings().usesIndividualMountPath().set(true);
-			//TODO: open directory picker
-
+			vault.getVaultSettings().winDriveLetter().set(driveLetterSelection.getSelectionModel().getSelectedItem().toString());
+			vault.getVaultSettings().individualMountPath().set(null);
+		} else if (specificDirectory.isSelected()) {
+			final File file = chooseDirectory();
+			if (file != null) {
+				//TODO: should we check wether the directory is empty or not?
+				vault.getVaultSettings().usesIndividualMountPath().set(true);
+				vault.getVaultSettings().individualMountPath().set(file.getAbsolutePath());
+				vault.getVaultSettings().winDriveLetter().set(null);
+			} else {
+				//NO-OP
+				//TODO: deduplicate code
+				toggleGroup.selectToggle(automaticDriveLetter);
+				vault.getVaultSettings().usesIndividualMountPath().set(false);
+				vault.getVaultSettings().winDriveLetter().set(null);
+				vault.getVaultSettings().individualMountPath().set(null);
+			}
 		} else {
 			//set property
 			vault.getVaultSettings().usesIndividualMountPath().set(false);
@@ -89,8 +128,79 @@ public class MountOptionsController implements FxController {
 		}
 	}
 
-	@FXML
-	public void selectEmptyDirectory(ActionEvent actionEvent) {
+	private File chooseDirectory() {
+		DirectoryChooser directoryChooser = new DirectoryChooser();
+		directoryChooser.setTitle(resourceBundle.getString("TODO"));
+		try {
+			directoryChooser.setInitialDirectory(Path.of(System.getProperty("user.home")).toFile());
+		} catch (Exception e) {
+			//NO-OP
+		}
+		return directoryChooser.showDialog(window);
+	}
+
+	/**
+	 * Converts 'C' to "C:" to translate between model and GUI.
+	 */
+	private class WinDriveLetterLabelConverter extends StringConverter<Path> {
+
+		@Override
+		public String toString(Path root) {
+			if (root == null) {
+				//TODO: none drive letter is selected
+				return "";
+			} else if (root.endsWith("occupied")) {
+				return root.getRoot().toString().substring(0, 1) + " (" + resourceBundle.getString("TODO") + ")";
+			} else {
+				return root.toString().substring(0, 1);
+			}
+		}
+
+		@Override
+		public Path fromString(String string) {
+			if (resourceBundle.getString("TODO").equals(string)) {
+				return null;
+			} else {
+				return Path.of(string);
+			}
+		}
+
+	}
+
+	/**
+	 * Natural sorting of ASCII letters, but <code>null</code> always on first, as this is "auto-assign".
+	 */
+	private static class WinDriveLetterComparator implements Comparator<Path> {
+
+		@Override
+		public int compare(Path c1, Path c2) {
+			if (c1 == null) {
+				return -1;
+			} else if (c2 == null) {
+				return 1;
+			} else {
+				return c1.compareTo(c2);
+			}
+		}
+	}
+
+	private void chooseSelectedDriveLetter() {
+		assert SystemUtils.IS_OS_WINDOWS;
+		// if the vault prefers a drive letter, that is currently occupied, this is our last chance to reset this:
+		if (vault.getVaultSettings().winDriveLetter().isNotEmpty().get()) {
+			final Path pickedRoot = Path.of(vault.getVaultSettings().winDriveLetter().get());
+			if (windowsDriveLetters.getOccupiedDriveLetters().contains(pickedRoot)) {
+				Path alteredPath = pickedRoot.resolve("occupied");
+				driveLetterSelection.getItems().add(alteredPath);
+				driveLetterSelection.getSelectionModel().select(alteredPath);
+			} else {
+				driveLetterSelection.getSelectionModel().select(pickedRoot);
+			}
+		} else {
+			// first option is known to be 'auto-assign' due to #WinDriveLetterComparator.
+			driveLetterSelection.getSelectionModel().selectFirst();
+		}
+
 	}
 
 	// Getter & Setter

+ 7 - 9
main/ui/src/main/resources/fxml/vault_options_mount.fxml

@@ -3,14 +3,13 @@
 <?import javafx.geometry.Insets?>
 <?import javafx.scene.control.CheckBox?>
 <?import javafx.scene.control.ChoiceBox?>
-<?import javafx.scene.control.Hyperlink?>
 <?import javafx.scene.control.Label?>
 <?import javafx.scene.control.RadioButton?>
 <?import javafx.scene.control.TextField?>
 <?import javafx.scene.layout.HBox?>
 <?import javafx.scene.layout.VBox?>
+<?import javafx.scene.text.Text?>
 <?import org.cryptomator.ui.controls.AlphanumericTextField?>
-<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
 <VBox xmlns="http://javafx.com/javafx"
 	  xmlns:fx="http://javafx.com/fxml"
 	  fx:controller="org.cryptomator.ui.vaultoptions.MountOptionsController"
@@ -37,17 +36,16 @@
 		</HBox>
 
 		<!-- TODO windows drive letter, see https://github.com/cryptomator/cryptomator/blob/1.4.16/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java#L283-L298 -->
-		<!-- TODO maybe change label to text because you cannot interact with the hbox -->
-		<Label text="Mount Point"/>
-		<RadioButton fx:id="automaticDriveLetter" text="Automatically pick free drive letter" visible="${controller.osIsWindows}" managed="${controller.adapterIsDokan}" onAction="#changeMountPointForWindows"/>
+		<Text text="TODO Mount Point"/>
+		<RadioButton fx:id="automaticDriveLetter" text="TODO Automatically pick free drive letter" visible="${controller.osIsWindows}" managed="${controller.adapterIsDokan}" onAction="#changeMountPointForWindows"/>
 		<HBox spacing="6">
 			<children>
-				<RadioButton fx:id="specificDriveLetter" text="Choose specific drive letter" visible="${controller.osIsWindows}" managed="${controller.adapterIsDokan}" onAction="#changeMountPointForWindows"/>
-				<ChoiceBox fx:id="DriveLetterSelection" disable="${!specificDriveLetter.selected}"/>
+				<RadioButton fx:id="specificDriveLetter" text="TODO Choose specific drive letter" visible="${controller.osIsWindows}" managed="${controller.adapterIsDokan}" onAction="#changeMountPointForWindows"/>
+				<ChoiceBox fx:id="driveLetterSelection" disable="${!specificDriveLetter.selected}"/>
 			</children>
 		</HBox>
-		<RadioButton fx:id="specificDirectory" text="Choose empty directory" visible="${controller.adapterIsDokan}" managed="${controller.adapterIsDokan}" onAction="#changeMountPointForWindows"/>
-		<HBox visible="${specificDirectory.selected}" >
+		<RadioButton fx:id="specificDirectory" text="TODO Choose empty directory" visible="${controller.adapterIsDokan}" managed="${controller.adapterIsDokan}" onAction="#changeMountPointForWindows"/>
+		<HBox visible="${specificDirectory.selected}">
 			<padding>
 				<Insets left="25"/>
 			</padding>