From f6626d15376eefcb158a683047814a179e61c675 Mon Sep 17 00:00:00 2001 From: dorving Date: Wed, 3 May 2023 21:09:57 +0200 Subject: [PATCH] Added system tray icon support --- G-Earth/src/main/java/gearth/GEarth.java | 9 +- .../main/java/gearth/ui/GEarthTrayIcon.java | 105 ++++++++++++++++++ .../extensions/ExtensionsController.java | 9 +- 3 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 G-Earth/src/main/java/gearth/ui/GEarthTrayIcon.java diff --git a/G-Earth/src/main/java/gearth/GEarth.java b/G-Earth/src/main/java/gearth/GEarth.java index cc5fb52..abddbd1 100644 --- a/G-Earth/src/main/java/gearth/GEarth.java +++ b/G-Earth/src/main/java/gearth/GEarth.java @@ -5,6 +5,7 @@ import gearth.misc.Cacher; import gearth.misc.UpdateChecker; import gearth.misc.listenerpattern.ObservableObject; import gearth.ui.GEarthController; +import gearth.ui.GEarthTrayIcon; import gearth.ui.themes.Theme; import gearth.ui.themes.ThemeFactory; import gearth.ui.titlebar.TitleBarConfig; @@ -41,7 +42,6 @@ public class GEarth extends Application { public void start(Stage primaryStage) throws Exception{ main = this; stage = primaryStage; - FXMLLoader loader = new FXMLLoader(getClass().getResource("/gearth/ui/G-Earth.fxml")); Parent root; try { @@ -120,7 +120,8 @@ public class GEarth extends Application { stage.getScene().getStylesheets().add(GEarth.class.getResource(String.format("/gearth/ui/themes/%s/styling.css", theme.internalName())).toExternalForm()); stage.getIcons().clear(); - stage.getIcons().add(new Image(GEarth.class.getResourceAsStream(String.format("/gearth/ui/themes/%s/logoSmall.png", theme.overridesLogo() ? theme.internalName() : defaultTheme.internalName())))); + final Image image = new Image(GEarth.class.getResourceAsStream(String.format("/gearth/ui/themes/%s/logoSmall.png", theme.overridesLogo() ? theme.internalName() : defaultTheme.internalName()))); + stage.getIcons().add(image); stage.setTitle((theme.overridesTitle() ? theme.title() : defaultTheme.title()) + " " + GEarth.version); controller.infoController.img_logo.setImage(new Image(GEarth.class.getResourceAsStream( @@ -131,7 +132,11 @@ public class GEarth extends Application { ))); controller.infoController.version.setText(stage.getTitle()); // }); + GEarthTrayIcon.updateOrCreate(image); + } + public GEarthController getController() { + return controller; } public static String[] args; diff --git a/G-Earth/src/main/java/gearth/ui/GEarthTrayIcon.java b/G-Earth/src/main/java/gearth/ui/GEarthTrayIcon.java new file mode 100644 index 0000000..9637e7f --- /dev/null +++ b/G-Earth/src/main/java/gearth/ui/GEarthTrayIcon.java @@ -0,0 +1,105 @@ +package gearth.ui; + +import gearth.GEarth; +import gearth.services.extension_handler.extensions.GEarthExtension; +import gearth.services.extension_handler.extensions.extensionproducers.ExtensionProducerFactory; +import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionServer; +import javafx.application.Platform; +import javafx.embed.swing.SwingFXUtils; +import javafx.scene.image.Image; +import javafx.stage.Stage; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Stream; + +/** + * Adds a {@link TrayIcon} to the {@link SystemTray} for this G-Earth instance. + * + * @author Dorving + */ +public final class GEarthTrayIcon { + + private static final String TO_FRONT_LABEL = "To front"; + private static final String TO_BACK_LABEL = "To back"; + + private static PopupMenu menu; + + public static void updateOrCreate(Image image) { + + if (!SystemTray.isSupported()) + return; + + final NetworkExtensionServer server = ExtensionProducerFactory.getExtensionServer(); + final BufferedImage awtImage = SwingFXUtils.fromFXImage(image, null); + final String appTitle = "G-Earth " + GEarth.version + " (" + server.getPort() + ")"; + + final Optional trayIcon = Stream.of(SystemTray.getSystemTray().getTrayIcons()) + .filter(other -> Objects.equals(other.getToolTip(), appTitle)) + .findFirst(); + if (trayIcon.isPresent()) { + EventQueue.invokeLater(() -> trayIcon.get().setImage(awtImage)); + } else { + menu = new PopupMenu(); + menu.add(createToFrontOrBackMenuItem()); + menu.addSeparator(); + menu.addSeparator(); + menu.add(createInstallMenuItem()); + try { + SystemTray.getSystemTray().add(new TrayIcon(awtImage, appTitle, menu)); + } catch (AWTException e) { + e.printStackTrace(); + menu = null; + } + } + } + + private static MenuItem createToFrontOrBackMenuItem() { + final MenuItem showMenuItem = new MenuItem(TO_FRONT_LABEL); + showMenuItem.addActionListener(e -> { + if (Objects.equals(showMenuItem.getLabel(), TO_FRONT_LABEL)) { + showMenuItem.setLabel(TO_BACK_LABEL); + Platform.runLater(() -> GEarth.main.getController().getStage().toFront()); + } else { + showMenuItem.setLabel(TO_FRONT_LABEL); + Platform.runLater(() -> GEarth.main.getController().getStage().toBack()); + } + }); + return showMenuItem; + } + + private static MenuItem createInstallMenuItem() { + final MenuItem showMenuItem = new MenuItem("Install Extension..."); + showMenuItem.addActionListener(e -> + Optional.ofNullable(GEarth.main.getController()) + .map(c -> c.extensionsController) + .ifPresent(c -> Platform.runLater(() -> { + final Stage stage = c.parentController.getStage(); + final boolean isOnTop = stage.isAlwaysOnTop(); + stage.setAlwaysOnTop(true); // bit of a hack to force stage to front + c.installBtnClicked(null); + stage.setAlwaysOnTop(isOnTop); + }))); + return showMenuItem; + } + + /** + * Adds the argued extension as a menu item to {@link #menu}. + * + * @param extension the {@link GEarthExtension} to add to the {@link #menu}. + */ + public static void addExtension(GEarthExtension extension) { + + if (menu == null) + return; + + final MenuItem menuItem = new MenuItem("Show "+extension.getTitle()); + EventQueue.invokeLater(() -> menu.insert(menuItem, 2)); + menuItem + .addActionListener(e -> Platform.runLater(() -> extension.getClickedObservable().fireEvent())); + extension.getDeletedObservable() + .addListener(() -> EventQueue.invokeLater(() -> menu.remove(menuItem))); + } +} diff --git a/G-Earth/src/main/java/gearth/ui/subforms/extensions/ExtensionsController.java b/G-Earth/src/main/java/gearth/ui/subforms/extensions/ExtensionsController.java index 69b6546..7692d19 100644 --- a/G-Earth/src/main/java/gearth/ui/subforms/extensions/ExtensionsController.java +++ b/G-Earth/src/main/java/gearth/ui/subforms/extensions/ExtensionsController.java @@ -8,6 +8,7 @@ import gearth.services.extension_handler.extensions.implementations.network.exec import gearth.services.extension_handler.extensions.implementations.network.executer.ExtensionRunner; import gearth.services.extension_handler.extensions.implementations.network.executer.ExtensionRunnerFactory; import gearth.services.g_python.GPythonShell; +import gearth.ui.GEarthTrayIcon; import gearth.ui.SubForm; import gearth.ui.subforms.extensions.logger.ExtensionLogger; import gearth.ui.translations.LanguageBundle; @@ -18,6 +19,7 @@ import javafx.scene.control.*; import javafx.scene.layout.GridPane; import javafx.scene.layout.VBox; import javafx.stage.FileChooser; +import javafx.stage.Stage; import java.io.File; @@ -54,9 +56,10 @@ public class ExtensionsController extends SubForm { protected void onParentSet() { ExtensionItemContainerProducer producer = new ExtensionItemContainerProducer(extensioncontainer, scroller); extensionHandler = new ExtensionHandler(getHConnection()); - extensionHandler.getObservable().addListener((e -> { - Platform.runLater(() -> producer.extensionConnected(e)); - })); + extensionHandler.getObservable().addListener((e -> Platform.runLater(() -> { + producer.extensionConnected(e); + GEarthTrayIcon.addExtension(e); + }))); //noinspection OptionalGetWithoutIsPresent networkExtensionsProducer