From 1d9406914475a8d9f4707f08089f51495caa7344 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 6 Aug 2019 13:34:29 +0200 Subject: [PATCH] Added proper Quit dialog (not yet fully implemented), fixes #838 --- .../org/cryptomator/ui/common/FxmlFile.java | 1 + .../cryptomator/ui/fxapp/FxApplication.java | 15 ++++- .../ui/fxapp/FxApplicationModule.java | 3 +- .../cryptomator/ui/quit/QuitComponent.java | 44 +++++++++++++ .../cryptomator/ui/quit/QuitController.java | 64 +++++++++++++++++++ .../org/cryptomator/ui/quit/QuitModule.java | 56 ++++++++++++++++ .../org/cryptomator/ui/quit/QuitScoped.java | 13 ++++ .../org/cryptomator/ui/quit/QuitWindow.java | 14 ++++ .../ui/traymenu/TrayMenuController.java | 38 +++++++++-- main/ui/src/main/resources/fxml/quit.fxml | 34 ++++++++++ 10 files changed, 274 insertions(+), 8 deletions(-) create mode 100644 main/ui/src/main/java/org/cryptomator/ui/quit/QuitComponent.java create mode 100644 main/ui/src/main/java/org/cryptomator/ui/quit/QuitController.java create mode 100644 main/ui/src/main/java/org/cryptomator/ui/quit/QuitModule.java create mode 100644 main/ui/src/main/java/org/cryptomator/ui/quit/QuitScoped.java create mode 100644 main/ui/src/main/java/org/cryptomator/ui/quit/QuitWindow.java create mode 100644 main/ui/src/main/resources/fxml/quit.fxml diff --git a/main/ui/src/main/java/org/cryptomator/ui/common/FxmlFile.java b/main/ui/src/main/java/org/cryptomator/ui/common/FxmlFile.java index 0f30aeab8..103d01e04 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/common/FxmlFile.java +++ b/main/ui/src/main/java/org/cryptomator/ui/common/FxmlFile.java @@ -8,6 +8,7 @@ public enum FxmlFile { ADDVAULT_NEW_LOCATION("/fxml/addvault_new_location.fxml"), // ADDVAULT_NEW_PASSWORD("/fxml/addvault_new_password.fxml"), // PREFERENCES("/fxml/preferences.fxml"), // + QUIT("/fxml/quit.fxml"), UNLOCK("/fxml/unlock2.fxml"), // TODO rename UNLOCK_SUCCESS("/fxml/unlock_success.fxml"), VAULT_OPTIONS("/fxml/vault_options.fxml"); diff --git a/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java b/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java index dd1fb4152..3cc51cbf0 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java +++ b/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java @@ -12,13 +12,13 @@ import org.cryptomator.jni.MacApplicationUiAppearance; import org.cryptomator.jni.MacFunctions; import org.cryptomator.ui.mainwindow.MainWindowComponent; import org.cryptomator.ui.preferences.PreferencesComponent; +import org.cryptomator.ui.quit.QuitComponent; import org.cryptomator.ui.unlock.UnlockComponent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; -import java.awt.Desktop; -import java.awt.desktop.PreferencesEvent; +import java.awt.desktop.QuitResponse; import java.util.Optional; @FxApplicationScoped @@ -30,14 +30,16 @@ public class FxApplication extends Application { private final Lazy mainWindow; private final Lazy preferencesWindow; private final UnlockComponent.Builder unlockWindowBuilder; + private final QuitComponent.Builder quitWindowBuilder; private final Optional macFunctions; @Inject - FxApplication(Settings settings, Lazy mainWindow, Lazy preferencesWindow, UnlockComponent.Builder unlockWindowBuilder, Optional macFunctions) { + FxApplication(Settings settings, Lazy mainWindow, Lazy preferencesWindow, UnlockComponent.Builder unlockWindowBuilder, QuitComponent.Builder quitWindowBuilder, Optional macFunctions) { this.settings = settings; this.mainWindow = mainWindow; this.preferencesWindow = preferencesWindow; this.unlockWindowBuilder = unlockWindowBuilder; + this.quitWindowBuilder = quitWindowBuilder; this.macFunctions = macFunctions; } @@ -75,6 +77,13 @@ public class FxApplication extends Application { }); } + public void showQuitWindow(QuitResponse response) { + Platform.runLater(() -> { + quitWindowBuilder.quitResponse(response).build().showQuitWindow(); + LOG.debug("Showing QuitWindow"); + }); + } + private void themeChanged(@SuppressWarnings("unused") ObservableValue observable, @SuppressWarnings("unused") UiTheme oldValue, UiTheme newValue) { loadSelectedStyleSheet(newValue); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java b/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java index de07249e1..447503ecb 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java @@ -15,11 +15,12 @@ import org.cryptomator.common.vaults.Vault; import org.cryptomator.keychain.KeychainModule; import org.cryptomator.ui.mainwindow.MainWindowComponent; import org.cryptomator.ui.preferences.PreferencesComponent; +import org.cryptomator.ui.quit.QuitComponent; import org.cryptomator.ui.unlock.UnlockComponent; import java.util.ResourceBundle; -@Module(includes = {KeychainModule.class, UpdateCheckerModule.class}, subcomponents = {MainWindowComponent.class, PreferencesComponent.class, UnlockComponent.class}) +@Module(includes = {KeychainModule.class, UpdateCheckerModule.class}, subcomponents = {MainWindowComponent.class, PreferencesComponent.class, UnlockComponent.class, QuitComponent.class}) abstract class FxApplicationModule { @Binds diff --git a/main/ui/src/main/java/org/cryptomator/ui/quit/QuitComponent.java b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitComponent.java new file mode 100644 index 000000000..c548b7c41 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitComponent.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt). + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the accompanying LICENSE file. + *******************************************************************************/ +package org.cryptomator.ui.quit; + +import dagger.BindsInstance; +import dagger.Lazy; +import dagger.Subcomponent; +import javafx.scene.Scene; +import javafx.stage.Stage; +import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlScene; + +import java.awt.desktop.QuitResponse; + +@QuitScoped +@Subcomponent(modules = {QuitModule.class}) +public interface QuitComponent { + + @QuitWindow + Stage window(); + + @FxmlScene(FxmlFile.QUIT) + Lazy scene(); + + default void showQuitWindow() { + Stage stage = window(); + stage.setScene(scene().get()); + stage.show(); + stage.requestFocus(); + } + + @Subcomponent.Builder + interface Builder { + + @BindsInstance + Builder quitResponse(QuitResponse response); + + QuitComponent build(); + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/quit/QuitController.java b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitController.java new file mode 100644 index 000000000..9a0f6c836 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitController.java @@ -0,0 +1,64 @@ +package org.cryptomator.ui.quit; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.fxml.FXML; +import javafx.scene.control.ContentDisplay; +import javafx.stage.Stage; +import org.cryptomator.ui.common.FxController; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import java.awt.desktop.QuitResponse; +import java.util.concurrent.ExecutorService; + +@QuitScoped +public class QuitController implements FxController { + + private static final Logger LOG = LoggerFactory.getLogger(QuitController.class); + + private final Stage window; + private final QuitResponse response; + private final ExecutorService executor; + private final ObjectProperty quitButtonState; + + @Inject + QuitController(@QuitWindow Stage window, QuitResponse response, ExecutorService executor) { + this.window = window; + this.response = response; + this.executor = executor; + this.quitButtonState = new SimpleObjectProperty<>(ContentDisplay.TEXT_ONLY); + } + + @FXML + public void cancel() { + LOG.info("Quitting application canceled by user."); + window.close(); + response.cancelQuit(); + } + + @FXML + public void quit() { + LOG.warn("Quit not yet implemented."); + window.close(); + response.cancelQuit(); + } + + @FXML + public void forceQuit() { + LOG.warn("Force Quit not yet implemented."); + window.close(); + response.cancelQuit(); + } + + /* Observable Properties */ + + public ObjectProperty quitButtonStateProperty() { + return quitButtonState; + } + + public ContentDisplay getQuitButtonState() { + return quitButtonState.get(); + } +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/quit/QuitModule.java b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitModule.java new file mode 100644 index 000000000..b61d5b572 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitModule.java @@ -0,0 +1,56 @@ +package org.cryptomator.ui.quit; + +import dagger.Binds; +import dagger.Module; +import dagger.Provides; +import dagger.multibindings.IntoMap; +import javafx.scene.Scene; +import javafx.stage.Modality; +import javafx.stage.Stage; +import org.cryptomator.ui.common.FXMLLoaderFactory; +import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.common.FxControllerKey; +import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlScene; +import org.cryptomator.ui.unlock.UnlockController; + +import javax.inject.Provider; +import java.util.Map; +import java.util.ResourceBundle; + +@Module +abstract class QuitModule { + + @Provides + @QuitWindow + @QuitScoped + static FXMLLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, ResourceBundle resourceBundle) { + return new FXMLLoaderFactory(factories, resourceBundle); + } + + @Provides + @QuitWindow + @QuitScoped + static Stage provideStage() { + Stage stage = new Stage(); + stage.setMinWidth(300); + stage.setMinHeight(200); + stage.initModality(Modality.APPLICATION_MODAL); + return stage; + } + + @Provides + @FxmlScene(FxmlFile.QUIT) + @QuitScoped + static Scene provideUnlockScene(@QuitWindow FXMLLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene("/fxml/quit.fxml"); + } + + // ------------------ + + @Binds + @IntoMap + @FxControllerKey(QuitController.class) + abstract FxController bindQuitController(QuitController controller); + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/quit/QuitScoped.java b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitScoped.java new file mode 100644 index 000000000..5e093dd06 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitScoped.java @@ -0,0 +1,13 @@ +package org.cryptomator.ui.quit; + +import javax.inject.Scope; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Scope +@Documented +@Retention(RetentionPolicy.RUNTIME) +@interface QuitScoped { + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/quit/QuitWindow.java b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitWindow.java new file mode 100644 index 000000000..4771884c2 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitWindow.java @@ -0,0 +1,14 @@ +package org.cryptomator.ui.quit; + +import javax.inject.Qualifier; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface QuitWindow { + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java b/main/ui/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java index 8bfbdf1df..6fd0ddc3c 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java @@ -1,10 +1,13 @@ package org.cryptomator.ui.traymenu; import javafx.beans.Observable; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.BooleanBinding; import javafx.collections.ObservableList; import org.cryptomator.common.settings.Settings; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.fxapp.FxApplication; +import org.fxmisc.easybind.EasyBind; import javax.inject.Inject; import javax.inject.Named; @@ -12,6 +15,8 @@ import java.awt.Desktop; import java.awt.Menu; import java.awt.MenuItem; import java.awt.PopupMenu; +import java.awt.desktop.QuitEvent; +import java.awt.desktop.QuitResponse; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.EventObject; @@ -26,6 +31,7 @@ class TrayMenuController { private final Settings settings; private final ObservableList vaults; private final PopupMenu menu; + private final BooleanBinding allLocked; @Inject TrayMenuController(FxApplicationStarter fxApplicationStarter, @Named("shutdownLatch") CountDownLatch shutdownLatch, Settings settings, ObservableList vaults) { @@ -34,6 +40,7 @@ class TrayMenuController { this.settings = settings; this.vaults = vaults; this.menu = new PopupMenu(); + this.allLocked = Bindings.isEmpty(vaults.filtered(Vault::isUnlocked)); // TODO better use Vault::isNotLocked ;) } public PopupMenu getMenu() { @@ -50,6 +57,11 @@ class TrayMenuController { Desktop.getDesktop().setPreferencesHandler(this::showPreferencesWindow); } + // register preferences shortcut + if (Desktop.getDesktop().isSupported(Desktop.Action.APP_QUIT_HANDLER)) { + Desktop.getDesktop().setQuitHandler(this::handleQuitRequest); + } + // show window on start? if (!settings.startHidden().get()) { showMainWindow(null); @@ -111,14 +123,32 @@ class TrayMenuController { } void showMainWindow(@SuppressWarnings("unused") ActionEvent actionEvent) { - fxApplicationStarter.get(true).thenAccept(FxApplication::showMainWindow); + fxApplicationStarter.get(true).thenAccept(app -> app.showMainWindow()); } - void showPreferencesWindow(@SuppressWarnings("unused") EventObject actionEvent) { + private void showPreferencesWindow(@SuppressWarnings("unused") EventObject actionEvent) { fxApplicationStarter.get(true).thenAccept(FxApplication::showPreferencesWindow); } - void quitApplication(@SuppressWarnings("unused") ActionEvent actionEvent) { - shutdownLatch.countDown(); + private void handleQuitRequest(EventObject e, QuitResponse response) { + if (allLocked.get()) { + response.performQuit(); // really? + } else { + fxApplicationStarter.get(true).thenAccept(app -> app.showQuitWindow(response)); + } + } + + private void quitApplication(EventObject actionEvent) { + handleQuitRequest(actionEvent, new QuitResponse() { + @Override + public void performQuit() { + shutdownLatch.countDown(); + } + + @Override + public void cancelQuit() { + // no-op + } + }); } } diff --git a/main/ui/src/main/resources/fxml/quit.fxml b/main/ui/src/main/resources/fxml/quit.fxml new file mode 100644 index 000000000..5e9f6017c --- /dev/null +++ b/main/ui/src/main/resources/fxml/quit.fxml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + +