Added proper Quit dialog (not yet fully implemented), fixes #838

This commit is contained in:
Sebastian Stenzel
2019-08-06 13:34:29 +02:00
parent 6e10d6bcd7
commit 1d94069144
10 changed files with 274 additions and 8 deletions

View File

@@ -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");

View File

@@ -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<MainWindowComponent> mainWindow;
private final Lazy<PreferencesComponent> preferencesWindow;
private final UnlockComponent.Builder unlockWindowBuilder;
private final QuitComponent.Builder quitWindowBuilder;
private final Optional<MacFunctions> macFunctions;
@Inject
FxApplication(Settings settings, Lazy<MainWindowComponent> mainWindow, Lazy<PreferencesComponent> preferencesWindow, UnlockComponent.Builder unlockWindowBuilder, Optional<MacFunctions> macFunctions) {
FxApplication(Settings settings, Lazy<MainWindowComponent> mainWindow, Lazy<PreferencesComponent> preferencesWindow, UnlockComponent.Builder unlockWindowBuilder, QuitComponent.Builder quitWindowBuilder, Optional<MacFunctions> 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<? extends UiTheme> observable, @SuppressWarnings("unused") UiTheme oldValue, UiTheme newValue) {
loadSelectedStyleSheet(newValue);
}

View File

@@ -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

View File

@@ -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> 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();
}
}

View File

@@ -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<ContentDisplay> 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<ContentDisplay> quitButtonStateProperty() {
return quitButtonState;
}
public ContentDisplay getQuitButtonState() {
return quitButtonState.get();
}
}

View File

@@ -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<Class<? extends FxController>, Provider<FxController>> 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);
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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<Vault> vaults;
private final PopupMenu menu;
private final BooleanBinding allLocked;
@Inject
TrayMenuController(FxApplicationStarter fxApplicationStarter, @Named("shutdownLatch") CountDownLatch shutdownLatch, Settings settings, ObservableList<Vault> 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
}
});
}
}

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ProgressIndicator?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Text?>
<?import javafx.scene.text.TextFlow?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.quit.QuitController"
minWidth="300"
spacing="6">
<padding>
<Insets bottom="6.0" left="6.0" right="6.0" top="6.0"/>
</padding>
<children>
<TextFlow styleClass="text-flow">
<Text text="TODO Quit application? There are unlocked vaults "/>
</TextFlow>
<HBox>
<Button text="TODO cancel" cancelButton="true" onAction="#cancel"/>
<Region HBox.hgrow="ALWAYS"/>
<Button text="TODO force quit" cancelButton="true" onAction="#forceQuit"/>
<Button text="TODO lock and quit" defaultButton="true" onAction="#quit" contentDisplay="${controller.quitButtonState}">
<graphic>
<ProgressIndicator progress="-1" prefWidth="12" prefHeight="12"/>
</graphic>
</Button>
</HBox>
</children>
</VBox>