ソースを参照

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

Sebastian Stenzel 6 年 前
コミット
1d94069144

+ 1 - 0
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");

+ 12 - 3
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<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);
 	}

+ 2 - 1
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

+ 44 - 0
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> 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();
+	}
+
+}

+ 64 - 0
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<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();
+	}
+}

+ 56 - 0
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<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);
+
+}

+ 13 - 0
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 {
+
+}

+ 14 - 0
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 {
+
+}

+ 34 - 4
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<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
+			}
+		});
 	}
 }

+ 34 - 0
main/ui/src/main/resources/fxml/quit.fxml

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