Added Menu Entry tooltip support. Emits warning on first load of

tooltips (as they are not supported by all OS configurations). This
warning can be suppressed. Misc fix for loading swing with GTK.
This commit is contained in:
nathan 2017-07-24 14:32:58 +02:00
parent 365645ecd3
commit 65e7c669b5
17 changed files with 238 additions and 145 deletions

View File

@ -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.
* <p>
* NOTE: Maximum length is 64 characters long, and it is not supported on all Operating Systems and Desktop Environments.
* <p>
* 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() {

View File

@ -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) {

View File

@ -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.
* <p>
* The maximum length is 64 characters long, and it is not supported on all Operating Systems and Desktop
* Environments.
* <p>
* 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.
* <p>

View File

@ -31,4 +31,6 @@ interface MenuItemPeer extends EntryPeer {
void setCallback(MenuItem menuItem);
void setShortcut(MenuItem menuItem);
void setTooltip(MenuItem menuItem);
}

View File

@ -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() {

View File

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

View File

@ -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() {

View File

@ -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
*

View File

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

View File

@ -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() {

View File

@ -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() {

View File

@ -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
*/

View File

@ -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() {

View File

@ -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() {

View File

@ -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);

View File

@ -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);

View File

@ -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);