diff --git a/client/src/main/java/envoy/client/data/Settings.java b/client/src/main/java/envoy/client/data/Settings.java index fc16c26..d511553 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 whether the application should be minimized on close * @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/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..10c5300 100644 --- a/client/src/main/java/envoy/client/ui/StatusTrayIcon.java +++ b/client/src/main/java/envoy/client/ui/StatusTrayIcon.java @@ -2,15 +2,13 @@ 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
@@ -35,66 +33,65 @@ public class StatusTrayIcon { */ private boolean displayMessages = false; + /** + * @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(); } + /** * 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 - * @throws EnvoyException if the currently used OS does not support the System - * Tray API - * @since Envoy Client v0.2-alpha + * @param stage the stage whose focus determines if message + * notifications are displayed + * @since Envoy Client v0.2-beta */ - public StatusTrayIcon(Window focusTarget) 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"); + 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."); 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); - // 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.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) { - 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); - } + } 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 93978e8..177524b 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; @@ -64,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() { @@ -210,5 +209,23 @@ public final class LoginScene { sceneContext.getStage().setMinWidth(350); sceneContext.load(SceneContext.SceneInfo.CHAT_SCENE); sceneContext.getController().initializeData(sceneContext, localDB, client, writeProxy); + + if (StatusTrayIcon.isSupported()) { + + // Configure hide on close + sceneContext.getStage().setOnCloseRequest(e -> { + 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(); + }); + } } } 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))