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
	 GitHub
						GitHub