From 5d03d0f0ebe1ca46bf7664c89f1dce7a0675bb1f Mon Sep 17 00:00:00 2001 From: kske Date: Mon, 20 Jul 2020 12:57:34 +0200 Subject: [PATCH 1/5] Make StatusTrayIcon work with JavaFX * Remove Swing dependencies from StatusTrayIcon * Pass a stage to the constructor * Adjust focus change handler and reactivation * Add IconUtil#loadAWTCompatible for BufferedImage loading --- .../main/java/envoy/client/ui/IconUtil.java | 23 ++++++++++++- .../java/envoy/client/ui/StatusTrayIcon.java | 33 +++++++------------ 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/client/src/main/java/envoy/client/ui/IconUtil.java b/client/src/main/java/envoy/client/ui/IconUtil.java index 5324619..f95d1a6 100644 --- a/client/src/main/java/envoy/client/ui/IconUtil.java +++ b/client/src/main/java/envoy/client/ui/IconUtil.java @@ -1,9 +1,13 @@ package envoy.client.ui; +import java.awt.image.BufferedImage; +import java.io.IOException; import java.util.EnumMap; import java.util.EnumSet; import java.util.logging.Level; +import javax.imageio.ImageIO; + import javafx.scene.image.Image; import envoy.client.data.Settings; @@ -145,6 +149,23 @@ public class IconUtil { return icons; } + /** + * Loads a buffered image from the resource folder which is compatible with AWT. + * + * @param path the path to the icon inside the resource folder + * @return the loaded image + * @since Envoy Client v0.2-beta + */ + public static BufferedImage loadAWTCompatible(String path) { + BufferedImage image = null; + try { + image = ImageIO.read(IconUtil.class.getResource(path)); + } catch (IOException e) { + EnvoyLog.getLogger(IconUtil.class).log(Level.WARNING, String.format("Could not load image at path %s: ", path), e); + } + return image; + } + /** * This method should be called if the display of an image depends upon the * currently active theme.
@@ -154,7 +175,7 @@ public class IconUtil { * @return the theme specific folder * @since Envoy Client v0.1-beta */ - public static String themeSpecificSubFolder() { + private static String themeSpecificSubFolder() { return Settings.getInstance().isUsingDefaultTheme() ? Settings.getInstance().getCurrentTheme() + "/" : ""; } } diff --git a/client/src/main/java/envoy/client/ui/StatusTrayIcon.java b/client/src/main/java/envoy/client/ui/StatusTrayIcon.java index eb0178d..ec22b68 100644 --- a/client/src/main/java/envoy/client/ui/StatusTrayIcon.java +++ b/client/src/main/java/envoy/client/ui/StatusTrayIcon.java @@ -2,15 +2,14 @@ package envoy.client.ui; import java.awt.*; import java.awt.TrayIcon.MessageType; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.util.logging.Level; + +import javafx.application.Platform; +import javafx.stage.Stage; import envoy.client.event.MessageCreationEvent; import envoy.data.Message; import envoy.event.EventBus; import envoy.exception.EnvoyException; -import envoy.util.EnvoyLog; /** * Project: envoy-client
@@ -39,18 +38,16 @@ public class StatusTrayIcon { * Creates a {@link StatusTrayIcon} with the Envoy logo, a tool tip and a pop-up * menu. * - * @param focusTarget the {@link Window} which focus determines if message - * notifications are displayed + * @param stage the stage which focus determines if message + * notifications are displayed * @throws EnvoyException if the currently used OS does not support the System * Tray API - * @since Envoy Client v0.2-alpha + * @since Envoy Client v0.2-beta */ - public StatusTrayIcon(Window focusTarget) throws EnvoyException { + public StatusTrayIcon(Stage stage) throws EnvoyException { if (!SystemTray.isSupported()) throw new EnvoyException("The Envoy tray icon is not supported."); - final ClassLoader loader = Thread.currentThread().getContextClassLoader(); - final Image img = Toolkit.getDefaultToolkit().createImage(loader.getResource("envoy_logo.png")); - trayIcon = new TrayIcon(img, "Envoy Client"); + trayIcon = new TrayIcon(IconUtil.loadAWTCompatible("/icons/envoy_logo.png"), "Envoy"); trayIcon.setImageAutoSize(true); trayIcon.setToolTip("You are notified if you have unread messages."); @@ -62,18 +59,11 @@ public class StatusTrayIcon { trayIcon.setPopupMenu(popup); - // Only display messages if the chat window is not focused - focusTarget.addWindowFocusListener(new WindowAdapter() { - - @Override - public void windowGainedFocus(WindowEvent e) { displayMessages = false; } - - @Override - public void windowLostFocus(WindowEvent e) { displayMessages = true; } - }); + // Only display messages if the stage is not focused + stage.focusedProperty().addListener((ov, onHidden, onShown) -> displayMessages = ov.getValue()); // Show the window if the user clicks on the icon - trayIcon.addActionListener(evt -> { focusTarget.setVisible(true); focusTarget.requestFocus(); }); + trayIcon.addActionListener(evt -> Platform.runLater(() -> { stage.show(); stage.requestFocus(); })); // Start processing message events // TODO: Handle other message types @@ -93,7 +83,6 @@ public class StatusTrayIcon { try { SystemTray.getSystemTray().add(trayIcon); } catch (final AWTException e) { - EnvoyLog.getLogger(StatusTrayIcon.class).log(Level.INFO, "Could not display StatusTrayIcon: ", e); throw new EnvoyException("Could not attach Envoy tray icon to system tray.", e); } } From 2ed30c56cdb24e7d08a67752e920302ffcffcf72 Mon Sep 17 00:00:00 2001 From: kske Date: Thu, 23 Jul 2020 18:53:36 +0200 Subject: [PATCH 2/5] Iconify stage on close, reopen it with the tray icon --- .../main/java/envoy/client/ui/StatusTrayIcon.java | 4 ++-- .../envoy/client/ui/controller/LoginScene.java | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/client/src/main/java/envoy/client/ui/StatusTrayIcon.java b/client/src/main/java/envoy/client/ui/StatusTrayIcon.java index ec22b68..f60645a 100644 --- a/client/src/main/java/envoy/client/ui/StatusTrayIcon.java +++ b/client/src/main/java/envoy/client/ui/StatusTrayIcon.java @@ -54,7 +54,7 @@ public class StatusTrayIcon { final PopupMenu popup = new PopupMenu(); final MenuItem exitMenuItem = new MenuItem("Exit"); - exitMenuItem.addActionListener(evt -> System.exit(0)); + exitMenuItem.addActionListener(evt -> { Platform.exit(); System.exit(0); }); popup.add(exitMenuItem); trayIcon.setPopupMenu(popup); @@ -63,7 +63,7 @@ public class StatusTrayIcon { stage.focusedProperty().addListener((ov, onHidden, onShown) -> displayMessages = ov.getValue()); // Show the window if the user clicks on the icon - trayIcon.addActionListener(evt -> Platform.runLater(() -> { stage.show(); stage.requestFocus(); })); + trayIcon.addActionListener(evt -> Platform.runLater(() -> { stage.setIconified(false); stage.toFront(); stage.requestFocus(); })); // Start processing message events // TODO: Handle other message types diff --git a/client/src/main/java/envoy/client/ui/controller/LoginScene.java b/client/src/main/java/envoy/client/ui/controller/LoginScene.java index 93978e8..7d058cd 100644 --- a/client/src/main/java/envoy/client/ui/controller/LoginScene.java +++ b/client/src/main/java/envoy/client/ui/controller/LoginScene.java @@ -15,9 +15,7 @@ import javafx.scene.control.Alert.AlertType; import envoy.client.data.*; import envoy.client.net.Client; import envoy.client.net.WriteProxy; -import envoy.client.ui.ClearableTextField; -import envoy.client.ui.SceneContext; -import envoy.client.ui.Startup; +import envoy.client.ui.*; import envoy.data.LoginCredentials; import envoy.data.User; import envoy.data.User.UserStatus; @@ -210,5 +208,16 @@ public final class LoginScene { sceneContext.getStage().setMinWidth(350); sceneContext.load(SceneContext.SceneInfo.CHAT_SCENE); sceneContext.getController().initializeData(sceneContext, localDB, client, writeProxy); + + try { + new StatusTrayIcon(sceneContext.getStage()).show(); + sceneContext.getStage().setOnCloseRequest(e -> { + sceneContext.getStage().setIconified(true); + e.consume(); + }); + } catch (EnvoyException e) { + e.printStackTrace(); + } + } } From 07fbe3438a659da3c7a79c4234034f5d6e8c27f5 Mon Sep 17 00:00:00 2001 From: kske Date: Thu, 23 Jul 2020 19:20:58 +0200 Subject: [PATCH 3/5] Notify about messages when out of focus --- client/src/main/java/envoy/client/ui/StatusTrayIcon.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/main/java/envoy/client/ui/StatusTrayIcon.java b/client/src/main/java/envoy/client/ui/StatusTrayIcon.java index f60645a..80a83db 100644 --- a/client/src/main/java/envoy/client/ui/StatusTrayIcon.java +++ b/client/src/main/java/envoy/client/ui/StatusTrayIcon.java @@ -60,7 +60,8 @@ public class StatusTrayIcon { trayIcon.setPopupMenu(popup); // Only display messages if the stage is not focused - stage.focusedProperty().addListener((ov, onHidden, onShown) -> displayMessages = ov.getValue()); + stage.focusedProperty().addListener((ov, onHidden, onShown) -> displayMessages = ov.getValue() == Boolean.FALSE); + // Show the window if the user clicks on the icon trayIcon.addActionListener(evt -> Platform.runLater(() -> { stage.setIconified(false); stage.toFront(); stage.requestFocus(); })); From 59354c403d74bf3fb826fafd6d131840a5867ef1 Mon Sep 17 00:00:00 2001 From: kske Date: Fri, 24 Jul 2020 09:57:09 +0200 Subject: [PATCH 4/5] Integrated the tray icon with the hide on close setting --- .../main/java/envoy/client/data/Settings.java | 8 ++-- .../java/envoy/client/ui/StatusTrayIcon.java | 43 +++++++++++-------- .../client/ui/controller/LoginScene.java | 22 +++++++--- .../ui/settings/GeneralSettingsPane.java | 2 +- 4 files changed, 45 insertions(+), 30 deletions(-) diff --git a/client/src/main/java/envoy/client/data/Settings.java b/client/src/main/java/envoy/client/data/Settings.java index fc16c26..239175d 100644 --- a/client/src/main/java/envoy/client/data/Settings.java +++ b/client/src/main/java/envoy/client/data/Settings.java @@ -75,7 +75,7 @@ public class Settings { private void supplementDefaults() { items.putIfAbsent("enterToSend", new SettingsItem<>(true, "Enter to send", "Sends a message by pressing the enter key.")); - items.putIfAbsent("onCloseMode", new SettingsItem<>(true, "Hide on close", "Hides the chat window when it is closed.")); + items.putIfAbsent("hideOnClose", new SettingsItem<>(true, "Hide on close", "Hides the chat window when it is closed.")); items.putIfAbsent("currentTheme", new SettingsItem<>("dark", "Current Theme Name", "The name of the currently selected theme.")); } @@ -124,15 +124,15 @@ public class Settings { * @return the current on close mode. * @since Envoy Client v0.3-alpha */ - public Boolean getCurrentOnCloseMode() { return (Boolean) items.get("onCloseMode").get(); } + public Boolean isHideOnClose() { return (Boolean) items.get("hideOnClose").get(); } /** * Sets the current on close mode. * - * @param currentOnCloseMode the on close mode that should be set. + * @param hideOnClose the on close mode that should be set. * @since Envoy Client v0.3-alpha */ - public void setCurrentOnCloseMode(boolean currentOnCloseMode) { ((SettingsItem) items.get("onCloseMode")).set(currentOnCloseMode); } + public void setHideOnClose(boolean hideOnClose) { ((SettingsItem) items.get("hideOnClose")).set(hideOnClose); } /** * @return the items diff --git a/client/src/main/java/envoy/client/ui/StatusTrayIcon.java b/client/src/main/java/envoy/client/ui/StatusTrayIcon.java index 80a83db..9555117 100644 --- a/client/src/main/java/envoy/client/ui/StatusTrayIcon.java +++ b/client/src/main/java/envoy/client/ui/StatusTrayIcon.java @@ -9,7 +9,6 @@ import javafx.stage.Stage; import envoy.client.event.MessageCreationEvent; import envoy.data.Message; import envoy.event.EventBus; -import envoy.exception.EnvoyException; /** * Project: envoy-client
@@ -34,19 +33,21 @@ public class StatusTrayIcon { */ private boolean displayMessages = false; + /** + * @return true if the status tray icon is supported on this platform + * @since Envoy Client v0.2-beta + */ + public static boolean isSupported() { return SystemTray.isSupported(); } + /** * Creates a {@link StatusTrayIcon} with the Envoy logo, a tool tip and a pop-up * menu. * * @param stage the stage which focus determines if message * notifications are displayed - * @throws EnvoyException if the currently used OS does not support the System - * Tray API * @since Envoy Client v0.2-beta */ - public StatusTrayIcon(Stage stage) throws EnvoyException { - if (!SystemTray.isSupported()) throw new EnvoyException("The Envoy tray icon is not supported."); - + public StatusTrayIcon(Stage stage) { trayIcon = new TrayIcon(IconUtil.loadAWTCompatible("/icons/envoy_logo.png"), "Envoy"); trayIcon.setImageAutoSize(true); trayIcon.setToolTip("You are notified if you have unread messages."); @@ -61,30 +62,36 @@ public class StatusTrayIcon { // Only display messages if the stage is not focused stage.focusedProperty().addListener((ov, onHidden, onShown) -> displayMessages = ov.getValue() == Boolean.FALSE); - // Show the window if the user clicks on the icon trayIcon.addActionListener(evt -> Platform.runLater(() -> { stage.setIconified(false); stage.toFront(); stage.requestFocus(); })); // Start processing message events - // TODO: Handle other message types - EventBus.getInstance() - .register(MessageCreationEvent.class, - evt -> { if (displayMessages) trayIcon.displayMessage("New message received", evt.get().getText(), MessageType.INFO); }); + EventBus.getInstance().register(MessageCreationEvent.class, evt -> { + if (displayMessages) trayIcon + .displayMessage( + evt.get().hasAttachment() ? "New " + evt.get().getAttachment().getType().toString().toLowerCase() + " message received" + : "New message received", + evt.get().getText(), + MessageType.INFO); + }); } /** - * Makes this {@link StatusTrayIcon} appear in the system tray. + * Makes the icon appear in the system tray. * - * @throws EnvoyException if the status icon could not be attaches to the system - * tray for system-internal reasons * @since Envoy Client v0.2-alpha */ - public void show() throws EnvoyException { + public void show() { try { SystemTray.getSystemTray().add(trayIcon); - } catch (final AWTException e) { - throw new EnvoyException("Could not attach Envoy tray icon to system tray.", e); - } + } catch (AWTException e) {} } + + /** + * Removes the icon from the system tray. + * + * @since Envoy Client v0.2-beta + */ + public void hide() { SystemTray.getSystemTray().remove(trayIcon); } } diff --git a/client/src/main/java/envoy/client/ui/controller/LoginScene.java b/client/src/main/java/envoy/client/ui/controller/LoginScene.java index 7d058cd..177524b 100644 --- a/client/src/main/java/envoy/client/ui/controller/LoginScene.java +++ b/client/src/main/java/envoy/client/ui/controller/LoginScene.java @@ -62,6 +62,7 @@ public final class LoginScene { private static final Logger logger = EnvoyLog.getLogger(LoginScene.class); private static final EventBus eventBus = EventBus.getInstance(); private static final ClientConfig config = ClientConfig.getInstance(); + private static final Settings settings = Settings.getInstance(); @FXML private void initialize() { @@ -209,15 +210,22 @@ public final class LoginScene { sceneContext.load(SceneContext.SceneInfo.CHAT_SCENE); sceneContext.getController().initializeData(sceneContext, localDB, client, writeProxy); - try { - new StatusTrayIcon(sceneContext.getStage()).show(); + if (StatusTrayIcon.isSupported()) { + + // Configure hide on close sceneContext.getStage().setOnCloseRequest(e -> { - sceneContext.getStage().setIconified(true); - e.consume(); + if (settings.isHideOnClose()) { + sceneContext.getStage().setIconified(true); + e.consume(); + } + }); + + // Initialize status tray icon + final var trayIcon = new StatusTrayIcon(sceneContext.getStage()); + settings.getItems().get("hideOnClose").setChangeHandler(c -> { + if (((Boolean) c)) trayIcon.show(); + else trayIcon.hide(); }); - } catch (EnvoyException e) { - e.printStackTrace(); } - } } diff --git a/client/src/main/java/envoy/client/ui/settings/GeneralSettingsPane.java b/client/src/main/java/envoy/client/ui/settings/GeneralSettingsPane.java index e6d5761..c82cb18 100644 --- a/client/src/main/java/envoy/client/ui/settings/GeneralSettingsPane.java +++ b/client/src/main/java/envoy/client/ui/settings/GeneralSettingsPane.java @@ -31,7 +31,7 @@ public class GeneralSettingsPane extends SettingsPane { final var vbox = new VBox(); // TODO: Support other value types - List.of("onCloseMode", "enterToSend") + List.of("hideOnClose", "enterToSend") .stream() .map(settings.getItems()::get) .map(i -> new SettingsCheckbox((SettingsItem) i)) From 2ffcad9d3565cb0aa677ff45ec3a7df930613727 Mon Sep 17 00:00:00 2001 From: kske Date: Fri, 24 Jul 2020 10:25:35 +0200 Subject: [PATCH 5/5] Apply suggestions from code review --- client/src/main/java/envoy/client/data/Settings.java | 2 +- client/src/main/java/envoy/client/ui/StatusTrayIcon.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/main/java/envoy/client/data/Settings.java b/client/src/main/java/envoy/client/data/Settings.java index 239175d..d511553 100644 --- a/client/src/main/java/envoy/client/data/Settings.java +++ b/client/src/main/java/envoy/client/data/Settings.java @@ -129,7 +129,7 @@ public class Settings { /** * Sets the current on close mode. * - * @param hideOnClose the on close mode that should be set. + * @param hideOnClose whether the application should be minimized on close * @since Envoy Client v0.3-alpha */ public void setHideOnClose(boolean hideOnClose) { ((SettingsItem) items.get("hideOnClose")).set(hideOnClose); } diff --git a/client/src/main/java/envoy/client/ui/StatusTrayIcon.java b/client/src/main/java/envoy/client/ui/StatusTrayIcon.java index 9555117..10c5300 100644 --- a/client/src/main/java/envoy/client/ui/StatusTrayIcon.java +++ b/client/src/main/java/envoy/client/ui/StatusTrayIcon.java @@ -34,7 +34,7 @@ public class StatusTrayIcon { private boolean displayMessages = false; /** - * @return true if the status tray icon is supported on this platform + * @return {@code true} if the status tray icon is supported on this platform * @since Envoy Client v0.2-beta */ public static boolean isSupported() { return SystemTray.isSupported(); } @@ -43,7 +43,7 @@ public class StatusTrayIcon { * Creates a {@link StatusTrayIcon} with the Envoy logo, a tool tip and a pop-up * menu. * - * @param stage the stage which focus determines if message + * @param stage the stage whose focus determines if message * notifications are displayed * @since Envoy Client v0.2-beta */ @@ -61,7 +61,7 @@ public class StatusTrayIcon { trayIcon.setPopupMenu(popup); // Only display messages if the stage is not focused - stage.focusedProperty().addListener((ov, onHidden, onShown) -> displayMessages = ov.getValue() == Boolean.FALSE); + stage.focusedProperty().addListener((ov, onHidden, onShown) -> displayMessages = !ov.getValue()); // Show the window if the user clicks on the icon trayIcon.addActionListener(evt -> Platform.runLater(() -> { stage.setIconified(false); stage.toFront(); stage.requestFocus(); }));