Refactored adding vaults

This commit is contained in:
Sebastian Stenzel
2019-09-12 17:01:50 +02:00
parent 9e14b5e70f
commit 2bbc3e5834
9 changed files with 130 additions and 101 deletions

View File

@@ -10,16 +10,13 @@ import dagger.Module;
import dagger.Provides;
import javafx.beans.binding.Binding;
import javafx.beans.binding.Bindings;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.SettingsProvider;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultComponent;
import org.cryptomator.common.vaults.VaultFactory;
import org.cryptomator.common.vaults.VaultListChangeListener;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.frontend.webdav.WebDavServer;
import org.fxmisc.easybind.EasyBind;
@@ -53,14 +50,8 @@ public abstract class CommonsModule {
@Provides
@Singleton
static ObservableList<Vault> provideVaultList(Settings settings, VaultFactory vaultFactory) {
ObservableList<Vault> list = FXCollections.observableArrayList(Vault::observables);
for (VaultSettings s : settings.getDirectories()) {
Vault v = vaultFactory.get(s);
list.add(v);
}
list.addListener(new VaultListChangeListener(settings.getDirectories()));
return list;
static ObservableList<Vault> provideVaultList(VaultListManager vaultListManager) {
return vaultListManager.getVaultList();
}
@Provides

View File

@@ -10,7 +10,7 @@ import java.util.stream.Collectors;
/**
* This listener makes sure to reflect any changes to the vault list back to the settings.
*/
public class VaultListChangeListener implements ListChangeListener<Vault> {
class VaultListChangeListener implements ListChangeListener<Vault> {
private final ObservableList<VaultSettings> vaultSettingsList;

View File

@@ -8,6 +8,9 @@
*******************************************************************************/
package org.cryptomator.common.vaults;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptofs.migration.Migrators;
@@ -15,25 +18,56 @@ import org.cryptomator.cryptofs.migration.Migrators;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.Collection;
import java.util.Optional;
import java.util.stream.Collectors;
@Singleton
public class VaultFactory {
public class VaultListManager {
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; // TODO: deduplicate constant declared in multiple classes
private final VaultComponent.Builder vaultComponentBuilder;
private final ConcurrentMap<VaultSettings, Vault> vaults = new ConcurrentHashMap<>();
private final ObservableList<Vault> vaultList;
@Inject
public VaultFactory(VaultComponent.Builder vaultComponentBuilder) {
public VaultListManager(VaultComponent.Builder vaultComponentBuilder, Settings settings) {
this.vaultComponentBuilder = vaultComponentBuilder;
this.vaultList = FXCollections.observableArrayList(Vault::observables);
addAll(settings.getDirectories());
vaultList.addListener(new VaultListChangeListener(settings.getDirectories()));
}
public Vault get(VaultSettings vaultSettings) {
return vaults.computeIfAbsent(vaultSettings, this::create);
public ObservableList<Vault> getVaultList() {
return vaultList;
}
public Vault add(Path pathToVault) throws NoSuchFileException {
if (!CryptoFileSystemProvider.containsVault(pathToVault, MASTERKEY_FILENAME)) {
throw new NoSuchFileException(pathToVault.toString(), null, "Not a vault directory");
}
Optional<Vault> alreadyExistingVault = get(pathToVault);
if (alreadyExistingVault.isPresent()) {
return alreadyExistingVault.get();
} else {
VaultSettings vaultSettings = VaultSettings.withRandomId();
vaultSettings.path().set(pathToVault);
Vault newVault = create(vaultSettings);
vaultList.add(newVault);
return newVault;
}
}
private void addAll(Collection<VaultSettings> vaultSettings) {
Collection<Vault> vaults = vaultSettings.stream().map(this::create).collect(Collectors.toList());
vaultList.addAll(vaults);
}
private Optional<Vault> get(Path vaultPath) {
return vaultList.stream().filter(v -> v.getPath().equals(vaultPath)).findAny();
}
private Vault create(VaultSettings vaultSettings) {

View File

@@ -2,44 +2,45 @@ package org.cryptomator.ui.addvaultwizard;
import dagger.Lazy;
import javafx.beans.property.ObjectProperty;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultFactory;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.File;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.ResourceBundle;
@AddVaultWizardScoped
public class ChooseExistingVaultController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(ChooseExistingVaultController.class);
private final Stage window;
private final Lazy<Scene> welcomeScene;
private final Lazy<Scene> successScene;
private final ObjectProperty<Path> vaultPath;
private final ObservableList<Vault> vaults;
private final ObjectProperty<Vault> vault;
private final VaultFactory vaultFactory;
private final VaultListManager vaultListManager;
private final ResourceBundle resourceBundle;
@Inject
ChooseExistingVaultController(@AddVaultWizard Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy<Scene> welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, ObjectProperty<Path> vaultPath, ObservableList<Vault> vaults, @AddVaultWizard ObjectProperty<Vault> vault, VaultFactory vaultFactory, ResourceBundle resourceBundle) {
ChooseExistingVaultController(@AddVaultWizard Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy<Scene> welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, ObjectProperty<Path> vaultPath, @AddVaultWizard ObjectProperty<Vault> vault, VaultListManager vaultListManager, ResourceBundle resourceBundle) {
this.window = window;
this.welcomeScene = welcomeScene;
this.successScene = successScene;
this.vaultPath = vaultPath;
this.vaults = vaults;
this.vault = vault;
this.vaultFactory = vaultFactory;
this.vaultListManager = vaultListManager;
this.resourceBundle = resourceBundle;
}
@@ -54,16 +55,17 @@ public class ChooseExistingVaultController implements FxController {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle(resourceBundle.getString("addvaultwizard.existing.filePickerTitle"));
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Cryptomator Masterkey", "*.cryptomator"));
final File file = fileChooser.showOpenDialog(window);
File file = fileChooser.showOpenDialog(window);
if (file != null) {
vaultPath.setValue(file.toPath().toAbsolutePath().getParent());
VaultSettings vaultSettings = VaultSettings.withRandomId();
vaultSettings.path().setValue(vaultPath.get());
Vault newVault = vaultFactory.get(vaultSettings);
vaults.add(newVault);
vault.set(newVault);
//TODO: error handling?
window.setScene(successScene.get());
try {
Vault newVault = vaultListManager.add(vaultPath.get());
vault.set(newVault);
window.setScene(successScene.get());
} catch (NoSuchFileException e) {
LOG.error("Nope", e);
// TODO
}
}
}

View File

@@ -10,7 +10,6 @@ import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
@@ -18,9 +17,8 @@ import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultFactory;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.ui.common.FxController;
@@ -36,10 +34,12 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.channels.WritableByteChannel;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Collections;
@@ -60,9 +60,8 @@ public class CreateNewVaultPasswordController implements FxController {
private final ExecutorService executor;
private final StringProperty vaultName;
private final ObjectProperty<Path> vaultPath;
private final ObservableList<Vault> vaults;
private final ObjectProperty<Vault> vault;
private final VaultFactory vaultFactory;
private final VaultListManager vaultListManager;
private final ResourceBundle resourceBundle;
private final PasswordStrengthUtil strengthRater;
private final ReadmeGenerator readmeGenerator;
@@ -81,16 +80,15 @@ public class CreateNewVaultPasswordController implements FxController {
public CheckBox finalConfirmationCheckbox;
@Inject
CreateNewVaultPasswordController(@AddVaultWizard Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy<Scene> chooseLocationScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, ExecutorService executor, StringProperty vaultName, ObjectProperty<Path> vaultPath, ObservableList<Vault> vaults, @AddVaultWizard ObjectProperty<Vault> vault, VaultFactory vaultFactory, ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater, ReadmeGenerator readmeGenerator) {
CreateNewVaultPasswordController(@AddVaultWizard Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy<Scene> chooseLocationScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, ExecutorService executor, StringProperty vaultName, ObjectProperty<Path> vaultPath, @AddVaultWizard ObjectProperty<Vault> vault, VaultListManager vaultListManager, ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater, ReadmeGenerator readmeGenerator) {
this.window = window;
this.chooseLocationScene = chooseLocationScene;
this.successScene = successScene;
this.executor = executor;
this.vaultName = vaultName;
this.vaultPath = vaultPath;
this.vaults = vaults;
this.vault = vault;
this.vaultFactory = vaultFactory;
this.vaultListManager = vaultListManager;
this.resourceBundle = resourceBundle;
this.strengthRater = strengthRater;
this.readmeGenerator = readmeGenerator;
@@ -127,8 +125,10 @@ public class CreateNewVaultPasswordController implements FxController {
@FXML
public void next() {
Path pathToVault = vaultPath.get();
try {
Files.createDirectory(vaultPath.get());
Files.createDirectory(pathToVault);
} catch (FileAlreadyExistsException e) {
LOG.error("Vault dir already exists.", e);
window.setScene(chooseLocationScene.get());
@@ -139,14 +139,9 @@ public class CreateNewVaultPasswordController implements FxController {
processing.set(true);
Tasks.create(() -> {
initializeVault(vaultPath.get(), passwordField.getCharacters());
initializeVault(pathToVault, passwordField.getCharacters());
}).onSuccess(() -> {
VaultSettings vaultSettings = VaultSettings.withRandomId();
vaultSettings.path().setValue(vaultPath.get());
Vault newVault = vaultFactory.get(vaultSettings);
vault.set(newVault);
vaults.add(newVault);
window.setScene(successScene.get());
initializationSucceeded(pathToVault);
}).onError(IOException.class, e -> {
// TODO show generic error screen
LOG.error("", e);
@@ -175,6 +170,16 @@ public class CreateNewVaultPasswordController implements FxController {
}
LOG.info("Created vault at {}", path);
}
private void initializationSucceeded(Path pathToVault) {
try {
Vault newVault = vaultListManager.add(pathToVault);
vault.set(newVault);
window.setScene(successScene.get());
} catch (NoSuchFileException e) {
throw new UncheckedIOException(e);
}
}
/* Getter/Setter */

View File

@@ -46,14 +46,13 @@ import javafx.util.Duration;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.ui.fxapp.FxApplicationScoped;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.ui.ExitUtil;
import org.cryptomator.ui.controls.DirectoryListCell;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.launcher.AppLaunchEvent;
import org.cryptomator.ui.model.AutoUnlocker;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultFactory;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.ui.model.upgrade.UpgradeStrategies;
import org.cryptomator.ui.model.upgrade.UpgradeStrategy;
import org.cryptomator.ui.util.DialogBuilderUtil;
@@ -70,7 +69,9 @@ import java.awt.Desktop;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
@@ -94,7 +95,7 @@ public class MainController implements ViewController {
private final Localization localization;
private final ExecutorService executorService;
private final BlockingQueue<AppLaunchEvent> launchEventQueue;
private final VaultFactory vaultFactoy;
private final VaultListManager vaultFactoy;
private final ViewControllerLoader viewControllerLoader;
private final ObjectProperty<ViewController> activeController = new SimpleObjectProperty<>();
private final ObservableList<Vault> vaults;
@@ -111,7 +112,7 @@ public class MainController implements ViewController {
@Inject
public MainController(@Named("mainWindow") Stage mainWindow, ExecutorService executorService, @Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue, ExitUtil exitUtil, Localization localization,
VaultFactory vaultFactoy, ViewControllerLoader viewControllerLoader, UpgradeStrategies upgradeStrategies, ObservableList<Vault> vaults, AutoUnlocker autoUnlocker) {
VaultListManager vaultFactoy, ViewControllerLoader viewControllerLoader, UpgradeStrategies upgradeStrategies, ObservableList<Vault> vaults, AutoUnlocker autoUnlocker) {
this.mainWindow = mainWindow;
this.executorService = executorService;
this.launchEventQueue = launchEventQueue;
@@ -344,9 +345,11 @@ public class MainController implements ViewController {
}
final Vault vault = vaults.stream().filter(v -> v.getPath().equals(vaultPath)).findAny().orElseGet(() -> {
VaultSettings vaultSettings = VaultSettings.withRandomId();
vaultSettings.path().set(vaultPath);
return vaultFactoy.get(vaultSettings);
try {
return vaultFactoy.add(vaultPath);
} catch (NoSuchFileException e) {
throw new UncheckedIOException(e);
}
});
if (!vaults.contains(vault)) {

View File

@@ -1,10 +1,7 @@
package org.cryptomator.ui.launcher;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultFactory;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.ui.fxapp.FxApplication;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -12,6 +9,7 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
@@ -20,20 +18,19 @@ import java.util.concurrent.ExecutorService;
class AppLaunchEventHandler {
private static final Logger LOG = LoggerFactory.getLogger(AppLaunchEventHandler.class);
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; // TODO: deduplicate constant declared in multiple classes
private final BlockingQueue<AppLaunchEvent> launchEventQueue;
private final ExecutorService executorService;
private final FxApplicationStarter fxApplicationStarter;
private final VaultFactory vaultFactory;
private final ObservableList<Vault> vaults;
private final VaultListManager vaultListManager;
@Inject
public AppLaunchEventHandler(@Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue, ExecutorService executorService, FxApplicationStarter fxApplicationStarter, VaultFactory vaultFactory, ObservableList<Vault> vaults) {
public AppLaunchEventHandler(@Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue, ExecutorService executorService, FxApplicationStarter fxApplicationStarter, VaultListManager vaultListManager) {
this.launchEventQueue = launchEventQueue;
this.executorService = executorService;
this.fxApplicationStarter = fxApplicationStarter;
this.vaultFactory = vaultFactory;
this.vaults = vaults;
this.vaultListManager = vaultListManager;
}
public void startHandlingLaunchEvents(boolean hasTrayIcon) {
@@ -73,12 +70,16 @@ class AppLaunchEventHandler {
// TODO dedup MainWindowController...
private void addVault(Path potentialVaultPath) {
assert Platform.isFxApplicationThread();
// TODO CryptoFileSystemProvider.containsVault(potentialVaultPath, "masterkey.cryptomator");
VaultSettings settings = VaultSettings.withRandomId();
settings.path().set(potentialVaultPath);
Vault vault = vaultFactory.get(settings);
vaults.add(vault);
LOG.debug("Added vault {}", potentialVaultPath);
try {
if (potentialVaultPath.getFileName().toString().equals(MASTERKEY_FILENAME)) {
vaultListManager.add(potentialVaultPath.getParent());
} else {
vaultListManager.add(potentialVaultPath);
}
LOG.debug("Added vault {}", potentialVaultPath);
} catch (NoSuchFileException e) {
LOG.error("Failed to add vault " + potentialVaultPath, e);
}
}
}

View File

@@ -1,7 +1,6 @@
package org.cryptomator.ui.mainwindow;
import javafx.beans.binding.BooleanBinding;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.HBox;
@@ -9,9 +8,8 @@ import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultFactory;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.ui.common.FontLoader;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplication;
@@ -24,23 +22,26 @@ import javax.inject.Inject;
import javax.inject.Named;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@MainWindowScoped
public class MainWindowController implements FxController {
private static final String TITLE_FONT = "/css/dosis-bold.ttf";
private static final Logger LOG = LoggerFactory.getLogger(MainWindowController.class);
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; // TODO: deduplicate constant declared in multiple classes
private final Stage window;
private final FxApplication application;
private final boolean minimizeToSysTray;
private final UpdateChecker updateChecker;
private final BooleanBinding updateAvailable;
private final ObservableList<Vault> vaults;
private final VaultFactory vaultFactory;
private final VaultListManager vaultListManager;
private final WrongFileAlertComponent.Builder wrongFileAlert;
public HBox titleBar;
public VBox root;
@@ -50,14 +51,13 @@ public class MainWindowController implements FxController {
private double yOffset;
@Inject
public MainWindowController(@MainWindow Stage window, FxApplication application, @Named("trayMenuSupported") boolean minimizeToSysTray, UpdateChecker updateChecker, ObservableList<Vault> vaults, VaultFactory vaultFactory, WrongFileAlertComponent.Builder wrongFileAlert) {
public MainWindowController(@MainWindow Stage window, FxApplication application, @Named("trayMenuSupported") boolean minimizeToSysTray, UpdateChecker updateChecker, VaultListManager vaultListManager, WrongFileAlertComponent.Builder wrongFileAlert) {
this.window = window;
this.application = application;
this.minimizeToSysTray = minimizeToSysTray;
this.updateChecker = updateChecker;
this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
this.vaults = vaults;
this.vaultFactory = vaultFactory;
this.vaultListManager = vaultListManager;
this.wrongFileAlert = wrongFileAlert;
}
@@ -93,33 +93,26 @@ public class MainWindowController implements FxController {
if (event.getGestureSource() != root && event.getDragboard().hasFiles()) {
/* allow for both copying and moving, whatever user chooses */
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
Collection<Path> vaultPaths = event.getDragboard().getFiles().stream().map(File::toPath).filter(this::isVaultPath).collect(Collectors.toSet());
Collection<Vault> vaultPaths = event.getDragboard().getFiles().stream().map(File::toPath).flatMap(this::addVault).collect(Collectors.toSet());
if (vaultPaths.isEmpty()) {
wrongFileAlert.build().showWrongFileAlertWindow();
} else {
vaultPaths.forEach(this::addVault);
}
}
event.consume();
});
}
private boolean isVaultPath(Path path) {
if (path.getFileName().toString().endsWith(".cryptomator")) {
return true;
} else if (Files.exists(path.resolve("masterkey.cryptomator"))) {
return true;
} else {
return false;
private Stream<Vault> addVault(Path pathToVault) {
try {
if (pathToVault.getFileName().toString().equals(MASTERKEY_FILENAME)) {
return Stream.of(vaultListManager.add(pathToVault.getParent()));
} else {
return Stream.of(vaultListManager.add(pathToVault));
}
} catch (NoSuchFileException e) {
LOG.debug("Not a vault: {}", pathToVault);
}
}
private void addVault(Path pathToVault) {
VaultSettings vaultSettings = VaultSettings.withRandomId();
vaultSettings.path().setValue(pathToVault);
Vault newVault = vaultFactory.get(vaultSettings);
vaults.add(newVault);
//TODO: error handling?
return Stream.empty();
}
private void loadFont(String resourcePath) {

View File

@@ -56,7 +56,7 @@ public class VaultListController implements FxController {
window.setIconified(false);
window.show();
window.toFront();
window.requestFocus();
window.requestFocus(); // TODO: this beeps on macOS if there is a modal child window...
}
}
});