Browse Source

macOS: Only show application menu and dock icon when a Window is visible (related to #957, #283, #346, #62)

Sebastian Stenzel 5 years ago
parent
commit
264e81b4a0

+ 30 - 4
main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java

@@ -3,11 +3,16 @@ package org.cryptomator.ui.fxapp;
 import dagger.Lazy;
 import javafx.application.Application;
 import javafx.application.Platform;
+import javafx.beans.binding.Bindings;
+import javafx.beans.binding.BooleanBinding;
 import javafx.beans.value.ObservableValue;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableSet;
 import javafx.stage.Stage;
 import org.cryptomator.common.settings.Settings;
 import org.cryptomator.common.settings.UiTheme;
 import org.cryptomator.common.vaults.Vault;
+import org.cryptomator.jni.MacApplicationUiState;
 import org.cryptomator.jni.MacFunctions;
 import org.cryptomator.ui.mainwindow.MainWindowComponent;
 import org.cryptomator.ui.preferences.PreferencesComponent;
@@ -31,6 +36,8 @@ public class FxApplication extends Application {
 	private final UnlockComponent.Builder unlockWindowBuilder;
 	private final QuitComponent.Builder quitWindowBuilder;
 	private final Optional<MacFunctions> macFunctions;
+	private final ObservableSet<Stage> visibleStages = FXCollections.observableSet();
+	private final BooleanBinding hasVisibleStages = Bindings.isNotEmpty(visibleStages);
 
 	@Inject
 	FxApplication(Settings settings, Lazy<MainWindowComponent> mainWindow, Lazy<PreferencesComponent> preferencesWindow, UnlockComponent.Builder unlockWindowBuilder, QuitComponent.Builder quitWindowBuilder, Optional<MacFunctions> macFunctions) {
@@ -46,6 +53,8 @@ public class FxApplication extends Application {
 		LOG.trace("FxApplication.start()");
 		Platform.setImplicitExit(false);
 
+		hasVisibleStages.addListener(this::hasVisibleStagesChanged);
+
 		settings.theme().addListener(this::themeChanged);
 		loadSelectedStyleSheet(settings.theme().get());
 	}
@@ -55,30 +64,47 @@ public class FxApplication extends Application {
 		throw new UnsupportedOperationException("Use start() instead.");
 	}
 
+	private void addVisibleStage(Stage stage) {
+		visibleStages.add(stage);
+		stage.setOnHidden(evt -> visibleStages.remove(stage));
+	}
+
+	private void hasVisibleStagesChanged(@SuppressWarnings("unused") ObservableValue<? extends Boolean> observableValue, @SuppressWarnings("unused") boolean oldValue, boolean newValue) {
+		if (newValue) {
+			macFunctions.map(MacFunctions::uiState).ifPresent(MacApplicationUiState::transformToForegroundApplication);
+		} else {
+			macFunctions.map(MacFunctions::uiState).ifPresent(MacApplicationUiState::transformToAgentApplication);
+		}
+	}
+
 	public void showPreferencesWindow() {
 		Platform.runLater(() -> {
-			preferencesWindow.get().showPreferencesWindow();
+			Stage stage = preferencesWindow.get().showPreferencesWindow();
+			addVisibleStage(stage);
 			LOG.debug("Showing Preferences");
 		});
 	}
 
 	public void showMainWindow() {
 		Platform.runLater(() -> {
-			mainWindow.get().showMainWindow();
+			Stage stage = mainWindow.get().showMainWindow();
+			addVisibleStage(stage);
 			LOG.debug("Showing MainWindow");
 		});
 	}
 
 	public void showUnlockWindow(Vault vault) {
 		Platform.runLater(() -> {
-			unlockWindowBuilder.vault(vault).build().showUnlockWindow();
+			Stage stage = unlockWindowBuilder.vault(vault).build().showUnlockWindow();
+			addVisibleStage(stage);
 			LOG.debug("Showing UnlockWindow for {}", vault.getDisplayableName());
 		});
 	}
 
 	public void showQuitWindow(QuitResponse response) {
 		Platform.runLater(() -> {
-			quitWindowBuilder.quitResponse(response).build().showQuitWindow();
+			Stage stage = quitWindowBuilder.quitResponse(response).build().showQuitWindow();
+			addVisibleStage(stage);
 			LOG.debug("Showing QuitWindow");
 		});
 	}

+ 2 - 1
main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowComponent.java

@@ -22,11 +22,12 @@ public interface MainWindowComponent {
 	@FxmlScene(FxmlFile.MAIN_WINDOW)
 	Lazy<Scene> scene();
 
-	default void showMainWindow() {
+	default Stage showMainWindow() {
 		Stage stage = window();
 		stage.setScene(scene().get());
 		stage.show();
 		stage.requestFocus();
+		return stage;
 	}
 
 	@Subcomponent.Builder

+ 2 - 1
main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesComponent.java

@@ -23,11 +23,12 @@ public interface PreferencesComponent {
 	@FxmlScene(FxmlFile.PREFERENCES)
 	Lazy<Scene> scene();
 
-	default void showPreferencesWindow() {
+	default Stage showPreferencesWindow() {
 		Stage stage = window();
 		stage.setScene(scene().get());
 		stage.show();
 		stage.requestFocus();
+		return stage;
 	}
 
 	@Subcomponent.Builder

+ 2 - 1
main/ui/src/main/java/org/cryptomator/ui/quit/QuitComponent.java

@@ -25,11 +25,12 @@ public interface QuitComponent {
 	@FxmlScene(FxmlFile.QUIT)
 	Lazy<Scene> scene();
 
-	default void showQuitWindow() {
+	default Stage showQuitWindow() {
 		Stage stage = window();
 		stage.setScene(scene().get());
 		stage.show();
 		stage.requestFocus();
+		return stage;
 	}
 
 	@Subcomponent.Builder

+ 9 - 3
main/ui/src/main/java/org/cryptomator/ui/traymenu/TrayIconController.java

@@ -3,6 +3,9 @@ package org.cryptomator.ui.traymenu;
 import javafx.beans.Observable;
 import org.apache.commons.lang3.SystemUtils;
 import org.cryptomator.common.settings.Settings;
+import org.cryptomator.jni.JniException;
+import org.cryptomator.jni.MacApplicationUiState;
+import org.cryptomator.jni.MacFunctions;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -10,6 +13,7 @@ import javax.inject.Inject;
 import java.awt.AWTException;
 import java.awt.SystemTray;
 import java.awt.TrayIcon;
+import java.util.Optional;
 
 @TrayMenuScoped
 public class TrayIconController {
@@ -20,15 +24,15 @@ public class TrayIconController {
 	private final TrayImageFactory imageFactory;
 	private final TrayMenuController trayMenuController;
 	private final TrayIcon trayIcon;
-	//	private final Optional<MacFunctions> macFunctions;
+	private final Optional<MacFunctions> macFunctions;
 
 	@Inject
-	TrayIconController(Settings settings, TrayImageFactory imageFactory, TrayMenuController trayMenuController) {
+	TrayIconController(Settings settings, TrayImageFactory imageFactory, TrayMenuController trayMenuController, Optional<MacFunctions> macFunctions) {
 		this.settings = settings;
 		this.trayMenuController = trayMenuController;
 		this.imageFactory = imageFactory;
 		this.trayIcon = new TrayIcon(imageFactory.loadImage(), "Cryptomator", trayMenuController.getMenu());
-//		this.macFunctions = macFunctions;
+		this.macFunctions = macFunctions;
 	}
 
 	public void initializeTrayIcon() {
@@ -38,6 +42,8 @@ public class TrayIconController {
 		if (SystemUtils.IS_OS_WINDOWS) {
 			// TODO: test on windows: is this a double click?
 			trayIcon.addActionListener(trayMenuController::showMainWindow);
+		} else if (SystemUtils.IS_OS_MAC) {
+			macFunctions.map(MacFunctions::uiState).ifPresent(JniException.ignore(MacApplicationUiState::transformToAgentApplication));
 		}
 
 		try {

+ 2 - 1
main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockComponent.java

@@ -24,10 +24,11 @@ public interface UnlockComponent {
 	@FxmlScene(FxmlFile.UNLOCK)
 	Lazy<Scene> scene();
 
-	default void showUnlockWindow() {
+	default Stage showUnlockWindow() {
 		Stage stage = window();
 		stage.setScene(scene().get());
 		stage.show();
+		return stage;
 	}
 
 	@Subcomponent.Builder