diff --git a/src/dorkbox/systemTray/MenuItem.java b/src/dorkbox/systemTray/MenuItem.java index a0bc7be..29737bb 100644 --- a/src/dorkbox/systemTray/MenuItem.java +++ b/src/dorkbox/systemTray/MenuItem.java @@ -33,6 +33,8 @@ import dorkbox.util.SwingUtil; @SuppressWarnings({"unused", "SameParameterValue", "WeakerAccess"}) public class MenuItem extends Entry { + private static boolean alreadyEmittedTooltipWarning = false; + private volatile String text; private volatile File imageFile; private volatile ActionListener callback; @@ -40,6 +42,7 @@ class MenuItem extends Entry { // default enabled is always true private volatile boolean enabled = true; private volatile char mnemonicKey; + private volatile String tooltip; public MenuItem() { @@ -133,6 +136,7 @@ class MenuItem extends Entry { peer.setText(this); peer.setCallback(this); peer.setShortcut(this); + peer.setTooltip(this); } protected @@ -343,6 +347,47 @@ class MenuItem extends Entry { } } + /** + * Specifies the tooltip text, usually this is used to brand the SystemTray icon with your product's name, or to provide extra + * information during mouse-over for menu entries. + *

+ * NOTE: Maximum length is 64 characters long, and it is not supported on all Operating Systems and Desktop Environments. + *

+ * For more details on Linux see https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12. + * + * @param tooltipText the text to use as a mouse-over tooltip for the tray icon or menu entry, null to remove. + */ + public + void setTooltip(final String tooltipText) { + if (tooltipText != null) { + // this is a safety precaution, since the behavior of really long text is undefined. + if (tooltipText.length() > 64) { + throw new RuntimeException("Tooltip text cannot be longer than 64 characters."); + } + + if (!alreadyEmittedTooltipWarning) { + alreadyEmittedTooltipWarning = true; + SystemTray.logger.warn("Tooltips are not consistent across all platforms and tray types."); + } + } + + this.tooltip = tooltipText; + + if (peer != null) { + ((MenuItemPeer) peer).setTooltip(this); + } + } + + /** + * Gets the mouse-over tooltip for the meme entry. + * + * NOTE: This is not consistent across all platforms and tray types. + */ + public + String getTooltip() { + return this.tooltip; + } + @Override public void remove() { diff --git a/src/dorkbox/systemTray/SystemTray.java b/src/dorkbox/systemTray/SystemTray.java index 96d158d..89faa7b 100644 --- a/src/dorkbox/systemTray/SystemTray.java +++ b/src/dorkbox/systemTray/SystemTray.java @@ -675,7 +675,7 @@ class SystemTray { logger.debug("Forced tray type: {}", FORCE_TRAY_TYPE.name()); } logger.debug("Force GTK2: {}", FORCE_GTK2); - logger.debug("Prefer GTK3: {}", FORCE_GTK2); + logger.debug("Prefer GTK3: {}", PREFER_GTK3); } // Note: AppIndicators DO NOT support tooltips. We could try to create one, by creating a GTK widget and attaching it on @@ -777,8 +777,9 @@ class SystemTray { // at this point, the tray type is what it should be. If there are failures or special cases, all types will fall back to // Swing. - if (isNix && !isTrayType(trayType, TrayType.Swing)) { + if (isNix) { // linux/unix need access to GTK, so load it up before the tray is loaded! + // Swing gets the image size info VIA gtk, so this is important as well. GtkEventDispatch.startGui(FORCE_GTK2, PREFER_GTK3, DEBUG); GtkEventDispatch.waitForEventsToComplete(); @@ -788,65 +789,65 @@ class SystemTray { logger.debug("Is the system already running GTK? {}", Gtk.alreadyRunningGTK); } - // NOTE: appindicator1 -> GTk2, appindicator3 -> GTK3. - // appindicator3 doesn't support menu icons via GTK2!! - if (!Gtk.isLoaded) { - trayType = selectTypeQuietly(TrayType.Swing); - - logger.error("Unable to initialize GTK! Something is severely wrong! Using the Swing Tray type instead."); - } - else if (OSUtil.Linux.isArch()) { - // arch linux is fun! - - if (isTrayType(trayType, TrayType.AppIndicator) && !AppIndicator.isLoaded) { - // appindicators - - // requires the install of libappindicator which is GTK2 (as of 25DEC2016) - // requires the install of libappindicator3 which is GTK3 (as of 25DEC2016) + if (!isTrayType(trayType, TrayType.Swing)) { +// NOTE: appindicator1 -> GTk2, appindicator3 -> GTK3. + // appindicator3 doesn't support menu icons via GTK2!! + if (!Gtk.isLoaded) { trayType = selectTypeQuietly(TrayType.Swing); - if (Gtk.isGtk2) { - logger.warn("Unable to initialize AppIndicator for Arch linux, it requires GTK2! " + - "Please install libappindicator, for example: 'sudo pacman -S libappindicator'. " + + logger.error("Unable to initialize GTK! Something is severely wrong! Using the Swing Tray type instead."); + } + else if (OSUtil.Linux.isArch()) { + // arch linux is fun! + + if (isTrayType(trayType, TrayType.AppIndicator) && !AppIndicator.isLoaded) { + // appindicators + + // requires the install of libappindicator which is GTK2 (as of 25DEC2016) + // requires the install of libappindicator3 which is GTK3 (as of 25DEC2016) + trayType = selectTypeQuietly(TrayType.Swing); + + if (Gtk.isGtk2) { + logger.warn("Unable to initialize AppIndicator for Arch linux, it requires GTK2! " + + "Please install libappindicator, for example: 'sudo pacman -S libappindicator'. " + + "Using the Swing Tray type instead."); + } else { + logger.error("Unable to initialize AppIndicator for Arch linux, it requires GTK3! " + + "Please install libappindicator3, for example: 'sudo pacman -S libappindicator3'. " + + "Using the Swing Tray type instead."); // GTK3 + } + } else if (isTrayType(trayType, TrayType.GtkStatusIcon)) { + if (!Extension.isInstalled()) { + // Automatically install the extension for everyone except Arch. They are bonkers. + Extension.ENABLE_EXTENSION_INSTALL = false; + SystemTray.logger.info("You may need a work-around for showing the SystemTray icon - we suggest installing the " + + "the [Top Icons] plugin (https://extensions.gnome.org/extension/1031/topicons/) which moves " + + "icons from the *notification drawer* (it is normally collapsed) at the bottom left corner " + + "of the screen to the menu panel next to the clock."); + } + } + } + else if (isTrayType(trayType, TrayType.AppIndicator)) { + if (Gtk.isGtk2 && AppIndicator.isVersion3) { + trayType = selectTypeQuietly(TrayType.Swing); + + logger.warn("AppIndicator3 detected with GTK2, falling back to GTK2 system tray type. " + + "Please install libappindicator1 OR GTK3, for example: 'sudo apt-get install libappindicator1'. " + "Using the Swing Tray type instead."); - } else { - logger.error("Unable to initialize AppIndicator for Arch linux, it requires GTK3! " + - "Please install libappindicator3, for example: 'sudo pacman -S libappindicator3'. " + - "Using the Swing Tray type instead."); // GTK3 + } - } else if (isTrayType(trayType, TrayType.GtkStatusIcon)) { - if (!Extension.isInstalled()) { - // Automatically install the extension for everyone except Arch. They are bonkers. - Extension.ENABLE_EXTENSION_INSTALL = false; - SystemTray.logger.info("You may need a work-around for showing the SystemTray icon - we suggest installing the " + - "the [Top Icons] plugin (https://extensions.gnome.org/extension/1031/topicons/) which moves " + - "icons from the *notification drawer* (it is normally collapsed) at the bottom left corner " + - "of the screen to the menu panel next to the clock."); + else if (!AppIndicator.isLoaded) { + // YIKES. Try to fallback to GtkStatusIndicator, since AppIndicator couldn't load. + trayType = selectTypeQuietly(TrayType.Swing); + + logger.warn("Unable to initialize the AppIndicator correctly. Using the Swing Tray type instead."); } } } - else if (isTrayType(trayType, TrayType.AppIndicator)) { - if (Gtk.isGtk2 && AppIndicator.isVersion3) { - trayType = selectTypeQuietly(TrayType.Swing); - - logger.warn("AppIndicator3 detected with GTK2, falling back to GTK2 system tray type. " + - "Please install libappindicator1 OR GTK3, for example: 'sudo apt-get install libappindicator1'. " + - "Using the Swing Tray type instead."); - - } - else if (!AppIndicator.isLoaded) { - // YIKES. Try to fallback to GtkStatusIndicator, since AppIndicator couldn't load. - trayType = selectTypeQuietly(TrayType.Swing); - - logger.warn("Unable to initialize the AppIndicator correctly. Using the Swing Tray type instead."); - } - } } - - // have to make adjustments BEFORE the tray/menu image size calculations if (AUTO_FIX_INCONSISTENCIES && isTrayType(trayType, TrayType.Swing) && SystemTray.SWING_UI == null) { if (isNix) { diff --git a/src/dorkbox/systemTray/Tray.java b/src/dorkbox/systemTray/Tray.java index 834b156..f0d36af 100644 --- a/src/dorkbox/systemTray/Tray.java +++ b/src/dorkbox/systemTray/Tray.java @@ -90,32 +90,6 @@ class Tray extends Menu { } } - // method that is meant to be overridden by the tray implementations - protected - void setTooltip_(final String tooltipText) { - // default is NO OP - } - - /** - * Specifies the tooltip text, usually this is used to brand the SystemTray icon with your product's name. - *

- * The maximum length is 64 characters long, and it is not supported on all Operating Systems and Desktop - * Environments. - *

- * For more details on Linux see https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12. - * - * @param tooltipText the text to use as tooltip for the tray icon, null to remove - */ - final - void setTooltip(final String tooltipText) { - // this is a safety precaution, since the behavior of really long text is undefined. - if (tooltipText.length() > 64) { - throw new RuntimeException("Tooltip text cannot be longer than 64 characters."); - } - - setTooltip_(tooltipText); - } - /** * Specifies the new image to set for the tray icon. *

diff --git a/src/dorkbox/systemTray/peer/MenuItemPeer.java b/src/dorkbox/systemTray/peer/MenuItemPeer.java index aa45c0d..db57442 100644 --- a/src/dorkbox/systemTray/peer/MenuItemPeer.java +++ b/src/dorkbox/systemTray/peer/MenuItemPeer.java @@ -31,4 +31,6 @@ interface MenuItemPeer extends EntryPeer { void setCallback(MenuItem menuItem); void setShortcut(MenuItem menuItem); + + void setTooltip(MenuItem menuItem); } diff --git a/src/dorkbox/systemTray/ui/awt/AwtMenu.java b/src/dorkbox/systemTray/ui/awt/AwtMenu.java index d9e112a..df7d527 100644 --- a/src/dorkbox/systemTray/ui/awt/AwtMenu.java +++ b/src/dorkbox/systemTray/ui/awt/AwtMenu.java @@ -136,6 +136,18 @@ class AwtMenu implements MenuPeer { }); } + @Override + public + void setTooltip(final MenuItem menuItem) { + SwingUtil.invokeLater(new Runnable() { + @Override + public + void run() { + _native.setLabel(menuItem.getTooltip()); + } + }); + } + @Override public void remove() { diff --git a/src/dorkbox/systemTray/ui/awt/AwtMenuItem.java b/src/dorkbox/systemTray/ui/awt/AwtMenuItem.java index 99018c7..384a8eb 100644 --- a/src/dorkbox/systemTray/ui/awt/AwtMenuItem.java +++ b/src/dorkbox/systemTray/ui/awt/AwtMenuItem.java @@ -19,6 +19,7 @@ import java.awt.MenuShortcut; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import dorkbox.systemTray.MenuItem; import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.peer.MenuItemPeer; import dorkbox.util.SwingUtil; @@ -114,6 +115,12 @@ class AwtMenuItem implements MenuItemPeer { }); } + @Override + public + void setTooltip(final MenuItem menuItem) { + // no op. (awt menus cannot show tooltips) + } + @SuppressWarnings("Duplicates") @Override public diff --git a/src/dorkbox/systemTray/ui/awt/_AwtTray.java b/src/dorkbox/systemTray/ui/awt/_AwtTray.java index 673e8dd..883233c 100644 --- a/src/dorkbox/systemTray/ui/awt/_AwtTray.java +++ b/src/dorkbox/systemTray/ui/awt/_AwtTray.java @@ -182,6 +182,31 @@ class _AwtTray extends Tray { // no op } + @Override + public + void setTooltip(final MenuItem menuItem) { + final String text = menuItem.getTooltip(); + + if (tooltipText != null && tooltipText.equals(text) || + tooltipText == null && text != null) { + return; + } + + tooltipText = text; + + SwingUtil.invokeLater(new Runnable() { + @Override + public + void run() { + // don't want to matter which (setImage/setTooltip/setEnabled) is done first, and if the image/enabled is changed, we + // want to make sure keep the tooltip text the same as before. + if (trayIcon != null) { + trayIcon.setToolTip(text); + } + } + }); + } + @Override public void remove() { @@ -209,31 +234,6 @@ class _AwtTray extends Tray { bind(awtMenu, null, systemTray); } - @Override - protected - void setTooltip_(final String tooltipText) { - if (this.tooltipText.equals(tooltipText)){ - return; - } - this.tooltipText = tooltipText; - - SwingUtil.invokeLater(new Runnable() { - @Override - public - void run() { - if (tray == null) { - tray = SystemTray.getSystemTray(); - } - - // don't want to matter which (setImage/setTooltip/setEnabled) is done first, and if the image/enabled is changed, we - // want to make sure keep the tooltip text the same as before. - if (trayIcon != null) { - trayIcon.setToolTip(tooltipText); - } - } - }); - } - @Override public boolean hasImage() { diff --git a/src/dorkbox/systemTray/ui/gtk/GtkMenu.java b/src/dorkbox/systemTray/ui/gtk/GtkMenu.java index 9098756..308e74d 100644 --- a/src/dorkbox/systemTray/ui/gtk/GtkMenu.java +++ b/src/dorkbox/systemTray/ui/gtk/GtkMenu.java @@ -346,6 +346,12 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer { setText(menuItem); } + @Override + public + void setTooltip(final MenuItem menuItem) { + + } + /** * called when a child removes itself from the parent menu. Does not work for sub-menus * diff --git a/src/dorkbox/systemTray/ui/gtk/GtkMenuItem.java b/src/dorkbox/systemTray/ui/gtk/GtkMenuItem.java index 8e76ec9..a23a9b7 100644 --- a/src/dorkbox/systemTray/ui/gtk/GtkMenuItem.java +++ b/src/dorkbox/systemTray/ui/gtk/GtkMenuItem.java @@ -167,6 +167,20 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback { setText(menuItem); } + @Override + public + void setTooltip(final MenuItem menuItem) { + GtkEventDispatch.dispatch(new Runnable() { + @Override + public + void run() { + // NOTE: this will not work for AppIndicator tray types! + // null will remove the tooltip + Gtk2.gtk_widget_set_tooltip_text(_native, menuItem.getTooltip()); + } + }); + } + @SuppressWarnings("Duplicates") @Override public diff --git a/src/dorkbox/systemTray/ui/gtk/_AppIndicatorNativeTray.java b/src/dorkbox/systemTray/ui/gtk/_AppIndicatorNativeTray.java index 920a15f..95bb737 100644 --- a/src/dorkbox/systemTray/ui/gtk/_AppIndicatorNativeTray.java +++ b/src/dorkbox/systemTray/ui/gtk/_AppIndicatorNativeTray.java @@ -186,6 +186,12 @@ class _AppIndicatorNativeTray extends Tray { // no op. } + @Override + public + void setTooltip(final MenuItem menuItem) { + // no op. see https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12 + } + @Override public void remove() { @@ -231,12 +237,6 @@ class _AppIndicatorNativeTray extends Tray { bind(gtkMenu, null, systemTray); } - @Override - protected - void setTooltip_(final String tooltipText) { - // https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12 - } - @Override public boolean hasImage() { diff --git a/src/dorkbox/systemTray/ui/gtk/_GtkStatusIconNativeTray.java b/src/dorkbox/systemTray/ui/gtk/_GtkStatusIconNativeTray.java index 213c6af..ee4b95f 100644 --- a/src/dorkbox/systemTray/ui/gtk/_GtkStatusIconNativeTray.java +++ b/src/dorkbox/systemTray/ui/gtk/_GtkStatusIconNativeTray.java @@ -123,6 +123,27 @@ class _GtkStatusIconNativeTray extends Tray { // no op } + @Override + public + void setTooltip(final MenuItem menuItem) { + final String text = menuItem.getTooltip(); + + if (tooltipText != null && tooltipText.equals(text) || + tooltipText == null && text != null) { + return; + } + + tooltipText = text; + + GtkEventDispatch.dispatch(new Runnable() { + @Override + public + void run() { + Gtk2.gtk_status_icon_set_tooltip_text(trayIcon, text); + } + }); + } + @Override public void remove() { @@ -208,23 +229,6 @@ class _GtkStatusIconNativeTray extends Tray { } } - @Override - protected - void setTooltip_(final String tooltipText) { - if (this.tooltipText.equals(tooltipText)){ - return; - } - this.tooltipText = tooltipText; - - GtkEventDispatch.dispatch(new Runnable() { - @Override - public - void run() { - Gtk2.gtk_status_icon_set_tooltip_text(trayIcon, tooltipText); - } - }); - } - @Override public boolean hasImage() { diff --git a/src/dorkbox/systemTray/ui/swing/SwingMenu.java b/src/dorkbox/systemTray/ui/swing/SwingMenu.java index 6d4ece1..59350ea 100644 --- a/src/dorkbox/systemTray/ui/swing/SwingMenu.java +++ b/src/dorkbox/systemTray/ui/swing/SwingMenu.java @@ -173,6 +173,12 @@ class SwingMenu implements MenuPeer { }); } + @Override + public + void setTooltip(final MenuItem menuItem) { + + } + /** * This removes all menu entries from this menu AND this menu from it's parent */ diff --git a/src/dorkbox/systemTray/ui/swing/SwingMenuItem.java b/src/dorkbox/systemTray/ui/swing/SwingMenuItem.java index eac0de2..1e771f5 100644 --- a/src/dorkbox/systemTray/ui/swing/SwingMenuItem.java +++ b/src/dorkbox/systemTray/ui/swing/SwingMenuItem.java @@ -166,6 +166,18 @@ class SwingMenuItem implements MenuItemPeer { }); } + @Override + public + void setTooltip(final MenuItem menuItem) { + SwingUtil.invokeLater(new Runnable() { + @Override + public + void run() { + _native.setToolTipText(menuItem.getTooltip()); + } + }); + } + @Override public void remove() { diff --git a/src/dorkbox/systemTray/ui/swing/_SwingTray.java b/src/dorkbox/systemTray/ui/swing/_SwingTray.java index 2e6374f..0255957 100644 --- a/src/dorkbox/systemTray/ui/swing/_SwingTray.java +++ b/src/dorkbox/systemTray/ui/swing/_SwingTray.java @@ -167,6 +167,31 @@ class _SwingTray extends Tray { // no op } + @Override + public + void setTooltip(final MenuItem menuItem) { + final String text = menuItem.getTooltip(); + + if (tooltipText != null && tooltipText.equals(text) || + tooltipText == null && text != null) { + return; + } + + tooltipText = text; + + SwingUtil.invokeLater(new Runnable() { + @Override + public + void run() { + // don't want to matter which (setImage/setTooltip/setEnabled) is done first, and if the image/enabled is changed, we + // want to make sure keep the tooltip text the same as before. + if (trayIcon != null) { + trayIcon.setToolTip(text); + } + } + }); + } + @Override public void remove() { @@ -199,27 +224,6 @@ class _SwingTray extends Tray { bind(swingMenu, null, systemTray); } - @Override - protected - void setTooltip_(final String tooltipText) { - if (this.tooltipText.equals(tooltipText)){ - return; - } - this.tooltipText = tooltipText; - - SwingUtil.invokeLater(new Runnable() { - @Override - public - void run() { - // don't want to matter which (setImage/setTooltip/setEnabled) is done first, and if the image/enabled is changed, we - // want to make sure keep the tooltip text the same as before. - if (trayIcon != null) { - trayIcon.setToolTip(tooltipText); - } - } - }); - } - @Override public boolean hasImage() { diff --git a/test/dorkbox/TestTray.java b/test/dorkbox/TestTray.java index 4c567eb..b28ff41 100644 --- a/test/dorkbox/TestTray.java +++ b/test/dorkbox/TestTray.java @@ -102,12 +102,14 @@ class TestTray { entry.setCallback(callbackGray); entry.setImage(BLACK_MAIL); entry.setText("Delete Mail"); + entry.setTooltip(null); // remove the tooltip // systemTray.remove(menuEntry); } }); greenEntry.setImage(GREEN_MAIL); // case does not matter greenEntry.setShortcut('G'); + greenEntry.setTooltip("This means you have green mail!"); mainMenu.add(greenEntry); diff --git a/test/dorkbox/TestTrayJavaFX.java b/test/dorkbox/TestTrayJavaFX.java index 4680a21..de3c643 100644 --- a/test/dorkbox/TestTrayJavaFX.java +++ b/test/dorkbox/TestTrayJavaFX.java @@ -164,12 +164,14 @@ class TestTrayJavaFX { entry.setCallback(callbackGray); entry.setImage(BLACK_MAIL); entry.setText("Delete Mail"); + entry.setTooltip(null); // remove the tooltip // systemTray.remove(menuEntry); } }); greenEntry.setImage(GREEN_MAIL); // case does not matter greenEntry.setShortcut('G'); + greenEntry.setTooltip("This means you have green mail!"); mainMenu.add(greenEntry); diff --git a/test/dorkbox/TestTraySwt.java b/test/dorkbox/TestTraySwt.java index 3aecbd6..75b9141 100644 --- a/test/dorkbox/TestTraySwt.java +++ b/test/dorkbox/TestTraySwt.java @@ -118,12 +118,14 @@ class TestTraySwt { entry.setCallback(callbackGray); entry.setImage(BLACK_MAIL); entry.setText("Delete Mail"); + entry.setTooltip(null); // remove the tooltip // systemTray.remove(menuEntry); } }); greenEntry.setImage(GREEN_MAIL); // case does not matter greenEntry.setShortcut('G'); + greenEntry.setTooltip("This means you have green mail!"); mainMenu.add(greenEntry);