|
@@ -1,6 +1,12 @@
|
|
|
package org.cryptomator.ui.traymenu;
|
|
|
|
|
|
+import com.google.common.base.Preconditions;
|
|
|
+import org.apache.commons.lang3.SystemUtils;
|
|
|
import org.cryptomator.common.vaults.Vault;
|
|
|
+import org.cryptomator.integrations.tray.ActionItem;
|
|
|
+import org.cryptomator.integrations.tray.SeparatorItem;
|
|
|
+import org.cryptomator.integrations.tray.SubMenuItem;
|
|
|
+import org.cryptomator.integrations.tray.TrayMenuItem;
|
|
|
import org.cryptomator.ui.fxapp.FxApplication;
|
|
|
import org.cryptomator.ui.launcher.AppLifecycleListener;
|
|
|
import org.cryptomator.ui.launcher.FxApplicationStarter;
|
|
@@ -10,44 +16,61 @@ import javax.inject.Inject;
|
|
|
import javafx.application.Platform;
|
|
|
import javafx.beans.Observable;
|
|
|
import javafx.collections.ObservableList;
|
|
|
-import java.awt.Menu;
|
|
|
-import java.awt.MenuItem;
|
|
|
-import java.awt.PopupMenu;
|
|
|
-import java.awt.event.ActionEvent;
|
|
|
+import java.awt.Image;
|
|
|
+import java.awt.Toolkit;
|
|
|
import java.awt.event.ActionListener;
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.UncheckedIOException;
|
|
|
+import java.util.ArrayList;
|
|
|
import java.util.EventObject;
|
|
|
+import java.util.List;
|
|
|
import java.util.Optional;
|
|
|
import java.util.ResourceBundle;
|
|
|
import java.util.function.Consumer;
|
|
|
|
|
|
@TrayMenuScoped
|
|
|
-class TrayMenuController {
|
|
|
+public class TrayMenuController {
|
|
|
+
|
|
|
+ private static final String TRAY_ICON_MAC = "/img/tray_icon_mac.png";
|
|
|
+ private static final String TRAY_ICON = "/img/tray_icon.png";
|
|
|
|
|
|
private final ResourceBundle resourceBundle;
|
|
|
private final AppLifecycleListener appLifecycle;
|
|
|
private final FxApplicationStarter fxApplicationStarter;
|
|
|
private final ObservableList<Vault> vaults;
|
|
|
- private final PopupMenu menu;
|
|
|
+ private final org.cryptomator.integrations.tray.TrayMenuController trayMenu;
|
|
|
+
|
|
|
+ private volatile boolean initialized;
|
|
|
|
|
|
@Inject
|
|
|
- TrayMenuController(ResourceBundle resourceBundle, AppLifecycleListener appLifecycle, FxApplicationStarter fxApplicationStarter, ObservableList<Vault> vaults) {
|
|
|
+ TrayMenuController(ResourceBundle resourceBundle, AppLifecycleListener appLifecycle, FxApplicationStarter fxApplicationStarter, ObservableList<Vault> vaults, Optional<org.cryptomator.integrations.tray.TrayMenuController> trayMenu) {
|
|
|
this.resourceBundle = resourceBundle;
|
|
|
this.appLifecycle = appLifecycle;
|
|
|
this.fxApplicationStarter = fxApplicationStarter;
|
|
|
this.vaults = vaults;
|
|
|
- this.menu = new PopupMenu();
|
|
|
+ this.trayMenu = trayMenu.orElse(null);
|
|
|
}
|
|
|
|
|
|
- public PopupMenu getMenu() {
|
|
|
- return menu;
|
|
|
- }
|
|
|
+ public synchronized void initTrayMenu() {
|
|
|
+ Preconditions.checkState(!initialized, "tray icon already initialized");
|
|
|
|
|
|
- public void initTrayMenu() {
|
|
|
vaults.addListener(this::vaultListChanged);
|
|
|
vaults.forEach(v -> {
|
|
|
v.displayNameProperty().addListener(this::vaultListChanged);
|
|
|
});
|
|
|
rebuildMenu();
|
|
|
+
|
|
|
+ try (var image = getClass().getResourceAsStream(SystemUtils.IS_OS_MAC_OSX ? TRAY_ICON_MAC : TRAY_ICON)) {
|
|
|
+ trayMenu.showTrayIcon(image, this::showMainWindow, "Cryptomator");
|
|
|
+ } catch (IOException e) {
|
|
|
+ throw new UncheckedIOException("Failed to load embedded resource", e);
|
|
|
+ }
|
|
|
+
|
|
|
+ initialized = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean isInitialized() {
|
|
|
+ return initialized;
|
|
|
}
|
|
|
|
|
|
private void vaultListChanged(@SuppressWarnings("unused") Observable observable) {
|
|
@@ -56,58 +79,42 @@ class TrayMenuController {
|
|
|
}
|
|
|
|
|
|
private void rebuildMenu() {
|
|
|
- menu.removeAll();
|
|
|
-
|
|
|
- MenuItem showMainWindowItem = new MenuItem(resourceBundle.getString("traymenu.showMainWindow"));
|
|
|
- showMainWindowItem.addActionListener(this::showMainWindow);
|
|
|
- menu.add(showMainWindowItem);
|
|
|
-
|
|
|
- MenuItem showPreferencesItem = new MenuItem(resourceBundle.getString("traymenu.showPreferencesWindow"));
|
|
|
- showPreferencesItem.addActionListener(this::showPreferencesWindow);
|
|
|
- menu.add(showPreferencesItem);
|
|
|
-
|
|
|
- menu.addSeparator();
|
|
|
- for (Vault v : vaults) {
|
|
|
- MenuItem submenu = buildSubmenu(v);
|
|
|
- menu.add(submenu);
|
|
|
+ List<TrayMenuItem> menu = new ArrayList<>();
|
|
|
+
|
|
|
+ menu.add(new ActionItem(resourceBundle.getString("traymenu.showMainWindow"), this::showMainWindow));
|
|
|
+ menu.add(new ActionItem(resourceBundle.getString("traymenu.showPreferencesWindow"), this::showPreferencesWindow));
|
|
|
+ menu.add(new SeparatorItem());
|
|
|
+ for (Vault vault : vaults) {
|
|
|
+ List<TrayMenuItem> submenu = buildSubmenu(vault);
|
|
|
+ menu.add(new SubMenuItem(vault.getDisplayName(), submenu));
|
|
|
}
|
|
|
- menu.addSeparator();
|
|
|
-
|
|
|
- MenuItem lockAllItem = new MenuItem(resourceBundle.getString("traymenu.lockAllVaults"));
|
|
|
- lockAllItem.addActionListener(this::lockAllVaults);
|
|
|
- lockAllItem.setEnabled(!vaults.filtered(Vault::isUnlocked).isEmpty());
|
|
|
- menu.add(lockAllItem);
|
|
|
+ menu.add(new SeparatorItem());
|
|
|
+ menu.add(new ActionItem(resourceBundle.getString("traymenu.lockAllVaults"), this::lockAllVaults));
|
|
|
+ menu.add(new ActionItem(resourceBundle.getString("traymenu.quitApplication"), this::quitApplication));
|
|
|
+
|
|
|
|
|
|
- MenuItem quitApplicationItem = new MenuItem(resourceBundle.getString("traymenu.quitApplication"));
|
|
|
- quitApplicationItem.addActionListener(this::quitApplication);
|
|
|
- menu.add(quitApplicationItem);
|
|
|
+ trayMenu.updateTrayMenu(menu);
|
|
|
}
|
|
|
|
|
|
- private Menu buildSubmenu(Vault vault) {
|
|
|
- Menu submenu = new Menu(vault.getDisplayName());
|
|
|
-
|
|
|
+ private List<TrayMenuItem> buildSubmenu(Vault vault) {
|
|
|
if (vault.isLocked()) {
|
|
|
- MenuItem unlockItem = new MenuItem(resourceBundle.getString("traymenu.vault.unlock"));
|
|
|
- unlockItem.addActionListener(createActionListenerForVault(vault, this::unlockVault));
|
|
|
- submenu.add(unlockItem);
|
|
|
+ return List.of(
|
|
|
+ new ActionItem(resourceBundle.getString("traymenu.vault.unlock"), () -> this.unlockVault(vault))
|
|
|
+ );
|
|
|
} else if (vault.isUnlocked()) {
|
|
|
- MenuItem lockItem = new MenuItem(resourceBundle.getString("traymenu.vault.lock"));
|
|
|
- lockItem.addActionListener(createActionListenerForVault(vault, this::lockVault));
|
|
|
- submenu.add(lockItem);
|
|
|
+ return List.of(
|
|
|
+ new ActionItem(resourceBundle.getString("traymenu.vault.lock"), () -> this.lockVault(vault)),
|
|
|
+ new ActionItem(resourceBundle.getString("traymenu.vault.reveal"), () -> this.revealVault(vault))
|
|
|
|
|
|
- MenuItem revealItem = new MenuItem(resourceBundle.getString("traymenu.vault.reveal"));
|
|
|
- revealItem.addActionListener(createActionListenerForVault(vault, this::revealVault));
|
|
|
- submenu.add(revealItem);
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ return List.of();
|
|
|
}
|
|
|
-
|
|
|
- return submenu;
|
|
|
}
|
|
|
|
|
|
- private ActionListener createActionListenerForVault(Vault vault, Consumer<Vault> consumer) {
|
|
|
- return actionEvent -> consumer.accept(vault);
|
|
|
- }
|
|
|
+
|
|
|
|
|
|
- private void quitApplication(EventObject actionEvent) {
|
|
|
+ private void quitApplication() {
|
|
|
appLifecycle.quit();
|
|
|
}
|
|
|
|
|
@@ -119,7 +126,7 @@ class TrayMenuController {
|
|
|
showMainAppAndThen(app -> app.startLockWorkflow(vault, Optional.empty()));
|
|
|
}
|
|
|
|
|
|
- private void lockAllVaults(ActionEvent actionEvent) {
|
|
|
+ private void lockAllVaults() {
|
|
|
showMainAppAndThen(app -> app.getVaultService().lockAll(vaults.filtered(Vault::isUnlocked), false));
|
|
|
}
|
|
|
|
|
@@ -127,11 +134,11 @@ class TrayMenuController {
|
|
|
showMainAppAndThen(app -> app.getVaultService().reveal(vault));
|
|
|
}
|
|
|
|
|
|
- void showMainWindow(@SuppressWarnings("unused") ActionEvent actionEvent) {
|
|
|
- showMainAppAndThen(app -> app.showMainWindow());
|
|
|
+ void showMainWindow() {
|
|
|
+ showMainAppAndThen(FxApplication::showMainWindow);
|
|
|
}
|
|
|
|
|
|
- private void showPreferencesWindow(@SuppressWarnings("unused") EventObject actionEvent) {
|
|
|
+ private void showPreferencesWindow() {
|
|
|
showMainAppAndThen(app -> app.showPreferencesWindow(SelectedPreferencesTab.ANY));
|
|
|
}
|
|
|
|