diff --git a/src/main/java/com/codedead/opal/OpalApplication.java b/src/main/java/com/codedead/opal/OpalApplication.java index 935ecd4..93c9f79 100644 --- a/src/main/java/com/codedead/opal/OpalApplication.java +++ b/src/main/java/com/codedead/opal/OpalApplication.java @@ -65,9 +65,11 @@ public static void main(final String[] args) { * Method that is called by the JavaFX runtime * * @param primaryStage The initial Stage object + * @throws IOException When the {@link java.awt.TrayIcon} could not be created */ @Override - public void start(final Stage primaryStage) { + public void start(final Stage primaryStage) throws IOException { + Platform.setImplicitExit(false); final SettingsController settingsController; try { @@ -121,5 +123,11 @@ public void start(final Stage primaryStage) { logger.info("Showing the MainWindow"); primaryStage.show(); + + // Load tray icons after displaying the main stage to display the proper icon in the task bar / activities bar (linux) + mainWindowController.createTrayIcon(); + if (Boolean.parseBoolean(properties.getProperty("trayIcon", "false"))) { + mainWindowController.showTrayIcon(); + } } } diff --git a/src/main/java/com/codedead/opal/controller/AboutWindowController.java b/src/main/java/com/codedead/opal/controller/AboutWindowController.java index 42c9fba..2c2dec7 100644 --- a/src/main/java/com/codedead/opal/controller/AboutWindowController.java +++ b/src/main/java/com/codedead/opal/controller/AboutWindowController.java @@ -63,14 +63,6 @@ public void setSettingsController(final SettingsController settingsController) { translationBundle = ResourceBundle.getBundle("translations.OpalApplication", locale); } - /** - * Method that is invoked to initialize the FXML object - */ - @FXML - private void initialize() { - logger.info("Initializing AboutWindow"); - } - /** * Method that is called when the close button is selected */ diff --git a/src/main/java/com/codedead/opal/controller/MainWindowController.java b/src/main/java/com/codedead/opal/controller/MainWindowController.java index ac9744d..8f86144 100644 --- a/src/main/java/com/codedead/opal/controller/MainWindowController.java +++ b/src/main/java/com/codedead/opal/controller/MainWindowController.java @@ -13,6 +13,8 @@ import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.control.*; +import javafx.scene.control.Menu; +import javafx.scene.control.MenuItem; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.DragEvent; @@ -25,11 +27,15 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; +import java.util.List; import static com.codedead.opal.utils.SharedVariables.DEFAULT_LOCALE; @@ -66,6 +72,7 @@ public final class MainWindowController implements IAudioTimer { @FXML private MenuItem mniOpenSoundPreset; + private TrayIcon trayIcon; private SettingsController settingsController; private UpdateController updateController; private ResourceBundle translationBundle; @@ -267,8 +274,6 @@ public void run() { */ @FXML private void initialize() { - logger.info("Initializing MainWindow"); - mniOpenSoundPreset.setGraphic(new ImageView(new Image(Objects.requireNonNull(getClass().getResourceAsStream("/images/open.png"))))); mniSaveSoundPreset.setGraphic(new ImageView(new Image(Objects.requireNonNull(getClass().getResourceAsStream("/images/save.png"))))); mniReset.setGraphic(new ImageView(new Image(Objects.requireNonNull(getClass().getResourceAsStream("/images/refresh.png"))))); @@ -295,6 +300,98 @@ private void initialize() { }); } + /** + * Create a tray icon + * + * @throws IOException When the {@link TrayIcon} could not be created + */ + public void createTrayIcon() throws IOException { + if (!SystemTray.isSupported()) { + logger.warn("SystemTray is not supported"); + return; + } + + final SystemTray tray = SystemTray.getSystemTray(); + final Dimension trayIconSize = tray.getTrayIconSize(); + final PopupMenu popup = new PopupMenu(); + final BufferedImage trayIconImage = ImageIO.read(Objects.requireNonNull(getClass().getResource("/images/opal.png"))); + final TrayIcon localTrayIcon = new TrayIcon(trayIconImage.getScaledInstance(trayIconSize.width, trayIconSize.height, java.awt.Image.SCALE_SMOOTH)); + final java.awt.MenuItem displayItem = new java.awt.MenuItem(translationBundle.getString("Display")); + final java.awt.MenuItem settingsItem = new java.awt.MenuItem(translationBundle.getString("Settings")); + final java.awt.MenuItem aboutItem = new java.awt.MenuItem(translationBundle.getString("About")); + final java.awt.MenuItem exitItem = new java.awt.MenuItem(translationBundle.getString("Exit")); + + // Platform.runLater to run on the JavaFX thread + displayItem.addActionListener(e -> Platform.runLater(this::hideShowStage)); + settingsItem.addActionListener(e -> Platform.runLater(this::settingsAction)); + aboutItem.addActionListener(e -> Platform.runLater(this::aboutAction)); + exitItem.addActionListener(e -> Platform.runLater(this::exitAction)); + + popup.add(displayItem); + popup.addSeparator(); + popup.add(settingsItem); + popup.add(aboutItem); + popup.addSeparator(); + popup.add(exitItem); + + localTrayIcon.setToolTip("Opal"); + localTrayIcon.setPopupMenu(popup); + localTrayIcon.addMouseListener(new java.awt.event.MouseAdapter() { + @Override + public void mouseClicked(final java.awt.event.MouseEvent evt) { + if (evt.getClickCount() == 2) { + Platform.runLater(() -> hideShowStage()); + } + } + }); + + this.trayIcon = localTrayIcon; + } + + /** + * Display the tray icon + */ + public void showTrayIcon() { + if (trayIcon == null) { + logger.warn("TrayIcon cannot be null!"); + return; + } + + final SystemTray tray = SystemTray.getSystemTray(); + try { + if (!Arrays.asList(tray.getTrayIcons()).contains(trayIcon)) { + tray.add(trayIcon); + } + } catch (final AWTException e) { + logger.error("TrayIcon could not be added", e); + } + } + + /** + * Hide the tray icon + */ + public void hideTrayIcon() { + if (trayIcon == null) { + logger.warn("TrayIcon cannot be null!"); + return; + } + + final SystemTray tray = SystemTray.getSystemTray(); + tray.remove(trayIcon); + } + + /** + * Hide the current stage + */ + private void hideShowStage() { + final Stage stage = (Stage) grpControls.getScene().getWindow(); + if (stage.isShowing()) { + stage.hide(); + } else { + stage.show(); + } + } + /** * Open a sound preset */ diff --git a/src/main/java/com/codedead/opal/controller/SettingsWindowController.java b/src/main/java/com/codedead/opal/controller/SettingsWindowController.java index e00394d..2ea6309 100644 --- a/src/main/java/com/codedead/opal/controller/SettingsWindowController.java +++ b/src/main/java/com/codedead/opal/controller/SettingsWindowController.java @@ -30,6 +30,8 @@ public final class SettingsWindowController { + @FXML + private CheckBox chbTrayIcon; @FXML private ComboBox cboTheme; @FXML @@ -63,14 +65,6 @@ public SettingsWindowController() { logger.info("Initializing new SettingsWindowController object"); } - /** - * Method that is invoked to initialize the FXML object - */ - @FXML - private void initialize() { - logger.info("Initializing SettingsWindow"); - } - /** * Set the {@link SettingsController} object * @@ -165,6 +159,7 @@ private void loadSettings() { chbAutoUpdate.setSelected(Boolean.parseBoolean(settingsController.getProperties().getProperty("autoUpdate", "true"))); chbMediaButtons.setSelected(Boolean.parseBoolean(settingsController.getProperties().getProperty("mediaButtons", "true"))); chbDragDrop.setSelected(Boolean.parseBoolean(settingsController.getProperties().getProperty("dragDrop", "true"))); + chbTrayIcon.setSelected(Boolean.parseBoolean(settingsController.getProperties().getProperty("trayIcon", "false"))); chbTimerApplicationShutdown.setSelected(Boolean.parseBoolean(settingsController.getProperties().getProperty("timerApplicationShutdown", "false"))); cboDelayType.getSelectionModel().select(delayType); cboTheme.getSelectionModel().select(themeIndex); @@ -185,6 +180,7 @@ private void resetSettingsAction() { settingsController.createDefaultProperties(); settingsController.setProperties(settingsController.readPropertiesFile()); mainWindowController.loadMediaButtonVisibility(Boolean.parseBoolean(settingsController.getProperties().getProperty("mediaButtons", "true"))); + mainWindowController.hideTrayIcon(); loadSettings(); } catch (final IOException ex) { @@ -204,7 +200,14 @@ private void saveSettingsAction() { settingsController.getProperties().setProperty("autoUpdate", Boolean.toString(chbAutoUpdate.isSelected())); settingsController.getProperties().setProperty("mediaButtons", Boolean.toString(chbMediaButtons.isSelected())); settingsController.getProperties().setProperty("dragDrop", Boolean.toString(chbDragDrop.isSelected())); + settingsController.getProperties().setProperty("trayIcon", Boolean.toString(chbTrayIcon.isSelected())); + mainWindowController.loadMediaButtonVisibility(chbMediaButtons.isSelected()); + if (chbTrayIcon.isSelected()) { + mainWindowController.showTrayIcon(); + } else { + mainWindowController.hideTrayIcon(); + } showAlertIfLanguageMismatch(settingsController.getProperties().getProperty("locale", DEFAULT_LOCALE)); diff --git a/src/main/resources/default.properties b/src/main/resources/default.properties index ff373d7..3670a67 100644 --- a/src/main/resources/default.properties +++ b/src/main/resources/default.properties @@ -8,3 +8,4 @@ timerApplicationShutdown=false mediaButtons=true dragDrop=true theme=light +trayIcon=false diff --git a/src/main/resources/translations/OpalApplication.properties b/src/main/resources/translations/OpalApplication.properties index e2cf04e..7d3dd84 100644 --- a/src/main/resources/translations/OpalApplication.properties +++ b/src/main/resources/translations/OpalApplication.properties @@ -84,3 +84,5 @@ NordDark=Nord dark Space=Space Restaurant=Restaurant Cancel=Cancel +Display=Display +TrayIcon=Tray icon diff --git a/src/main/resources/translations/OpalApplication_de_DE.properties b/src/main/resources/translations/OpalApplication_de_DE.properties index b8879ed..3e8df0d 100644 --- a/src/main/resources/translations/OpalApplication_de_DE.properties +++ b/src/main/resources/translations/OpalApplication_de_DE.properties @@ -84,3 +84,5 @@ NordDark=Norden dunkel Space=Weltraum Restaurant=Restaurant Cancel=Abbrechen +Display=Anzeige +TrayIcon=Benachrichtigungssymbol diff --git a/src/main/resources/translations/OpalApplication_en_US.properties b/src/main/resources/translations/OpalApplication_en_US.properties index e2cf04e..7d3dd84 100644 --- a/src/main/resources/translations/OpalApplication_en_US.properties +++ b/src/main/resources/translations/OpalApplication_en_US.properties @@ -84,3 +84,5 @@ NordDark=Nord dark Space=Space Restaurant=Restaurant Cancel=Cancel +Display=Display +TrayIcon=Tray icon diff --git a/src/main/resources/translations/OpalApplication_es_ES.properties b/src/main/resources/translations/OpalApplication_es_ES.properties index 88be583..3d60d43 100644 --- a/src/main/resources/translations/OpalApplication_es_ES.properties +++ b/src/main/resources/translations/OpalApplication_es_ES.properties @@ -84,3 +84,5 @@ NordDark=Nord oscuro Space=Espacio Restaurant=Restaurante Cancel=Cancelar +Display=Monitor +TrayIcon=Icono de bandeja diff --git a/src/main/resources/translations/OpalApplication_fr_FR.properties b/src/main/resources/translations/OpalApplication_fr_FR.properties index c26495d..a5b4389 100644 --- a/src/main/resources/translations/OpalApplication_fr_FR.properties +++ b/src/main/resources/translations/OpalApplication_fr_FR.properties @@ -84,3 +84,5 @@ NordDark=Nord foncé Space=L'espace Restaurant=Restaurant Cancel=Annuler +Display=Affichage +TrayIcon=Icône de la barre diff --git a/src/main/resources/translations/OpalApplication_nl_NL.properties b/src/main/resources/translations/OpalApplication_nl_NL.properties index b414273..5a6b446 100644 --- a/src/main/resources/translations/OpalApplication_nl_NL.properties +++ b/src/main/resources/translations/OpalApplication_nl_NL.properties @@ -33,7 +33,7 @@ Phone=Telefoon Rain=Regen Reset=Reset ResetSettingsError=Kan de instellingen niet resetten! -RestartRequired=Gelieve de applicatie te herstarten om te taal te wijzigen. +RestartRequired=Gelieve de applicatie te herstarten om te taal te wijzigen! Save=Opslaan SaveSettingsError=Kan instellingen niet opslaan! SaveSoundPreset=Sla geluidsinstellingen op @@ -84,3 +84,5 @@ NordDark=Nord donker Space=De ruimte Restaurant=Restaurant Cancel=Annuleren +Display=Weergave +TrayIcon=Ladepictogram diff --git a/src/main/resources/translations/OpalApplication_ru_RU.properties b/src/main/resources/translations/OpalApplication_ru_RU.properties index 82d2065..912df6b 100644 --- a/src/main/resources/translations/OpalApplication_ru_RU.properties +++ b/src/main/resources/translations/OpalApplication_ru_RU.properties @@ -84,3 +84,5 @@ NordDark=Норд темный Space=Космос Restaurant=Ресторан Cancel=Отмена +Display=Отображать +TrayIcon=Значок в трее diff --git a/src/main/resources/windows/SettingsWindow.fxml b/src/main/resources/windows/SettingsWindow.fxml index 202f6d7..40cc484 100644 --- a/src/main/resources/windows/SettingsWindow.fxml +++ b/src/main/resources/windows/SettingsWindow.fxml @@ -43,6 +43,7 @@ + @@ -68,12 +69,18 @@ -