diff --git a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java index f8316d289..fad1b3c85 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java @@ -86,6 +86,11 @@ abstract class MainWindowModule { @FxControllerKey(VaultListController.class) abstract FxController bindVaultListController(VaultListController controller); + @Binds + @IntoMap + @FxControllerKey(VaultListContextMenuController.class) + abstract FxController bindVaultListContextMenuController(VaultListContextMenuController controller); + @Binds @IntoMap @FxControllerKey(VaultDetailController.class) diff --git a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java new file mode 100644 index 000000000..b0866d80f --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java @@ -0,0 +1,135 @@ +package org.cryptomator.ui.mainwindow; + +import com.tobiasdiez.easybind.EasyBind; +import com.tobiasdiez.easybind.optional.ObservableOptionalValue; +import com.tobiasdiez.easybind.optional.OptionalBinding; +import org.cryptomator.common.keychain.KeychainManager; +import org.cryptomator.common.vaults.Vault; +import org.cryptomator.common.vaults.VaultState; +import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.fxapp.FxApplication; +import org.cryptomator.ui.removevault.RemoveVaultComponent; +import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab; +import org.cryptomator.ui.vaultoptions.VaultOptionsComponent; + +import javax.inject.Inject; +import javafx.beans.binding.Binding; +import javafx.beans.binding.Bindings; +import javafx.beans.property.ObjectProperty; +import javafx.fxml.FXML; +import javafx.stage.Stage; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.Optional; + +import static org.cryptomator.common.vaults.VaultState.ERROR; +import static org.cryptomator.common.vaults.VaultState.LOCKED; +import static org.cryptomator.common.vaults.VaultState.MISSING; +import static org.cryptomator.common.vaults.VaultState.NEEDS_MIGRATION; +import static org.cryptomator.common.vaults.VaultState.UNLOCKED; + +@MainWindowScoped +public class VaultListContextMenuController implements FxController { + + private final ObservableOptionalValue selectedVault; + private final Stage mainWindow; + private final FxApplication application; + private final KeychainManager keychain; + private final RemoveVaultComponent.Builder removeVault; + private final VaultOptionsComponent.Builder vaultOptionsWindow; + private final OptionalBinding selectedVaultState; + private final Binding selectedVaultPassphraseStored; + private final Binding selectedVaultRemovable; + private final Binding selectedVaultUnlockable; + private final Binding selectedVaultLockable; + + @Inject + VaultListContextMenuController(ObjectProperty selectedVault, @MainWindow Stage mainWindow, FxApplication application, KeychainManager keychain, RemoveVaultComponent.Builder removeVault, VaultOptionsComponent.Builder vaultOptionsWindow) { + this.selectedVault = EasyBind.wrapNullable(selectedVault); + this.mainWindow = mainWindow; + this.application = application; + this.keychain = keychain; + this.removeVault = removeVault; + this.vaultOptionsWindow = vaultOptionsWindow; + + this.selectedVaultState = this.selectedVault.mapObservable(Vault::stateProperty); + this.selectedVaultPassphraseStored = this.selectedVault.map(this::isPasswordStored).orElse(false); + this.selectedVaultRemovable = selectedVaultState.map(EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION)::contains).orElse(false); + this.selectedVaultUnlockable = selectedVaultState.map(LOCKED::equals).orElse(false); + this.selectedVaultLockable = selectedVaultState.map(UNLOCKED::equals).orElse(false); + + } + + private boolean isPasswordStored(Vault vault) { + return keychain.getPassphraseStoredProperty(vault.getId()).get(); + } + + @FXML + public void didClickRemoveVault() { + selectedVault.ifValuePresent(v -> { + removeVault.vault(v).build().showRemoveVault(); + }); + } + + @FXML + public void didClickShowVaultOptions() { + selectedVault.ifValuePresent(v -> { + vaultOptionsWindow.vault(v).build().showVaultOptionsWindow(SelectedVaultOptionsTab.ANY); + }); + } + + @FXML + public void didClickUnlockVault() { + selectedVault.ifValuePresent(v -> { + application.startUnlockWorkflow(v, Optional.of(mainWindow)); + }); + } + + @FXML + public void didClickLockVault() { + selectedVault.ifValuePresent(v -> { + application.startLockWorkflow(v, Optional.of(mainWindow)); + }); + } + + @FXML + public void didClickRevealVault() { + selectedVault.ifValuePresent(v -> { + application.getVaultService().reveal(v); + }); + } + + // Getter and Setter + + public Binding selectedVaultUnlockableProperty() { + return selectedVaultUnlockable; + } + + public boolean isSelectedVaultUnlockable() { + return selectedVaultUnlockable.getValue(); + } + + public Binding selectedVaultLockableProperty() { + return selectedVaultLockable; + } + + public boolean isSelectedVaultLockable() { + return selectedVaultLockable.getValue(); + } + + public Binding selectedVaultRemovableProperty() { + return selectedVaultRemovable; + } + + public boolean isSelectedVaultRemovable() { + return selectedVaultRemovable.getValue(); + } + + public Binding selectedVaultPassphraseStoredProperty() { + return selectedVaultPassphraseStored; + } + + public boolean isSelectedVaultPassphraseStored() { + return selectedVaultPassphraseStored.getValue(); + } +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java index fc751d862..04ea9f152 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java @@ -4,9 +4,6 @@ import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultListManager; import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent; import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.removevault.RemoveVaultComponent; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import javax.inject.Inject; import javafx.beans.binding.Bindings; @@ -17,30 +14,30 @@ import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.ListView; +import javafx.scene.input.ContextMenuEvent; +import javafx.scene.input.MouseEvent; @MainWindowScoped public class VaultListController implements FxController { - private static final Logger LOG = LoggerFactory.getLogger(VaultListController.class); private final ObservableList vaults; private final ObjectProperty selectedVault; private final VaultListCellFactory cellFactory; private final AddVaultWizardComponent.Builder addVaultWizard; - private final RemoveVaultComponent.Builder removeVault; - private final BooleanBinding noVaultSelected; private final BooleanBinding emptyVaultList; + public ListView vaultList; @Inject - VaultListController(ObservableList vaults, ObjectProperty selectedVault, VaultListCellFactory cellFactory, AddVaultWizardComponent.Builder addVaultWizard, RemoveVaultComponent.Builder removeVault) { + VaultListController(ObservableList vaults, ObjectProperty selectedVault, VaultListCellFactory cellFactory, AddVaultWizardComponent.Builder addVaultWizard) { this.vaults = vaults; this.selectedVault = selectedVault; this.cellFactory = cellFactory; this.addVaultWizard = addVaultWizard; - this.removeVault = removeVault; - this.noVaultSelected = selectedVault.isNull(); + this.emptyVaultList = Bindings.isEmpty(vaults); + selectedVault.addListener(this::selectedVaultDidChange); } @@ -56,6 +53,19 @@ public class VaultListController implements FxController { } } }); + vaultList.addEventFilter(MouseEvent.MOUSE_RELEASED, this::deselect); + vaultList.addEventFilter(ContextMenuEvent.CONTEXT_MENU_REQUESTED, request -> { + if (selectedVault.get() == null) { + request.consume(); + } + }); + } + + private void deselect(MouseEvent released) { + if (released.getY() > (vaultList.getItems().size() * vaultList.fixedCellSizeProperty().get())) { + vaultList.getSelectionModel().clearSelection(); + released.consume(); + } } private void selectedVaultDidChange(@SuppressWarnings("unused") ObservableValue observableValue, @SuppressWarnings("unused") Vault oldValue, Vault newValue) { @@ -70,16 +80,6 @@ public class VaultListController implements FxController { addVaultWizard.build().showAddVaultWizard(); } - @FXML - public void didClickRemoveVault() { - Vault v = selectedVault.get(); - if (v != null) { - removeVault.vault(v).build().showRemoveVault(); - } else { - LOG.debug("Cannot remove a vault if none is selected."); - } - } - // Getter and Setter public BooleanBinding emptyVaultListProperty() { @@ -90,11 +90,4 @@ public class VaultListController implements FxController { return emptyVaultList.get(); } - public BooleanBinding noVaultSelectedProperty() { - return noVaultSelected; - } - - public boolean isNoVaultSelected() { - return noVaultSelected.get(); - } } diff --git a/main/ui/src/main/resources/fxml/vault_list.fxml b/main/ui/src/main/resources/fxml/vault_list.fxml index 942074639..f01aa710c 100644 --- a/main/ui/src/main/resources/fxml/vault_list.fxml +++ b/main/ui/src/main/resources/fxml/vault_list.fxml @@ -2,10 +2,8 @@ - - @@ -15,13 +13,9 @@ fx:controller="org.cryptomator.ui.mainwindow.VaultListController" minWidth="206"> - + - - - - - + diff --git a/main/ui/src/main/resources/fxml/vault_list_cell.fxml b/main/ui/src/main/resources/fxml/vault_list_cell.fxml index e86f084c1..ce8664639 100644 --- a/main/ui/src/main/resources/fxml/vault_list_cell.fxml +++ b/main/ui/src/main/resources/fxml/vault_list_cell.fxml @@ -13,6 +13,7 @@ prefWidth="200" spacing="12" alignment="CENTER_LEFT"> + diff --git a/main/ui/src/main/resources/fxml/vault_list_contextmenu.fxml b/main/ui/src/main/resources/fxml/vault_list_contextmenu.fxml new file mode 100644 index 000000000..bd8f19204 --- /dev/null +++ b/main/ui/src/main/resources/fxml/vault_list_contextmenu.fxml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/main/ui/src/main/resources/i18n/strings.properties b/main/ui/src/main/resources/i18n/strings.properties index 20aa17a14..239e3fb74 100644 --- a/main/ui/src/main/resources/i18n/strings.properties +++ b/main/ui/src/main/resources/i18n/strings.properties @@ -224,7 +224,12 @@ main.dropZone.dropVault=Add this vault main.dropZone.unknownDragboardContent=If you want to add a vault, drag it to this window ## Vault List main.vaultlist.emptyList.onboardingInstruction=Click here to add a vault -main.vaultlist.contextMenu.remove=Remove Vault… +main.vaultlist.contextMenu.remove=Remove… +main.vaultlist.contextMenu.lock=Lock +main.vaultlist.contextMenu.unlock=Unlock… +main.vaultlist.contextMenu.unlockNow=Unlock Now +main.vaultlist.contextMenu.vaultoptions=Show Vault Options +main.vaultlist.contextMenu.reveal=Reveal Drive main.vaultlist.addVaultBtn=Add Vault ## Vault Detail ### Welcome