Merge pull request #27 from informatik-ag-ngl/f/status_tray
Restore Status Tray Functionality
This commit is contained in:
		@@ -75,7 +75,7 @@ public class Settings {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	private void supplementDefaults() {
 | 
						private void supplementDefaults() {
 | 
				
			||||||
		items.putIfAbsent("enterToSend", new SettingsItem<>(true, "Enter to send", "Sends a message by pressing the enter key."));
 | 
							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."));
 | 
							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.
 | 
						 * @return the current on close mode.
 | 
				
			||||||
	 * @since Envoy Client v0.3-alpha
 | 
						 * @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.
 | 
						 * 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
 | 
						 * @since Envoy Client v0.3-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void setCurrentOnCloseMode(boolean currentOnCloseMode) { ((SettingsItem<Boolean>) items.get("onCloseMode")).set(currentOnCloseMode); }
 | 
						public void setHideOnClose(boolean hideOnClose) { ((SettingsItem<Boolean>) items.get("hideOnClose")).set(hideOnClose); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return the items
 | 
						 * @return the items
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,13 @@
 | 
				
			|||||||
package envoy.client.ui;
 | 
					package envoy.client.ui;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.awt.image.BufferedImage;
 | 
				
			||||||
 | 
					import java.io.IOException;
 | 
				
			||||||
import java.util.EnumMap;
 | 
					import java.util.EnumMap;
 | 
				
			||||||
import java.util.EnumSet;
 | 
					import java.util.EnumSet;
 | 
				
			||||||
import java.util.logging.Level;
 | 
					import java.util.logging.Level;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.imageio.ImageIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javafx.scene.image.Image;
 | 
					import javafx.scene.image.Image;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.Settings;
 | 
					import envoy.client.data.Settings;
 | 
				
			||||||
@@ -145,6 +149,23 @@ public class IconUtil {
 | 
				
			|||||||
		return icons;
 | 
							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
 | 
						 * This method should be called if the display of an image depends upon the
 | 
				
			||||||
	 * currently active theme.<br>
 | 
						 * currently active theme.<br>
 | 
				
			||||||
@@ -154,7 +175,7 @@ public class IconUtil {
 | 
				
			|||||||
	 * @return the theme specific folder
 | 
						 * @return the theme specific folder
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static String themeSpecificSubFolder() {
 | 
						private static String themeSpecificSubFolder() {
 | 
				
			||||||
		return Settings.getInstance().isUsingDefaultTheme() ? Settings.getInstance().getCurrentTheme() + "/" : "";
 | 
							return Settings.getInstance().isUsingDefaultTheme() ? Settings.getInstance().getCurrentTheme() + "/" : "";
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,15 +2,13 @@ package envoy.client.ui;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import java.awt.*;
 | 
					import java.awt.*;
 | 
				
			||||||
import java.awt.TrayIcon.MessageType;
 | 
					import java.awt.TrayIcon.MessageType;
 | 
				
			||||||
import java.awt.event.WindowAdapter;
 | 
					
 | 
				
			||||||
import java.awt.event.WindowEvent;
 | 
					import javafx.application.Platform;
 | 
				
			||||||
import java.util.logging.Level;
 | 
					import javafx.stage.Stage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.event.MessageCreationEvent;
 | 
					import envoy.client.event.MessageCreationEvent;
 | 
				
			||||||
import envoy.data.Message;
 | 
					import envoy.data.Message;
 | 
				
			||||||
import envoy.event.EventBus;
 | 
					import envoy.event.EventBus;
 | 
				
			||||||
import envoy.exception.EnvoyException;
 | 
					 | 
				
			||||||
import envoy.util.EnvoyLog;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Project: <strong>envoy-client</strong><br>
 | 
					 * Project: <strong>envoy-client</strong><br>
 | 
				
			||||||
@@ -35,66 +33,65 @@ public class StatusTrayIcon {
 | 
				
			|||||||
	 */
 | 
						 */
 | 
				
			||||||
	private boolean displayMessages = false;
 | 
						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
 | 
						 * Creates a {@link StatusTrayIcon} with the Envoy logo, a tool tip and a pop-up
 | 
				
			||||||
	 * menu.
 | 
						 * menu.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param focusTarget the {@link Window} which focus determines if message
 | 
						 * @param stage the stage whose focus determines if message
 | 
				
			||||||
	 *                    notifications are displayed
 | 
						 *              notifications are displayed
 | 
				
			||||||
	 * @throws EnvoyException if the currently used OS does not support the System
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 *                        Tray API
 | 
					 | 
				
			||||||
	 * @since Envoy Client v0.2-alpha
 | 
					 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public StatusTrayIcon(Window focusTarget) throws EnvoyException {
 | 
						public StatusTrayIcon(Stage stage) {
 | 
				
			||||||
		if (!SystemTray.isSupported()) throw new EnvoyException("The Envoy tray icon is not supported.");
 | 
							trayIcon = new TrayIcon(IconUtil.loadAWTCompatible("/icons/envoy_logo.png"), "Envoy");
 | 
				
			||||||
 | 
					 | 
				
			||||||
		final ClassLoader	loader	= Thread.currentThread().getContextClassLoader();
 | 
					 | 
				
			||||||
		final Image			img		= Toolkit.getDefaultToolkit().createImage(loader.getResource("envoy_logo.png"));
 | 
					 | 
				
			||||||
		trayIcon = new TrayIcon(img, "Envoy Client");
 | 
					 | 
				
			||||||
		trayIcon.setImageAutoSize(true);
 | 
							trayIcon.setImageAutoSize(true);
 | 
				
			||||||
		trayIcon.setToolTip("You are notified if you have unread messages.");
 | 
							trayIcon.setToolTip("You are notified if you have unread messages.");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		final PopupMenu popup = new PopupMenu();
 | 
							final PopupMenu popup = new PopupMenu();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		final MenuItem exitMenuItem = new MenuItem("Exit");
 | 
							final MenuItem exitMenuItem = new MenuItem("Exit");
 | 
				
			||||||
		exitMenuItem.addActionListener(evt -> System.exit(0));
 | 
							exitMenuItem.addActionListener(evt -> { Platform.exit(); System.exit(0); });
 | 
				
			||||||
		popup.add(exitMenuItem);
 | 
							popup.add(exitMenuItem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		trayIcon.setPopupMenu(popup);
 | 
							trayIcon.setPopupMenu(popup);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Only display messages if the chat window is not focused
 | 
							// Only display messages if the stage is not focused
 | 
				
			||||||
		focusTarget.addWindowFocusListener(new WindowAdapter() {
 | 
							stage.focusedProperty().addListener((ov, onHidden, onShown) -> displayMessages = !ov.getValue());
 | 
				
			||||||
 | 
					 | 
				
			||||||
			@Override
 | 
					 | 
				
			||||||
			public void windowGainedFocus(WindowEvent e) { displayMessages = false; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			@Override
 | 
					 | 
				
			||||||
			public void windowLostFocus(WindowEvent e) { displayMessages = true; }
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Show the window if the user clicks on the icon
 | 
							// 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
 | 
							// Start processing message events
 | 
				
			||||||
		// TODO: Handle other message types
 | 
							EventBus.getInstance().register(MessageCreationEvent.class, evt -> {
 | 
				
			||||||
		EventBus.getInstance()
 | 
								if (displayMessages) trayIcon
 | 
				
			||||||
			.register(MessageCreationEvent.class,
 | 
									.displayMessage(
 | 
				
			||||||
					evt -> { if (displayMessages) trayIcon.displayMessage("New message received", evt.get().getText(), MessageType.INFO); });
 | 
											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
 | 
						 * @since Envoy Client v0.2-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void show() throws EnvoyException {
 | 
						public void show() {
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			SystemTray.getSystemTray().add(trayIcon);
 | 
								SystemTray.getSystemTray().add(trayIcon);
 | 
				
			||||||
		} catch (final AWTException e) {
 | 
							} catch (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);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Removes the icon from the system tray.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void hide() { SystemTray.getSystemTray().remove(trayIcon); }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,9 +15,7 @@ import javafx.scene.control.Alert.AlertType;
 | 
				
			|||||||
import envoy.client.data.*;
 | 
					import envoy.client.data.*;
 | 
				
			||||||
import envoy.client.net.Client;
 | 
					import envoy.client.net.Client;
 | 
				
			||||||
import envoy.client.net.WriteProxy;
 | 
					import envoy.client.net.WriteProxy;
 | 
				
			||||||
import envoy.client.ui.ClearableTextField;
 | 
					import envoy.client.ui.*;
 | 
				
			||||||
import envoy.client.ui.SceneContext;
 | 
					 | 
				
			||||||
import envoy.client.ui.Startup;
 | 
					 | 
				
			||||||
import envoy.data.LoginCredentials;
 | 
					import envoy.data.LoginCredentials;
 | 
				
			||||||
import envoy.data.User;
 | 
					import envoy.data.User;
 | 
				
			||||||
import envoy.data.User.UserStatus;
 | 
					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 Logger			logger		= EnvoyLog.getLogger(LoginScene.class);
 | 
				
			||||||
	private static final EventBus		eventBus	= EventBus.getInstance();
 | 
						private static final EventBus		eventBus	= EventBus.getInstance();
 | 
				
			||||||
	private static final ClientConfig	config		= ClientConfig.getInstance();
 | 
						private static final ClientConfig	config		= ClientConfig.getInstance();
 | 
				
			||||||
 | 
						private static final Settings		settings	= Settings.getInstance();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private void initialize() {
 | 
						private void initialize() {
 | 
				
			||||||
@@ -210,5 +209,23 @@ public final class LoginScene {
 | 
				
			|||||||
		sceneContext.getStage().setMinWidth(350);
 | 
							sceneContext.getStage().setMinWidth(350);
 | 
				
			||||||
		sceneContext.load(SceneContext.SceneInfo.CHAT_SCENE);
 | 
							sceneContext.load(SceneContext.SceneInfo.CHAT_SCENE);
 | 
				
			||||||
		sceneContext.<ChatScene>getController().initializeData(sceneContext, localDB, client, writeProxy);
 | 
							sceneContext.<ChatScene>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();
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,7 +31,7 @@ public class GeneralSettingsPane extends SettingsPane {
 | 
				
			|||||||
		final var vbox = new VBox();
 | 
							final var vbox = new VBox();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// TODO: Support other value types
 | 
							// TODO: Support other value types
 | 
				
			||||||
		List.of("onCloseMode", "enterToSend")
 | 
							List.of("hideOnClose", "enterToSend")
 | 
				
			||||||
			.stream()
 | 
								.stream()
 | 
				
			||||||
			.map(settings.getItems()::get)
 | 
								.map(settings.getItems()::get)
 | 
				
			||||||
			.map(i -> new SettingsCheckbox((SettingsItem<Boolean>) i))
 | 
								.map(i -> new SettingsCheckbox((SettingsItem<Boolean>) i))
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user