forked from dorkbox/SystemTray
WIP: native + swing menus
This commit is contained in:
parent
7e3fba9157
commit
7a2909abca
@ -168,6 +168,7 @@ interface Menu extends Entry {
|
||||
*/
|
||||
Menu addMenu(String menuText, InputStream imageStream);
|
||||
|
||||
|
||||
/**
|
||||
* Adds a swing widget as a menu entry.
|
||||
*
|
||||
|
@ -32,12 +32,18 @@ import org.slf4j.LoggerFactory;
|
||||
import dorkbox.systemTray.linux.GnomeShellExtension;
|
||||
import dorkbox.systemTray.linux.jna.AppIndicator;
|
||||
import dorkbox.systemTray.linux.jna.Gtk;
|
||||
import dorkbox.systemTray.swing._AppIndicatorTray;
|
||||
import dorkbox.systemTray.swing._GtkStatusIconTray;
|
||||
import dorkbox.systemTray.swing._SwingTray;
|
||||
import dorkbox.systemTray.nativeUI.NativeUI;
|
||||
import dorkbox.systemTray.nativeUI._AppIndicatorNativeTray;
|
||||
import dorkbox.systemTray.nativeUI._AwtTray;
|
||||
import dorkbox.systemTray.nativeUI._GtkStatusIconNativeTray;
|
||||
import dorkbox.systemTray.swingUI.SwingUI;
|
||||
import dorkbox.systemTray.swingUI._AppIndicatorTray;
|
||||
import dorkbox.systemTray.swingUI._GtkStatusIconTray;
|
||||
import dorkbox.systemTray.swingUI._SwingTray;
|
||||
import dorkbox.systemTray.util.ImageUtils;
|
||||
import dorkbox.systemTray.util.JavaFX;
|
||||
import dorkbox.systemTray.util.Swt;
|
||||
import dorkbox.systemTray.util.WindowsSystemTraySwing;
|
||||
import dorkbox.systemTray.util.SystemTrayFixes;
|
||||
import dorkbox.util.CacheUtil;
|
||||
import dorkbox.util.IO;
|
||||
import dorkbox.util.OS;
|
||||
@ -54,10 +60,12 @@ public
|
||||
class SystemTray implements Menu {
|
||||
public static final Logger logger = LoggerFactory.getLogger(SystemTray.class);
|
||||
|
||||
public static final int TYPE_AUTO_DETECT = 0;
|
||||
public static final int TYPE_GTK_STATUSICON = 1;
|
||||
public static final int TYPE_APP_INDICATOR = 2;
|
||||
public static final int TYPE_SWING = 3;
|
||||
public enum TrayType {
|
||||
AutoDetect,
|
||||
GtkStatusIcon,
|
||||
AppIndicator,
|
||||
Swing
|
||||
}
|
||||
|
||||
@Property
|
||||
/** Enables auto-detection for the system tray. This should be mostly successful.
|
||||
@ -97,11 +105,11 @@ class SystemTray implements Menu {
|
||||
|
||||
@Property
|
||||
/**
|
||||
* Forces the system tray detection to be Automatic (0), GtkStatusIcon (1), AppIndicator (2), or Swing (3).
|
||||
* Forces the system tray detection to be AutoDetect, GtkStatusIcon, AppIndicator, or Swing.
|
||||
* <p>
|
||||
* This is an advanced feature, and it is recommended to leave at 0.
|
||||
* This is an advanced feature, and it is recommended to leave at AutoDetect.
|
||||
*/
|
||||
public static int FORCE_TRAY_TYPE = 0;
|
||||
public static TrayType FORCE_TRAY_TYPE = TrayType.Swing;
|
||||
|
||||
@Property
|
||||
/**
|
||||
@ -124,6 +132,8 @@ class SystemTray implements Menu {
|
||||
|
||||
public final static boolean isJavaFxLoaded;
|
||||
public final static boolean isSwtLoaded;
|
||||
private static boolean forceNativeMenus = false;
|
||||
|
||||
|
||||
static {
|
||||
boolean isJavaFxLoaded_ = false;
|
||||
@ -151,7 +161,49 @@ class SystemTray implements Menu {
|
||||
isSwtLoaded = isSwtLoaded_;
|
||||
}
|
||||
|
||||
private static
|
||||
Class<? extends Menu> selectType(final TrayType trayType) throws Exception {
|
||||
if (trayType == TrayType.GtkStatusIcon) {
|
||||
if (forceNativeMenus) {
|
||||
return _GtkStatusIconNativeTray.class;
|
||||
} else {
|
||||
return _GtkStatusIconTray.class;
|
||||
}
|
||||
} else if (trayType == TrayType.AppIndicator) {
|
||||
if (forceNativeMenus) {
|
||||
return _AppIndicatorNativeTray.class;
|
||||
}
|
||||
else {
|
||||
return _AppIndicatorTray.class;
|
||||
}
|
||||
}
|
||||
else if (trayType == TrayType.Swing) {
|
||||
if (forceNativeMenus && !OS.isWindows()) {
|
||||
// AWT on windows looks like crap
|
||||
return _AwtTray.class;
|
||||
}
|
||||
else {
|
||||
return _SwingTray.class;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static
|
||||
Class<? extends Menu> selectTypeQuietly(final TrayType trayType) {
|
||||
try {
|
||||
return selectType(trayType);
|
||||
} catch (Throwable t) {
|
||||
if (DEBUG) {
|
||||
logger.error("Cannot initialize {}", trayType.name(), t);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
private static void init() {
|
||||
if (systemTray != null) {
|
||||
return;
|
||||
@ -173,15 +225,15 @@ class SystemTray implements Menu {
|
||||
} else {
|
||||
// windows and mac ONLY support the Swing SystemTray.
|
||||
// Linux CAN support Swing SystemTray, but it looks like crap (so we wrote our own GtkStatusIcon/AppIndicator)
|
||||
if (OS.isWindows() && FORCE_TRAY_TYPE != TYPE_SWING) {
|
||||
if (OS.isWindows() && FORCE_TRAY_TYPE != TrayType.Swing) {
|
||||
throw new RuntimeException("Windows is incompatible with the specified option for FORCE_TRAY_TYPE: " + FORCE_TRAY_TYPE);
|
||||
} else if (OS.isMacOsX() && FORCE_TRAY_TYPE != TYPE_SWING) {
|
||||
} else if (OS.isMacOsX() && FORCE_TRAY_TYPE != TrayType.Swing) {
|
||||
throw new RuntimeException("MacOSx is incompatible with the specified option for FORCE_TRAY_TYPE: " + FORCE_TRAY_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
// kablooie if SWT is not configured in a way that works with us.
|
||||
if (FORCE_TRAY_TYPE != TYPE_SWING && OS.isLinux()) {
|
||||
if (FORCE_TRAY_TYPE != TrayType.Swing && OS.isLinux()) {
|
||||
if (isSwtLoaded) {
|
||||
// Necessary for us to work with SWT based on version info. We can try to set us to be compatible with whatever it is set to
|
||||
// System.setProperty("SWT_GTK3", "0");
|
||||
@ -240,17 +292,11 @@ class SystemTray implements Menu {
|
||||
}
|
||||
}
|
||||
|
||||
if (FORCE_TRAY_TYPE < 0 || FORCE_TRAY_TYPE > 3) {
|
||||
throw new RuntimeException("Invalid option for FORCE_TRAY_TYPE: " + FORCE_TRAY_TYPE);
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
switch (FORCE_TRAY_TYPE) {
|
||||
case 1: logger.debug("Forced tray type: GtkStatusIcon"); break;
|
||||
case 2: logger.debug("Forced tray type: AppIndicator"); break;
|
||||
case 3: logger.debug("Forced tray type: Swing"); break;
|
||||
|
||||
default: logger.debug("Auto-detecting tray type"); break;
|
||||
if (FORCE_TRAY_TYPE == TrayType.AutoDetect) {
|
||||
logger.debug("Auto-detecting tray type");
|
||||
} else {
|
||||
logger.debug("Forced tray type: {}", FORCE_TRAY_TYPE.name());
|
||||
}
|
||||
logger.debug("FORCE_GTK2: {}", FORCE_GTK2);
|
||||
}
|
||||
@ -259,7 +305,7 @@ class SystemTray implements Menu {
|
||||
// mouseover or something, but I don't know how to do that. It seems that tooltips for app-indicators are a custom job, as
|
||||
// all examined ones sometimes have it (and it's more than just text), or they don't have it at all.
|
||||
|
||||
if (FORCE_TRAY_TYPE != TYPE_SWING && OS.isLinux()) {
|
||||
if (FORCE_TRAY_TYPE != TrayType.Swing && OS.isLinux()) {
|
||||
// see: https://askubuntu.com/questions/72549/how-to-determine-which-window-manager-is-running
|
||||
|
||||
// For funsies, SyncThing did a LOT of work on compatibility (unfortunate for us) in python.
|
||||
@ -275,26 +321,12 @@ class SystemTray implements Menu {
|
||||
}
|
||||
}
|
||||
|
||||
if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TYPE_GTK_STATUSICON) {
|
||||
try {
|
||||
trayType = _GtkStatusIconTray.class;
|
||||
} catch (Throwable e1) {
|
||||
if (DEBUG) {
|
||||
logger.error("Cannot initialize _GtkStatusIconTray", e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TYPE_APP_INDICATOR) {
|
||||
try {
|
||||
trayType = _AppIndicatorTray.class;
|
||||
} catch (Throwable e1) {
|
||||
if (DEBUG) {
|
||||
logger.error("Cannot initialize _AppIndicatorTray", e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
// don't check for SWING type at this spot, it is done elsewhere.
|
||||
|
||||
// this can never be swing
|
||||
// don't check for SWING type at this spot, it is done elsewhere.
|
||||
if (SystemTray.FORCE_TRAY_TYPE != TrayType.AutoDetect) {
|
||||
trayType = selectTypeQuietly(SystemTray.FORCE_TRAY_TYPE);
|
||||
}
|
||||
|
||||
|
||||
// quick check, because we know that unity uses app-indicator. Maybe REALLY old versions do not. We support 14.04 LTE at least
|
||||
@ -341,13 +373,7 @@ class SystemTray implements Menu {
|
||||
|
||||
if (trayType == null) {
|
||||
if ("unity".equalsIgnoreCase(XDG)) {
|
||||
try {
|
||||
trayType = _AppIndicatorTray.class;
|
||||
} catch (Throwable e) {
|
||||
if (DEBUG) {
|
||||
logger.error("Cannot initialize _AppIndicatorTray", e);
|
||||
}
|
||||
}
|
||||
trayType = selectTypeQuietly(TrayType.AppIndicator);
|
||||
}
|
||||
else if ("xfce".equalsIgnoreCase(XDG)) {
|
||||
// NOTE: XFCE used to use appindicator3, which DOES NOT support images in the menu. This change was reverted.
|
||||
@ -355,32 +381,14 @@ class SystemTray implements Menu {
|
||||
// see: https://git.gnome.org/browse/gtk+/commit/?id=627a03683f5f41efbfc86cc0f10e1b7c11e9bb25
|
||||
|
||||
// so far, it is OK to use GtkStatusIcon on XFCE <-> XFCE4 inclusive
|
||||
try {
|
||||
trayType = _GtkStatusIconTray.class;
|
||||
} catch (Throwable e1) {
|
||||
if (DEBUG) {
|
||||
logger.error("Cannot initialize _GtkStatusIconTray", e1);
|
||||
}
|
||||
}
|
||||
trayType = selectTypeQuietly(TrayType.GtkStatusIcon);
|
||||
}
|
||||
else if ("lxde".equalsIgnoreCase(XDG)) {
|
||||
try {
|
||||
trayType = _GtkStatusIconTray.class;
|
||||
} catch (Throwable e) {
|
||||
if (DEBUG) {
|
||||
logger.error("Cannot initialize _GtkStatusIconTray", e);
|
||||
}
|
||||
}
|
||||
trayType = selectTypeQuietly(TrayType.GtkStatusIcon);
|
||||
}
|
||||
else if ("kde".equalsIgnoreCase(XDG)) {
|
||||
// kde (at least, plasma 5.5.6) requires appindicator
|
||||
try {
|
||||
trayType = _AppIndicatorTray.class;
|
||||
} catch (Throwable e) {
|
||||
if (DEBUG) {
|
||||
logger.error("Cannot initialize _AppIndicatorTray", e);
|
||||
}
|
||||
}
|
||||
trayType = selectTypeQuietly(TrayType.AppIndicator);
|
||||
}
|
||||
else if ("gnome".equalsIgnoreCase(XDG)) {
|
||||
// check other DE
|
||||
@ -391,31 +399,13 @@ class SystemTray implements Menu {
|
||||
}
|
||||
|
||||
if ("cinnamon".equalsIgnoreCase(GDM)) {
|
||||
try {
|
||||
trayType = _GtkStatusIconTray.class;
|
||||
} catch (Throwable e) {
|
||||
if (DEBUG) {
|
||||
logger.error("Cannot initialize _GtkStatusIconTray", e);
|
||||
}
|
||||
}
|
||||
trayType = selectTypeQuietly(TrayType.GtkStatusIcon);
|
||||
}
|
||||
else if ("gnome-classic".equalsIgnoreCase(GDM)) {
|
||||
try {
|
||||
trayType = _GtkStatusIconTray.class;
|
||||
} catch (Throwable e) {
|
||||
if (DEBUG) {
|
||||
logger.error("Cannot initialize _GtkStatusIconTray", e);
|
||||
}
|
||||
}
|
||||
trayType = selectTypeQuietly(TrayType.GtkStatusIcon);
|
||||
}
|
||||
else if ("gnome-fallback".equalsIgnoreCase(GDM)) {
|
||||
try {
|
||||
trayType = _GtkStatusIconTray.class;
|
||||
} catch (Throwable e) {
|
||||
if (DEBUG) {
|
||||
logger.error("Cannot initialize _GtkStatusIconTray", e);
|
||||
}
|
||||
}
|
||||
trayType = selectTypeQuietly(TrayType.GtkStatusIcon);
|
||||
}
|
||||
else if ("ubuntu".equalsIgnoreCase(GDM)) {
|
||||
// have to install the gnome extension AND customize the restart command
|
||||
@ -453,7 +443,7 @@ class SystemTray implements Menu {
|
||||
GnomeShellExtension.install(output);
|
||||
// we might be running gnome-shell, we MIGHT NOT. If we are forced to be app-indicator or swing, don't do this.
|
||||
if (trayType == null) {
|
||||
trayType = _GtkStatusIconTray.class;
|
||||
trayType = selectType(TrayType.GtkStatusIcon);
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
@ -491,8 +481,8 @@ class SystemTray implements Menu {
|
||||
if (readLine != null && readLine.contains("indicator-app")) {
|
||||
// make sure we can also load the library (it might be the wrong version)
|
||||
try {
|
||||
trayType = _AppIndicatorTray.class;
|
||||
} catch (Throwable e) {
|
||||
trayType = selectType(TrayType.AppIndicator);
|
||||
} catch (Exception e) {
|
||||
if (DEBUG) {
|
||||
logger.error("AppIndicator support detected, but unable to load the library. Falling back to GTK", e);
|
||||
} else {
|
||||
@ -516,7 +506,7 @@ class SystemTray implements Menu {
|
||||
|
||||
// fallback...
|
||||
if (trayType == null) {
|
||||
trayType = _GtkStatusIconTray.class;
|
||||
trayType = selectTypeQuietly(TrayType.GtkStatusIcon);
|
||||
logger.error("Unable to load the system tray native library. Please write an issue and include your OS type and " +
|
||||
"configuration");
|
||||
}
|
||||
@ -526,14 +516,19 @@ class SystemTray implements Menu {
|
||||
// this has to happen BEFORE any sort of swing system tray stuff is accessed
|
||||
if (OS.isWindows()) {
|
||||
// windows is funky, and is hardcoded to 16x16. We fix that.
|
||||
WindowsSystemTraySwing.fix();
|
||||
SystemTrayFixes.fixWindows();
|
||||
}
|
||||
else if (OS.isMacOsX()) {
|
||||
// macos doesn't respond to all buttons (but should)
|
||||
SystemTrayFixes.fixMacOS();
|
||||
}
|
||||
|
||||
// this is windows OR mac
|
||||
if (trayType == null && java.awt.SystemTray.isSupported()) {
|
||||
ImageUtils.determineIconSize();
|
||||
|
||||
// this is likely windows OR mac
|
||||
if (trayType == null) {
|
||||
try {
|
||||
java.awt.SystemTray.getSystemTray();
|
||||
trayType = _SwingTray.class;
|
||||
trayType = selectType(TrayType.Swing);
|
||||
} catch (Throwable e) {
|
||||
if (DEBUG) {
|
||||
logger.error("Maybe you should grant the AWTPermission `accessSystemTray` in the SecurityManager.", e);
|
||||
@ -564,7 +559,7 @@ class SystemTray implements Menu {
|
||||
AppIndicator.isVersion3) {
|
||||
|
||||
try {
|
||||
trayType = _GtkStatusIconTray.class;
|
||||
trayType = selectType(TrayType.GtkStatusIcon);
|
||||
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'");
|
||||
} catch (Throwable e) {
|
||||
@ -578,6 +573,17 @@ class SystemTray implements Menu {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// if it's native + linux, have to do GTK instead. Don't need to be on the dispatch thread though.
|
||||
// _AwtTray must be constructed on the EDT...
|
||||
if (OS.isLinux() && NativeUI.class.isAssignableFrom(trayType) && trayType == _AwtTray.class) {
|
||||
try {
|
||||
reference.set((Menu) trayType.getConstructors()[0].newInstance(systemTray));
|
||||
logger.info("Successfully Loaded: {}", trayType.getSimpleName());
|
||||
} catch (Exception e) {
|
||||
logger.error("Unable to create tray type: '" + trayType.getSimpleName() + "'", e);
|
||||
}
|
||||
} else {
|
||||
// have to construct swing stuff inside the swing EDT
|
||||
// this is the safest way to do this.
|
||||
final Class<? extends Menu> finalTrayType = trayType;
|
||||
@ -593,12 +599,26 @@ class SystemTray implements Menu {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Unable to create tray type: '" + trayType.getSimpleName() + "'", e);
|
||||
}
|
||||
|
||||
systemTrayMenu = reference.get();
|
||||
|
||||
// verify that we have what we are expecting.
|
||||
if (OS.isWindows() && systemTrayMenu instanceof SwingUI) {
|
||||
// this configuration is OK.
|
||||
}
|
||||
else if (forceNativeMenus && systemTrayMenu instanceof NativeUI) {
|
||||
// this configuration is OK.
|
||||
} else if (!forceNativeMenus && systemTrayMenu instanceof SwingUI) {
|
||||
// this configuration is OK.
|
||||
} else {
|
||||
logger.error("Unable to correctly initialize the System Tray. Please write an issue and include your OS type and " +
|
||||
"configuration");
|
||||
}
|
||||
|
||||
|
||||
// These install a shutdown hook in JavaFX/SWT, so that when the main window is closed -- the system tray is ALSO closed.
|
||||
if (ENABLE_SHUTDOWN_HOOK) {
|
||||
@ -642,18 +662,41 @@ class SystemTray implements Menu {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a SystemTray instance that uses a custom Swing menus, which is more advanced than the native menus. The drawback is that
|
||||
* this menu is not native, and so loses the specific Look and Feel of that platform.
|
||||
* <p>
|
||||
* This always returns the same instance per JVM (it's a singleton), and on some platforms the system tray may not be
|
||||
* supported, in which case this will return NULL.
|
||||
*
|
||||
* <p>If this is using the Swing SystemTray and a SecurityManager is installed, the AWTPermission {@code accessSystemTray} must
|
||||
* <p>
|
||||
* If this is using the Swing SystemTray and a SecurityManager is installed, the AWTPermission {@code accessSystemTray} must
|
||||
* be granted in order to get the {@code SystemTray} instance. Otherwise this will return null.
|
||||
*/
|
||||
public static
|
||||
SystemTray get() {
|
||||
forceNativeMenus = true; // TODO set to false for final build
|
||||
init();
|
||||
return systemTray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables native menus on Linux/OSX instead of the custom swing menu. Windows will always use a custom Swing menu.
|
||||
* <p>
|
||||
* This always returns the same instance per JVM (it's a singleton), and on some platforms the system tray may not be
|
||||
* supported, in which case this will return NULL.
|
||||
* <p>
|
||||
* If this is using the Swing SystemTray and a SecurityManager is installed, the AWTPermission {@code accessSystemTray} must
|
||||
* be granted in order to get the {@code SystemTray} instance. Otherwise this will return null.
|
||||
*/
|
||||
public static
|
||||
SystemTray getNative() {
|
||||
forceNativeMenus = true;
|
||||
init();
|
||||
return systemTray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts-down the SystemTray, by removing the menus + tray icon.
|
||||
*/
|
||||
public
|
||||
void shutdown() {
|
||||
final Menu menu = systemTrayMenu;
|
||||
@ -661,10 +704,19 @@ class SystemTray implements Menu {
|
||||
if (menu instanceof _AppIndicatorTray) {
|
||||
((_AppIndicatorTray) menu).shutdown();
|
||||
}
|
||||
else if (menu instanceof _AppIndicatorNativeTray) {
|
||||
((_AppIndicatorNativeTray) menu).shutdown();
|
||||
}
|
||||
else if (menu instanceof _GtkStatusIconTray) {
|
||||
((_GtkStatusIconTray) menu).shutdown();
|
||||
} else {
|
||||
// swing
|
||||
}
|
||||
else if (menu instanceof _GtkStatusIconNativeTray) {
|
||||
((_GtkStatusIconNativeTray) menu).shutdown();
|
||||
}
|
||||
else if (menu instanceof _AwtTray) {
|
||||
((_AwtTray) menu).shutdown();
|
||||
}
|
||||
else {
|
||||
((_SwingTray) menu).shutdown();
|
||||
}
|
||||
}
|
||||
@ -675,13 +727,23 @@ class SystemTray implements Menu {
|
||||
public
|
||||
String getStatus() {
|
||||
final Menu menu = systemTrayMenu;
|
||||
|
||||
if (menu instanceof _AppIndicatorTray) {
|
||||
return ((_AppIndicatorTray) menu).getStatus();
|
||||
}
|
||||
else if (menu instanceof _AppIndicatorNativeTray) {
|
||||
return ((_AppIndicatorNativeTray) menu).getStatus();
|
||||
}
|
||||
else if (menu instanceof _GtkStatusIconTray) {
|
||||
return ((_GtkStatusIconTray) menu).getStatus();
|
||||
} else {
|
||||
// swing
|
||||
}
|
||||
else if (menu instanceof _GtkStatusIconNativeTray) {
|
||||
return ((_GtkStatusIconNativeTray) menu).getStatus();
|
||||
}
|
||||
else if (menu instanceof _AwtTray) {
|
||||
return ((_AwtTray) menu).getStatus();
|
||||
}
|
||||
else {
|
||||
return ((_SwingTray) menu).getStatus();
|
||||
}
|
||||
}
|
||||
@ -698,10 +760,19 @@ class SystemTray implements Menu {
|
||||
if (menu instanceof _AppIndicatorTray) {
|
||||
((_AppIndicatorTray) menu).setStatus(statusText);
|
||||
}
|
||||
else if (menu instanceof _AppIndicatorNativeTray) {
|
||||
((_AppIndicatorNativeTray) menu).setStatus(statusText);
|
||||
}
|
||||
else if (menu instanceof _GtkStatusIconTray) {
|
||||
((_GtkStatusIconTray) menu).setStatus(statusText);
|
||||
} else {
|
||||
// swing
|
||||
}
|
||||
else if (menu instanceof _GtkStatusIconNativeTray) {
|
||||
((_GtkStatusIconNativeTray) menu).setStatus(statusText);
|
||||
}
|
||||
else if (menu instanceof _AwtTray) {
|
||||
((_AwtTray) menu).setStatus(statusText);
|
||||
}
|
||||
else {
|
||||
((_SwingTray) menu).setStatus(statusText);
|
||||
}
|
||||
}
|
||||
@ -1063,6 +1134,7 @@ class SystemTray implements Menu {
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This removes a menu entry from the dropdown menu.
|
||||
*
|
||||
|
@ -50,7 +50,7 @@ class AppIndicator {
|
||||
// ALSO WHAT VERSION OF GTK to use? appindiactor1 -> GTk2, appindicator3 -> GTK3.
|
||||
// appindicator3 doesn't support menu icons via GTK2!!
|
||||
|
||||
if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TYPE_GTK_STATUSICON) {
|
||||
if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TrayType.GtkStatusIcon) {
|
||||
// if we force GTK type system tray, don't attempt to load AppIndicator libs
|
||||
if (SystemTray.DEBUG) {
|
||||
logger.debug("Forcing GTK tray, not using appindicator");
|
||||
|
29
src/dorkbox/systemTray/linux/jna/GCallback.java
Normal file
29
src/dorkbox/systemTray/linux/jna/GCallback.java
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.linux.jna;
|
||||
|
||||
import com.sun.jna.Callback;
|
||||
import com.sun.jna.Pointer;
|
||||
import dorkbox.util.Keep;
|
||||
|
||||
@Keep
|
||||
public
|
||||
interface GCallback extends Callback {
|
||||
/**
|
||||
* @return Gtk.TRUE if we handled this event
|
||||
*/
|
||||
int callback(Pointer instance, Pointer data);
|
||||
}
|
@ -37,5 +37,8 @@ class Gobject {
|
||||
|
||||
public static native void g_object_unref(Pointer object);
|
||||
|
||||
public static native void g_object_force_floating(Pointer object);
|
||||
public static native void g_object_ref_sink(Pointer object);
|
||||
|
||||
public static native NativeLong g_signal_connect_object(Pointer instance, String detailed_signal, Callback c_handler, Pointer object, int connect_flags);
|
||||
}
|
||||
|
@ -24,6 +24,9 @@ import java.util.concurrent.TimeUnit;
|
||||
import com.sun.jna.Function;
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
import dorkbox.systemTray.Action;
|
||||
import dorkbox.systemTray.Entry;
|
||||
import dorkbox.systemTray.Menu;
|
||||
import dorkbox.systemTray.SystemTray;
|
||||
import dorkbox.systemTray.util.JavaFX;
|
||||
import dorkbox.systemTray.util.Swt;
|
||||
@ -71,7 +74,7 @@ class Gtk {
|
||||
String gtk3LibName = "libgtk-3.so.0";
|
||||
|
||||
// we can force the system to use the swing indicator, which WORKS, but doesn't support transparency in the icon.
|
||||
if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TYPE_SWING) {
|
||||
if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TrayType.Swing) {
|
||||
isLoaded = true;
|
||||
}
|
||||
|
||||
@ -247,7 +250,7 @@ class Gtk {
|
||||
}
|
||||
}
|
||||
} else if (SystemTray.isSwtLoaded) {
|
||||
if (SystemTray.FORCE_TRAY_TYPE != SystemTray.TYPE_GTK_STATUSICON) {
|
||||
if (SystemTray.FORCE_TRAY_TYPE != SystemTray.TrayType.GtkStatusIcon) {
|
||||
// GTK system tray has threading issues if we block here (because it is likely in the event thread)
|
||||
// AppIndicator version doesn't have this problem
|
||||
|
||||
@ -375,6 +378,23 @@ class Gtk {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* required to properly setup the dispatch flag
|
||||
* @param callback will never be null.
|
||||
*/
|
||||
public static
|
||||
void proxyClick(final Menu parent, final Entry menuEntry, final Action callback) {
|
||||
Gtk.isDispatch = true;
|
||||
|
||||
try {
|
||||
callback.onClick(parent.getSystemTray(), parent, menuEntry);
|
||||
} catch (Throwable throwable) {
|
||||
SystemTray.logger.error("Error calling menu entry {} click event.", menuEntry.getText(), throwable);
|
||||
}
|
||||
|
||||
Gtk.isDispatch = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This would NORMALLY have a 2nd argument that is a String[] -- however JNA direct-mapping DOES NOT support this. We are lucky
|
||||
* enough that we just pass 'null' as the second argument, therefore, we don't have to define that parameter here.
|
||||
@ -400,10 +420,22 @@ class Gtk {
|
||||
|
||||
|
||||
public static native Pointer gtk_menu_new();
|
||||
public static native Pointer gtk_menu_item_set_submenu(Pointer menuEntry, Pointer menu);
|
||||
|
||||
|
||||
|
||||
public static native Pointer gtk_separator_menu_item_new();
|
||||
|
||||
// to create a menu entry WITH an icon.
|
||||
public static native Pointer gtk_image_new_from_file(String iconPath);
|
||||
|
||||
// uses '_' to define which key is the mnemonic
|
||||
public static native Pointer gtk_image_menu_item_new_with_mnemonic(String label);
|
||||
|
||||
public static native void gtk_image_menu_item_set_image(Pointer image_menu_item, Pointer image);
|
||||
|
||||
public static native void gtk_image_menu_item_set_always_show_image(Pointer menu_item, int forceShow);
|
||||
|
||||
public static native Pointer gtk_status_icon_new();
|
||||
|
||||
public static native void gtk_status_icon_set_from_file(Pointer widget, String label);
|
||||
@ -417,8 +449,18 @@ class Gtk {
|
||||
|
||||
public static native void gtk_status_icon_set_name(Pointer widget, String name);
|
||||
|
||||
public static native void gtk_menu_popup(Pointer menu, Pointer widget, Pointer bla, Function func, Pointer data, int button, int time);
|
||||
|
||||
public static native void gtk_menu_item_set_label(Pointer menu_item, String label);
|
||||
|
||||
public static native void gtk_menu_shell_append(Pointer menu_shell, Pointer child);
|
||||
|
||||
public static native void gtk_menu_shell_deactivate(Pointer menu_shell, Pointer child);
|
||||
|
||||
public static native void gtk_widget_set_sensitive(Pointer widget, int sensitive);
|
||||
|
||||
public static native void gtk_container_remove(Pointer menu, Pointer subItem);
|
||||
|
||||
public static native void gtk_widget_show_all(Pointer widget);
|
||||
|
||||
public static native void gtk_widget_destroy(Pointer widget);
|
||||
|
209
src/dorkbox/systemTray/nativeUI/AwtEntry.java
Normal file
209
src/dorkbox/systemTray/nativeUI/AwtEntry.java
Normal file
@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Copyright 2014 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.nativeUI;
|
||||
|
||||
import java.awt.MenuItem;
|
||||
import java.awt.MenuShortcut;
|
||||
import java.awt.PopupMenu;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
|
||||
import dorkbox.systemTray.Entry;
|
||||
import dorkbox.systemTray.Menu;
|
||||
import dorkbox.systemTray.swingUI.SwingUI;
|
||||
import dorkbox.systemTray.util.ImageUtils;
|
||||
import dorkbox.systemTray.util.MenuBase;
|
||||
import dorkbox.systemTray.util.SystemTrayFixes;
|
||||
|
||||
abstract
|
||||
class AwtEntry implements Entry, SwingUI {
|
||||
private final int id = MenuBase.MENU_ID_COUNTER.getAndIncrement();
|
||||
|
||||
private final AwtMenu parent;
|
||||
final MenuItem _native;
|
||||
|
||||
// this have to be volatile, because they can be changed from any thread
|
||||
private volatile String text;
|
||||
|
||||
// this is ALWAYS called on the EDT.
|
||||
AwtEntry(final AwtMenu parent, final MenuItem menuItem) {
|
||||
this.parent = parent;
|
||||
this._native = menuItem;
|
||||
|
||||
parent._native.add(menuItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
Menu getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* must always be called in the EDT thread
|
||||
*/
|
||||
abstract
|
||||
void renderText(final String text);
|
||||
|
||||
/**
|
||||
* Not always called on the EDT thread
|
||||
*/
|
||||
abstract
|
||||
void setImage_(final File imageFile);
|
||||
|
||||
/**
|
||||
* Enables, or disables the sub-menu entry.
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
void setEnabled(final boolean enabled) {
|
||||
_native.setEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setShortcut(final char key) {
|
||||
if (!(_native instanceof PopupMenu)) {
|
||||
// yikes...
|
||||
final int vKey = SystemTrayFixes.getVirtualKey(key);
|
||||
|
||||
parent.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
_native.setShortcut(new MenuShortcut(vKey));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setText(final String newText) {
|
||||
this.text = newText;
|
||||
|
||||
parent.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
renderText(newText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setImage(final File imageFile) {
|
||||
if (imageFile == null) {
|
||||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageFile));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setImage(final String imagePath) {
|
||||
if (imagePath == null) {
|
||||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imagePath));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setImage(final URL imageUrl) {
|
||||
if (imageUrl == null) {
|
||||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageUrl));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setImage(final String cacheName, final InputStream imageStream) {
|
||||
if (imageStream == null) {
|
||||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, cacheName, imageStream));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setImage(final InputStream imageStream) {
|
||||
if (imageStream == null) {
|
||||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageStream));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void remove() {
|
||||
parent.dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
removePrivate();
|
||||
parent._native.remove(_native);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// called when this item is removed. Necessary to cleanup/remove itself
|
||||
abstract
|
||||
void removePrivate();
|
||||
|
||||
@Override
|
||||
public final
|
||||
int hashCode() {
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final
|
||||
boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AwtEntry other = (AwtEntry) obj;
|
||||
return this.id == other.id;
|
||||
}
|
||||
}
|
90
src/dorkbox/systemTray/nativeUI/AwtEntryItem.java
Normal file
90
src/dorkbox/systemTray/nativeUI/AwtEntryItem.java
Normal file
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2014 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.nativeUI;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.File;
|
||||
|
||||
import dorkbox.systemTray.Action;
|
||||
|
||||
class AwtEntryItem extends AwtEntry {
|
||||
|
||||
private final ActionListener swingCallback;
|
||||
|
||||
private volatile Action callback;
|
||||
|
||||
// this is ALWAYS called on the EDT.
|
||||
AwtEntryItem(final AwtMenu parent, final Action callback) {
|
||||
super(parent, new java.awt.MenuItem());
|
||||
this.callback = callback;
|
||||
|
||||
|
||||
if (callback != null) {
|
||||
_native.setEnabled(true);
|
||||
swingCallback = new ActionListener() {
|
||||
@Override
|
||||
public
|
||||
void actionPerformed(ActionEvent e) {
|
||||
// we want it to run on the EDT
|
||||
handle();
|
||||
}
|
||||
};
|
||||
|
||||
_native.addActionListener(swingCallback);
|
||||
} else {
|
||||
_native.setEnabled(false);
|
||||
swingCallback = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setCallback(final Action callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
private
|
||||
void handle() {
|
||||
if (callback != null) {
|
||||
callback.onClick(getParent().getSystemTray(), getParent(), this);
|
||||
}
|
||||
}
|
||||
|
||||
// always called in the EDT
|
||||
@Override
|
||||
void renderText(final String text) {
|
||||
_native.setLabel(text);
|
||||
}
|
||||
|
||||
|
||||
// not supported!
|
||||
@Override
|
||||
public
|
||||
boolean hasImage() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// not supported!
|
||||
@Override
|
||||
void setImage_(final File imageFile) {
|
||||
}
|
||||
|
||||
@Override
|
||||
void removePrivate() {
|
||||
_native.removeActionListener(swingCallback);
|
||||
}
|
||||
}
|
58
src/dorkbox/systemTray/nativeUI/AwtEntrySeparator.java
Normal file
58
src/dorkbox/systemTray/nativeUI/AwtEntrySeparator.java
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2014 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.nativeUI;
|
||||
|
||||
import java.awt.MenuItem;
|
||||
import java.io.File;
|
||||
|
||||
import dorkbox.systemTray.Action;
|
||||
|
||||
class AwtEntrySeparator extends AwtEntry implements dorkbox.systemTray.Separator {
|
||||
|
||||
// this is ALWAYS called on the EDT.
|
||||
AwtEntrySeparator(final AwtMenu parent) {
|
||||
super(parent, new MenuItem("-"));
|
||||
}
|
||||
|
||||
// called in the EDT thread
|
||||
@Override
|
||||
void renderText(final String text) {
|
||||
}
|
||||
|
||||
@Override
|
||||
void setImage_(final File imageFile) {
|
||||
}
|
||||
|
||||
@Override
|
||||
void removePrivate() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setShortcut(final char key) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean hasImage() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setCallback(final Action callback) {
|
||||
}
|
||||
}
|
75
src/dorkbox/systemTray/nativeUI/AwtEntryStatus.java
Normal file
75
src/dorkbox/systemTray/nativeUI/AwtEntryStatus.java
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2014 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.nativeUI;
|
||||
|
||||
import static java.awt.Font.DIALOG;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.awt.MenuItem;
|
||||
import java.io.File;
|
||||
|
||||
import dorkbox.systemTray.Action;
|
||||
import dorkbox.systemTray.Status;
|
||||
|
||||
class AwtEntryStatus extends AwtEntry implements Status {
|
||||
|
||||
// this is ALWAYS called on the EDT.
|
||||
AwtEntryStatus(final AwtMenu parent, final String label) {
|
||||
super(parent, new MenuItem());
|
||||
setText(label);
|
||||
}
|
||||
|
||||
// called in the EDT thread
|
||||
@Override
|
||||
void renderText(final String text) {
|
||||
Font font = _native.getFont();
|
||||
if (font == null) {
|
||||
font = new Font(DIALOG, Font.BOLD, 12); // the default font used for dialogs.
|
||||
} else {
|
||||
font = font.deriveFont(Font.BOLD);
|
||||
}
|
||||
|
||||
_native.setFont(font);
|
||||
_native.setLabel(text);
|
||||
|
||||
// this makes sure it can't be selected
|
||||
_native.setEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
void setImage_(final File imageFile) {
|
||||
}
|
||||
|
||||
@Override
|
||||
void removePrivate() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setShortcut(final char key) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean hasImage() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setCallback(final Action callback) {
|
||||
}
|
||||
}
|
323
src/dorkbox/systemTray/nativeUI/AwtMenu.java
Normal file
323
src/dorkbox/systemTray/nativeUI/AwtMenu.java
Normal file
@ -0,0 +1,323 @@
|
||||
/*
|
||||
* Copyright 2014 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.nativeUI;
|
||||
|
||||
|
||||
import java.awt.MenuShortcut;
|
||||
import java.awt.PopupMenu;
|
||||
import java.io.File;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import dorkbox.systemTray.Action;
|
||||
import dorkbox.systemTray.Entry;
|
||||
import dorkbox.systemTray.Menu;
|
||||
import dorkbox.systemTray.Status;
|
||||
import dorkbox.systemTray.SystemTray;
|
||||
import dorkbox.systemTray.util.MenuBase;
|
||||
import dorkbox.systemTray.util.SystemTrayFixes;
|
||||
import dorkbox.util.SwingUtil;
|
||||
|
||||
// this is a weird composite class, because it must be a Menu, but ALSO a Entry -- so it has both
|
||||
@SuppressWarnings("ForLoopReplaceableByForEach")
|
||||
class AwtMenu extends MenuBase implements NativeUI {
|
||||
|
||||
// sub-menu = java.awt.Menu
|
||||
// systemtray = java.awt.PopupMenu
|
||||
volatile java.awt.Menu _native;
|
||||
|
||||
// this have to be volatile, because they can be changed from any thread
|
||||
private volatile String text;
|
||||
|
||||
/**
|
||||
* Called in the EDT
|
||||
*
|
||||
* @param systemTray the system tray (which is the object that sits in the system tray)
|
||||
* @param parent the parent of this menu, null if the parent is the system tray
|
||||
* @param _native the native element that represents this menu
|
||||
*/
|
||||
AwtMenu(final SystemTray systemTray, final Menu parent, final java.awt.Menu _native) {
|
||||
super(systemTray, parent);
|
||||
this._native = _native;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final
|
||||
void dispatch(final Runnable runnable) {
|
||||
// this will properly check if we are running on the EDT
|
||||
SwingUtil.invokeLater(runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final
|
||||
void dispatchAndWait(final Runnable runnable) {
|
||||
// this will properly check if we are running on the EDT
|
||||
try {
|
||||
SwingUtil.invokeAndWait(runnable);
|
||||
} catch (Exception e) {
|
||||
SystemTray.logger.error("Error processing event on the dispatch thread.", e);
|
||||
}
|
||||
}
|
||||
|
||||
// always called in the EDT
|
||||
protected final
|
||||
void renderText(final String text) {
|
||||
_native.setLabel(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setText(final String newText) {
|
||||
text = newText;
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
renderText(newText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Will add a new menu entry, or update one if it already exists
|
||||
* NOT ALWAYS CALLED ON EDT
|
||||
*/
|
||||
protected final
|
||||
Entry addEntry_(final String menuText, final File imagePath, final Action callback) {
|
||||
if (menuText == null) {
|
||||
throw new NullPointerException("Menu text cannot be null");
|
||||
}
|
||||
|
||||
final AtomicReference<Entry> value = new AtomicReference<Entry>();
|
||||
|
||||
dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
synchronized (menuEntries) {
|
||||
Entry entry = get(menuText);
|
||||
|
||||
if (entry == null) {
|
||||
// must always be called on the EDT
|
||||
entry = new AwtEntryItem(AwtMenu.this, callback);
|
||||
entry.setText(menuText);
|
||||
entry.setImage(imagePath);
|
||||
|
||||
menuEntries.add(entry);
|
||||
} else if (entry instanceof AwtEntryItem) {
|
||||
entry.setText(menuText);
|
||||
entry.setImage(imagePath);
|
||||
}
|
||||
|
||||
value.set(entry);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return value.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Will add a new sub-menu entry, or update one if it already exists
|
||||
* NOT ALWAYS CALLED ON EDT
|
||||
*/
|
||||
protected final
|
||||
Menu addMenu_(final String menuText, final File imagePath) {
|
||||
if (menuText == null) {
|
||||
throw new NullPointerException("Menu text cannot be null");
|
||||
}
|
||||
|
||||
final AtomicReference<Menu> value = new AtomicReference<Menu>();
|
||||
|
||||
dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
synchronized (menuEntries) {
|
||||
Entry entry = get(menuText);
|
||||
|
||||
if (entry == null) {
|
||||
// must always be called on the EDT
|
||||
entry = new AwtMenu(getSystemTray(), AwtMenu.this, new java.awt.Menu());
|
||||
_native.add(((AwtMenu) entry)._native); // have to add it separately
|
||||
|
||||
entry.setText(menuText);
|
||||
entry.setImage(imagePath);
|
||||
value.set((Menu) entry);
|
||||
|
||||
} else if (entry instanceof AwtMenu) {
|
||||
entry.setText(menuText);
|
||||
entry.setImage(imagePath);
|
||||
}
|
||||
|
||||
menuEntries.add(entry);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return value.get();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// public here so that Swing/Gtk/AppIndicator can override this
|
||||
public
|
||||
void setImage_(final File imageFile) {
|
||||
// not supported!
|
||||
}
|
||||
|
||||
// not supported!
|
||||
@Override
|
||||
public
|
||||
boolean hasImage() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// public here so that Swing/Gtk/AppIndicator can override this
|
||||
@Override
|
||||
public
|
||||
void setEnabled(final boolean enabled) {
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
_native.setEnabled(enabled);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* NOT ALWAYS CALLED ON EDT
|
||||
*/
|
||||
@Override
|
||||
public final
|
||||
void addSeparator() {
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
synchronized (menuEntries) {
|
||||
synchronized (menuEntries) {
|
||||
Entry entry = new AwtEntrySeparator(AwtMenu.this);
|
||||
menuEntries.add(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: buggy. The menu will **sometimes** stop responding to the "enter" key after this. Mnemonics still work however.
|
||||
// public
|
||||
// Entry addWidget(final JComponent widget) {
|
||||
// if (widget == null) {
|
||||
// throw new NullPointerException("Widget cannot be null");
|
||||
// }
|
||||
//
|
||||
// final AtomicReference<Entry> value = new AtomicReference<Entry>();
|
||||
//
|
||||
// dispatchAndWait(new Runnable() {
|
||||
// @Override
|
||||
// public
|
||||
// void run() {
|
||||
// synchronized (menuEntries) {
|
||||
// // must always be called on the EDT
|
||||
// Entry entry = new SwingEntryWidget(SwingMenu.this, widget);
|
||||
// value.set(entry);
|
||||
// menuEntries.add(entry);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// return value.get();
|
||||
// }
|
||||
|
||||
|
||||
// public here so that Swing/Gtk/AppIndicator can access this
|
||||
public final
|
||||
void setStatus(final String statusText) {
|
||||
final AwtMenu _this = this;
|
||||
dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
synchronized (menuEntries) {
|
||||
// status is ALWAYS at 0 index...
|
||||
AwtEntry menuEntry = null;
|
||||
if (!menuEntries.isEmpty()) {
|
||||
menuEntry = (AwtEntry) menuEntries.get(0);
|
||||
}
|
||||
|
||||
if (menuEntry instanceof Status) {
|
||||
// set the text or delete...
|
||||
|
||||
if (statusText == null) {
|
||||
// delete
|
||||
remove(menuEntry);
|
||||
}
|
||||
else {
|
||||
// set text
|
||||
menuEntry.setText(statusText);
|
||||
}
|
||||
|
||||
} else {
|
||||
// create a new one
|
||||
menuEntry = new AwtEntryStatus(_this, statusText);
|
||||
// status is ALWAYS at 0 index...
|
||||
menuEntries.add(0, menuEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setShortcut(final char key) {
|
||||
if (!(_native instanceof PopupMenu)) {
|
||||
// yikes...
|
||||
final int vKey = SystemTrayFixes.getVirtualKey(key);
|
||||
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
_native.setShortcut(new MenuShortcut(vKey));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void remove() {
|
||||
dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
AwtMenu parent = (AwtMenu) getParent();
|
||||
if (parent != null) {
|
||||
parent._native.remove(_native);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
218
src/dorkbox/systemTray/nativeUI/GtkEntry.java
Normal file
218
src/dorkbox/systemTray/nativeUI/GtkEntry.java
Normal file
@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright 2014 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.nativeUI;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
import dorkbox.systemTray.Entry;
|
||||
import dorkbox.systemTray.Menu;
|
||||
import dorkbox.systemTray.linux.jna.Gtk;
|
||||
import dorkbox.systemTray.util.ImageUtils;
|
||||
|
||||
abstract
|
||||
class GtkEntry implements Entry {
|
||||
private final int id = GtkMenu.MENU_ID_COUNTER.getAndIncrement();
|
||||
|
||||
private final GtkMenu parent;
|
||||
final Pointer _native;
|
||||
|
||||
// this have to be volatile, because they can be changed from any thread
|
||||
private volatile String text;
|
||||
|
||||
/**
|
||||
* called from inside dispatch thread. ONLY creates the menu item, but DOES NOT attach it!
|
||||
* this is a FLOATING reference. See: https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#floating-ref
|
||||
*/
|
||||
GtkEntry(final GtkMenu parent, final Pointer menuItem) {
|
||||
this.parent = parent;
|
||||
this._native = menuItem;
|
||||
}
|
||||
|
||||
public
|
||||
Menu getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* the menu entry looks FUNKY when there are a mis-match of entries WITH and WITHOUT images
|
||||
*
|
||||
* always called on the DISPATCH thread
|
||||
*/
|
||||
abstract
|
||||
void setSpacerImage(final boolean everyoneElseHasImages);
|
||||
|
||||
/**
|
||||
* must always be called in the GTK thread
|
||||
*/
|
||||
abstract
|
||||
void renderText(final String text);
|
||||
|
||||
/**
|
||||
* must always be called in the GTK thread
|
||||
*/
|
||||
abstract
|
||||
void setImage_(final File imageFile);
|
||||
|
||||
/**
|
||||
* must always be called in the GTK thread
|
||||
* called when this item is removed. Necessary to cleanup/remove itself
|
||||
*/
|
||||
abstract
|
||||
void removePrivate();
|
||||
|
||||
/**
|
||||
* Enables, or disables the sub-menu entry.
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
void setEnabled(final boolean enabled) {
|
||||
if (enabled) {
|
||||
Gtk.gtk_widget_set_sensitive(_native, Gtk.TRUE);
|
||||
} else {
|
||||
Gtk.gtk_widget_set_sensitive(_native, Gtk.FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setShortcut(final char key) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setText(final String newText) {
|
||||
text = newText;
|
||||
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
renderText(text);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setImage(final File imageFile) {
|
||||
if (imageFile == null) {
|
||||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageFile));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setImage(final String imagePath) {
|
||||
if (imagePath == null) {
|
||||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imagePath));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setImage(final URL imageUrl) {
|
||||
if (imageUrl == null) {
|
||||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageUrl));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setImage(final String cacheName, final InputStream imageStream) {
|
||||
if (imageStream == null) {
|
||||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, cacheName, imageStream));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setImage(final InputStream imageStream) {
|
||||
if (imageStream == null) {
|
||||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageStream));
|
||||
}
|
||||
}
|
||||
|
||||
// a child will always remove itself from the parent.
|
||||
@Override
|
||||
public final
|
||||
void remove() {
|
||||
parent.dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
Gtk.gtk_container_remove(parent._native, _native);
|
||||
Gtk.gtk_menu_shell_deactivate(parent._native, _native);
|
||||
|
||||
removePrivate();
|
||||
|
||||
Gtk.gtk_widget_destroy(_native);
|
||||
|
||||
// have to rebuild the menu now...
|
||||
parent.deleteMenu();
|
||||
parent.createMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
int hashCode() {
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final
|
||||
boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GtkEntry other = (GtkEntry) obj;
|
||||
return this.id == other.id;
|
||||
}
|
||||
}
|
192
src/dorkbox/systemTray/nativeUI/GtkEntryItem.java
Normal file
192
src/dorkbox/systemTray/nativeUI/GtkEntryItem.java
Normal file
@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright 2014 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.nativeUI;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import com.sun.jna.NativeLong;
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
import dorkbox.systemTray.Action;
|
||||
import dorkbox.systemTray.linux.jna.GCallback;
|
||||
import dorkbox.systemTray.linux.jna.Gobject;
|
||||
import dorkbox.systemTray.linux.jna.Gtk;
|
||||
import dorkbox.systemTray.util.ImageUtils;
|
||||
|
||||
class GtkEntryItem extends GtkEntry implements GCallback {
|
||||
private static File transparentIcon = null;
|
||||
|
||||
@SuppressWarnings({"FieldCanBeLocal", "unused"})
|
||||
private final NativeLong nativeLong;
|
||||
|
||||
// these have to be volatile, because they can be changed from any thread
|
||||
private volatile Action callback;
|
||||
private volatile Pointer image;
|
||||
|
||||
// these are necessary BECAUSE GTK menus look funky as hell when there are some menu entries WITH icons and some WITHOUT
|
||||
protected volatile boolean hasLegitIcon = true;
|
||||
|
||||
// The mnemonic will ONLY show-up once a menu entry is selected. IT WILL NOT show up before then!
|
||||
// AppIndicators will only show if you use the keyboard to navigate
|
||||
// GtkStatusIconTray will show on mouse+keyboard movement
|
||||
private volatile char mnemonicKey = 0;
|
||||
|
||||
/**
|
||||
* called from inside dispatch thread. ONLY creates the menu item, but DOES NOT attach it!
|
||||
* this is a FLOATING reference. See: https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#floating-ref
|
||||
*/
|
||||
GtkEntryItem(final GtkMenu parent, final Action callback) {
|
||||
super(parent, Gtk.gtk_image_menu_item_new_with_mnemonic(""));
|
||||
this.callback = callback;
|
||||
|
||||
// cannot be done in a static initializer, because the tray icon size might not yet have been determined
|
||||
if (transparentIcon == null) {
|
||||
transparentIcon = ImageUtils.getTransparentImage(ImageUtils.ENTRY_SIZE);
|
||||
}
|
||||
|
||||
if (callback != null) {
|
||||
Gtk.gtk_widget_set_sensitive(_native, Gtk.TRUE);
|
||||
nativeLong = Gobject.g_signal_connect_object(_native, "activate", this, null, 0);
|
||||
}
|
||||
else {
|
||||
Gtk.gtk_widget_set_sensitive(_native, Gtk.FALSE);
|
||||
nativeLong = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setShortcut(final char key) {
|
||||
this.mnemonicKey = Character.toLowerCase(key);
|
||||
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
renderText(getText());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setCallback(final Action callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
// called by native code
|
||||
@Override
|
||||
public
|
||||
int callback(final Pointer instance, final Pointer data) {
|
||||
final Action cb = this.callback;
|
||||
if (cb != null) {
|
||||
Gtk.proxyClick(getParent(), GtkEntryItem.this, cb);
|
||||
}
|
||||
|
||||
return Gtk.TRUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean hasImage() {
|
||||
return hasLegitIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
* the menu entry looks FUNKY when there are a mis-match of entries WITH and WITHOUT images.
|
||||
* This is primarily only with AppIndicators, although not always.
|
||||
* <p>
|
||||
* called on the DISPATCH thread
|
||||
*/
|
||||
void setSpacerImage(final boolean everyoneElseHasImages) {
|
||||
if (hasLegitIcon) {
|
||||
// we have a legit icon, so there is nothing else we can do.
|
||||
return;
|
||||
}
|
||||
|
||||
if (image != null) {
|
||||
Gtk.gtk_widget_destroy(image);
|
||||
image = null;
|
||||
Gtk.gtk_widget_show_all(_native);
|
||||
}
|
||||
|
||||
if (everyoneElseHasImages) {
|
||||
image = Gtk.gtk_image_new_from_file(transparentIcon.getAbsolutePath());
|
||||
Gtk.gtk_image_menu_item_set_image(_native, image);
|
||||
|
||||
// must always re-set always-show after setting the image
|
||||
Gtk.gtk_image_menu_item_set_always_show_image(_native, Gtk.TRUE);
|
||||
}
|
||||
|
||||
Gtk.gtk_widget_show_all(_native);
|
||||
}
|
||||
|
||||
/**
|
||||
* must always be called in the GTK thread
|
||||
*/
|
||||
void renderText(String text) {
|
||||
if (this.mnemonicKey != 0) {
|
||||
// they are CASE INSENSITIVE!
|
||||
int i = text.toLowerCase()
|
||||
.indexOf(this.mnemonicKey);
|
||||
|
||||
if (i >= 0) {
|
||||
text = text.substring(0, i) + "_" + text.substring(i);
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.gtk_menu_item_set_label(_native, text);
|
||||
Gtk.gtk_widget_show_all(_native);
|
||||
}
|
||||
|
||||
// NOTE: XFCE used to use appindicator3, which DOES NOT support images in the menu. This change was reverted.
|
||||
// see: https://ask.fedoraproject.org/en/question/23116/how-to-fix-missing-icons-in-program-menus-and-context-menus/
|
||||
// see: https://git.gnome.org/browse/gtk+/commit/?id=627a03683f5f41efbfc86cc0f10e1b7c11e9bb25
|
||||
void setImage_(final File imageFile) {
|
||||
hasLegitIcon = imageFile != null;
|
||||
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
if (image != null) {
|
||||
Gtk.gtk_widget_destroy(image);
|
||||
image = null;
|
||||
Gtk.gtk_widget_show_all(_native);
|
||||
}
|
||||
|
||||
if (imageFile != null) {
|
||||
image = Gtk.gtk_image_new_from_file(imageFile.getAbsolutePath());
|
||||
Gtk.gtk_image_menu_item_set_image(_native, image);
|
||||
|
||||
// must always re-set always-show after setting the image
|
||||
Gtk.gtk_image_menu_item_set_always_show_image(_native, Gtk.TRUE);
|
||||
}
|
||||
|
||||
Gtk.gtk_widget_show_all(_native);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void removePrivate() {
|
||||
callback = null;
|
||||
|
||||
if (image != null) {
|
||||
Gtk.gtk_widget_destroy(image);
|
||||
image = null;
|
||||
}
|
||||
}
|
||||
}
|
66
src/dorkbox/systemTray/nativeUI/GtkEntrySeparator.java
Normal file
66
src/dorkbox/systemTray/nativeUI/GtkEntrySeparator.java
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2014 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.nativeUI;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import dorkbox.systemTray.Action;
|
||||
import dorkbox.systemTray.Separator;
|
||||
import dorkbox.systemTray.linux.jna.Gtk;
|
||||
|
||||
class GtkEntrySeparator extends GtkEntry implements Separator {
|
||||
|
||||
/**
|
||||
* called from inside dispatch thread. ONLY creates the menu item, but DOES NOT attach it!
|
||||
* this is a FLOATING reference. See: https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#floating-ref
|
||||
*/
|
||||
GtkEntrySeparator(final GtkMenu parent) {
|
||||
super(parent, Gtk.gtk_separator_menu_item_new());
|
||||
}
|
||||
|
||||
@Override
|
||||
void setSpacerImage(final boolean everyoneElseHasImages) {
|
||||
}
|
||||
|
||||
// called in the GTK thread
|
||||
@Override
|
||||
void renderText(final String text) {
|
||||
}
|
||||
|
||||
@Override
|
||||
void setImage_(final File imageFile) {
|
||||
}
|
||||
|
||||
@Override
|
||||
void removePrivate() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean hasImage() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setCallback(final Action callback) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setEnabled(final boolean enabled) {
|
||||
}
|
||||
}
|
57
src/dorkbox/systemTray/nativeUI/GtkEntryStatus.java
Normal file
57
src/dorkbox/systemTray/nativeUI/GtkEntryStatus.java
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2014 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.nativeUI;
|
||||
|
||||
import dorkbox.systemTray.Action;
|
||||
import dorkbox.systemTray.linux.jna.Gtk;
|
||||
|
||||
// you might wonder WHY this extends MenuEntryItem -- the reason is that an AppIndicator "status" will be offset from everyone else,
|
||||
// where a GtkStatusIconTray + SwingUI will have everything lined up. (with or without icons). This is to normalize how it looks
|
||||
class GtkEntryStatus extends GtkEntryItem {
|
||||
|
||||
/**
|
||||
* called from inside dispatch thread. ONLY creates the menu item, but DOES NOT attach it!
|
||||
* this is a FLOATING reference. See: https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#floating-ref
|
||||
*/
|
||||
GtkEntryStatus(final GtkMenu parent, final String text) {
|
||||
super(parent, null);
|
||||
// need that extra space so it matches windows/mac
|
||||
hasLegitIcon = false;
|
||||
setText(text);
|
||||
}
|
||||
|
||||
// called in the GTK thread
|
||||
@Override
|
||||
void renderText(final String text) {
|
||||
// AppIndicator strips out markup text.
|
||||
// https://mail.gnome.org/archives/commits-list/2016-March/msg05444.html
|
||||
|
||||
Gtk.gtk_menu_item_set_label(_native, text);
|
||||
Gtk.gtk_widget_show_all(_native);
|
||||
|
||||
Gtk.gtk_widget_set_sensitive(_native, Gtk.FALSE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setCallback(final Action callback) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setEnabled(final boolean enabled) {
|
||||
}
|
||||
}
|
500
src/dorkbox/systemTray/nativeUI/GtkMenu.java
Normal file
500
src/dorkbox/systemTray/nativeUI/GtkMenu.java
Normal file
@ -0,0 +1,500 @@
|
||||
/*
|
||||
* Copyright 2014 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.nativeUI;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
import dorkbox.systemTray.Action;
|
||||
import dorkbox.systemTray.Entry;
|
||||
import dorkbox.systemTray.Menu;
|
||||
import dorkbox.systemTray.SystemTray;
|
||||
import dorkbox.systemTray.linux.jna.Gobject;
|
||||
import dorkbox.systemTray.linux.jna.Gtk;
|
||||
import dorkbox.systemTray.util.MenuBase;
|
||||
|
||||
class GtkMenu extends MenuBase implements NativeUI {
|
||||
static int TIMEOUT = 2;
|
||||
|
||||
// menu entry that this menu is attached to. Will be NULL when it's the system tray
|
||||
private final GtkEntryItem menuEntry;
|
||||
|
||||
// must ONLY be created at the end of delete!
|
||||
volatile Pointer _native;
|
||||
|
||||
// have to make sure no other methods can call obliterate, delete, or create menu once it's already started
|
||||
private boolean obliterateInProgress = false;
|
||||
|
||||
// called on dispatch
|
||||
GtkMenu(final SystemTray systemTray, final GtkMenu parent) {
|
||||
super(systemTray, parent);
|
||||
|
||||
if (parent != null) {
|
||||
this.menuEntry = new GtkEntryItem(parent, null);
|
||||
// by default, no callback on a menu entry means it's DISABLED. we have to undo that, because we don't have a callback for menus
|
||||
menuEntry.setEnabled(true);
|
||||
} else {
|
||||
this.menuEntry = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called inside the gdk_threads block
|
||||
*/
|
||||
protected
|
||||
void onMenuAdded(final Pointer menu) {
|
||||
// only needed for AppIndicator
|
||||
}
|
||||
|
||||
/**
|
||||
* Necessary to guarantee all updates occur on the dispatch thread
|
||||
*/
|
||||
protected
|
||||
void dispatch(final Runnable runnable) {
|
||||
Gtk.dispatch(runnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Necessary to guarantee all updates occur on the dispatch thread
|
||||
*/
|
||||
protected
|
||||
void dispatchAndWait(final Runnable runnable) {
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
try {
|
||||
runnable.run();
|
||||
} finally {
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// this is slightly different than how swing does it. We have a timeout here so that we can make sure that updates on the GUI
|
||||
// thread occur in REASONABLE time-frames, and alert the user if not.
|
||||
try {
|
||||
if (!countDownLatch.await(TIMEOUT, TimeUnit.SECONDS)) {
|
||||
if (SystemTray.DEBUG) {
|
||||
SystemTray.logger.error("Event dispatch queue took longer than " + TIMEOUT + " seconds to complete. Please adjust " +
|
||||
"`SystemTray.TIMEOUT` to a value which better suites your environment.");
|
||||
} else {
|
||||
throw new RuntimeException("Event dispatch queue took longer than " + TIMEOUT + " seconds to complete. Please adjust " +
|
||||
"`SystemTray.TIMEOUT` to a value which better suites your environment.");
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
SystemTray.logger.error("Error waiting for dispatch to complete.", new Exception());
|
||||
}
|
||||
}
|
||||
|
||||
public
|
||||
void shutdown() {
|
||||
dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
obliterateMenu();
|
||||
|
||||
Gtk.shutdownGui();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// public here so that Swing/Gtk/AppIndicator can access this
|
||||
public final
|
||||
void setStatus(final String statusText) {
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
// some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator.
|
||||
// To work around this issue, we destroy then recreate the menu every time something is changed.
|
||||
synchronized (menuEntries) {
|
||||
// status is ALWAYS at 0 index...
|
||||
GtkEntry menuEntry = null;
|
||||
if (!menuEntries.isEmpty()) {
|
||||
menuEntry = (GtkEntry) menuEntries.get(0);
|
||||
}
|
||||
|
||||
if (menuEntry instanceof GtkEntryStatus) {
|
||||
// always delete...
|
||||
remove(menuEntry);
|
||||
}
|
||||
|
||||
// some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator.
|
||||
// To work around this issue, we destroy then recreate the menu every time something is changed.
|
||||
deleteMenu();
|
||||
|
||||
if (menuEntry == null) {
|
||||
menuEntry = new GtkEntryStatus(GtkMenu.this, statusText);
|
||||
// status is ALWAYS at 0 index...
|
||||
menuEntries.add(0, menuEntry);
|
||||
}
|
||||
else if (menuEntry instanceof GtkEntryStatus) {
|
||||
// change the text?
|
||||
if (statusText != null) {
|
||||
menuEntry = new GtkEntryStatus(GtkMenu.this, statusText);
|
||||
menuEntries.add(0, menuEntry);
|
||||
}
|
||||
}
|
||||
|
||||
createMenu();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// public here so that Swing/Gtk/AppIndicator can override this
|
||||
@Override
|
||||
public
|
||||
boolean hasImage() {
|
||||
return menuEntry.hasImage();
|
||||
}
|
||||
|
||||
|
||||
// public here so that Swing/Gtk/AppIndicator can override this
|
||||
@Override
|
||||
protected
|
||||
void setImage_(final File imageFile) {
|
||||
menuEntry.setImage_(imageFile);
|
||||
}
|
||||
|
||||
// public here so that Swing/Gtk/AppIndicator can override this
|
||||
@Override
|
||||
public
|
||||
void setEnabled(final boolean enabled) {
|
||||
if (enabled) {
|
||||
Gtk.gtk_widget_set_sensitive(menuEntry._native, Gtk.TRUE);
|
||||
} else {
|
||||
Gtk.gtk_widget_set_sensitive(menuEntry._native, Gtk.FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
String getText() {
|
||||
return menuEntry.getText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setText(final String newText) {
|
||||
menuEntry.setText(newText);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void addSeparator() {
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
// some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator.
|
||||
// To work around this issue, we destroy then recreate the menu every time something is changed.
|
||||
synchronized (menuEntries) {
|
||||
// some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator.
|
||||
// To work around this issue, we destroy then recreate the menu every time something is changed.
|
||||
deleteMenu();
|
||||
|
||||
GtkEntry menuEntry = new GtkEntrySeparator(GtkMenu.this);
|
||||
menuEntries.add(menuEntry);
|
||||
|
||||
createMenu();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setShortcut(final char key) {
|
||||
menuEntry.setShortcut(key);
|
||||
}
|
||||
|
||||
// some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator.
|
||||
// To work around this issue, we destroy then recreate the menu every time something is changed.
|
||||
/**
|
||||
* Deletes the menu, and unreferences everything in it. ALSO recreates ONLY the menu object.
|
||||
*/
|
||||
void deleteMenu() {
|
||||
if (obliterateInProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_native != null) {
|
||||
// have to remove all other menu entries
|
||||
synchronized (menuEntries) {
|
||||
for (int i = 0, menuEntriesSize = menuEntries.size(); i < menuEntriesSize; i++) {
|
||||
final Entry menuEntry__ = menuEntries.get(i);
|
||||
if (menuEntry__ instanceof GtkEntry) {
|
||||
GtkEntry entry = (GtkEntry) menuEntry__;
|
||||
|
||||
Gobject.g_object_force_floating(entry._native);
|
||||
Gtk.gtk_container_remove(_native, entry._native);
|
||||
}
|
||||
else if (menuEntry__ instanceof GtkMenu) {
|
||||
GtkMenu subMenu = (GtkMenu) menuEntry__;
|
||||
|
||||
Gobject.g_object_force_floating(subMenu.menuEntry._native);
|
||||
Gtk.gtk_container_remove(_native, subMenu.menuEntry._native);
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.gtk_widget_destroy(_native);
|
||||
}
|
||||
}
|
||||
|
||||
if (getParent() != null) {
|
||||
((GtkMenu) getParent()).deleteMenu();
|
||||
}
|
||||
|
||||
// makes a new one
|
||||
_native = Gtk.gtk_menu_new();
|
||||
|
||||
// binds sub-menu to entry (if it exists! it does not for the root menu)
|
||||
if (menuEntry != null) {
|
||||
Gtk.gtk_menu_item_set_submenu(menuEntry._native, _native);
|
||||
}
|
||||
}
|
||||
|
||||
// some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator.
|
||||
// To work around this issue, we destroy then recreate the menu every time something is changed.
|
||||
void createMenu() {
|
||||
if (obliterateInProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (getParent() != null) {
|
||||
((GtkMenu) getParent()).createMenu();
|
||||
}
|
||||
|
||||
boolean hasImages = false;
|
||||
|
||||
// now add back other menu entries
|
||||
synchronized (menuEntries) {
|
||||
for (int i = 0, menuEntriesSize = menuEntries.size(); i < menuEntriesSize; i++) {
|
||||
final Entry menuEntry__ = menuEntries.get(i);
|
||||
hasImages |= menuEntry__.hasImage();
|
||||
}
|
||||
|
||||
for (int i = 0, menuEntriesSize = menuEntries.size(); i < menuEntriesSize; i++) {
|
||||
final Entry menuEntry__ = menuEntries.get(i);
|
||||
// the menu entry looks FUNKY when there are a mis-match of entries WITH and WITHOUT images
|
||||
if (menuEntry__ instanceof GtkEntry) {
|
||||
GtkEntry entry = (GtkEntry) menuEntry__;
|
||||
entry.setSpacerImage(hasImages);
|
||||
|
||||
// will also get: gsignal.c:2516: signal 'child-added' is invalid for instance '0x7f1df8244080' of type 'GtkMenu'
|
||||
Gtk.gtk_menu_shell_append(this._native, entry._native);
|
||||
Gobject.g_object_ref_sink(entry._native); // undoes "floating"
|
||||
}
|
||||
else if (menuEntry__ instanceof GtkMenu) {
|
||||
GtkMenu subMenu = (GtkMenu) menuEntry__;
|
||||
|
||||
// will also get: gsignal.c:2516: signal 'child-added' is invalid for instance '0x7f1df8244080' of type 'GtkMenu'
|
||||
Gtk.gtk_menu_shell_append(this._native, subMenu.menuEntry._native);
|
||||
Gobject.g_object_ref_sink(subMenu.menuEntry._native); // undoes "floating"
|
||||
|
||||
if (subMenu.getParent() != GtkMenu.this) {
|
||||
// we don't want to "createMenu" on our sub-menu that is assigned to us directly, as they are already doing it
|
||||
subMenu.createMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMenuAdded(_native);
|
||||
Gtk.gtk_widget_show_all(_native);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* must be called on the dispatch thread
|
||||
*
|
||||
* Completely obliterates the menu, no possible way to reconstruct it.
|
||||
*/
|
||||
private
|
||||
void obliterateMenu() {
|
||||
if (_native != null && !obliterateInProgress) {
|
||||
obliterateInProgress = true;
|
||||
|
||||
// have to remove all other menu entries
|
||||
synchronized (menuEntries) {
|
||||
// a copy is made because sub-menus remove themselves from parents when .remove() is called. If we don't
|
||||
// do this, errors will be had because indices don't line up anymore.
|
||||
ArrayList<Entry> menuEntriesCopy = new ArrayList<Entry>(this.menuEntries);
|
||||
|
||||
for (int i = 0, menuEntriesSize = menuEntriesCopy.size(); i < menuEntriesSize; i++) {
|
||||
final Entry menuEntry__ = menuEntriesCopy.get(i);
|
||||
menuEntry__.remove();
|
||||
}
|
||||
this.menuEntries.clear();
|
||||
menuEntriesCopy.clear();
|
||||
|
||||
Gtk.gtk_widget_destroy(_native);
|
||||
}
|
||||
|
||||
obliterateInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Will add a new menu entry, or update one if it already exists
|
||||
*/
|
||||
protected
|
||||
Entry addEntry_(final String menuText, final File imagePath, final Action callback) {
|
||||
// some implementations of appindicator, do NOT like having a menu added, which has no menu items yet.
|
||||
// see: https://bugs.launchpad.net/glipper/+bug/1203888
|
||||
|
||||
if (menuText == null) {
|
||||
throw new NullPointerException("Menu text cannot be null");
|
||||
}
|
||||
|
||||
// have to wait for the value
|
||||
final AtomicReference<Entry> value = new AtomicReference<Entry>();
|
||||
|
||||
dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
synchronized (menuEntries) {
|
||||
Entry menuEntry = get(menuText);
|
||||
if (menuEntry == null) {
|
||||
// some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator.
|
||||
// To work around this issue, we destroy then recreate the menu every time something is changed.
|
||||
deleteMenu();
|
||||
|
||||
menuEntry = new GtkEntryItem(GtkMenu.this, callback);
|
||||
menuEntry.setText(menuText);
|
||||
menuEntry.setImage(imagePath);
|
||||
menuEntries.add(menuEntry);
|
||||
|
||||
createMenu();
|
||||
} else if (menuEntry instanceof GtkEntryItem) {
|
||||
menuEntry.setText(menuText);
|
||||
menuEntry.setImage(imagePath);
|
||||
}
|
||||
|
||||
value.set(menuEntry);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return value.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Will add a new menu entry, or update one if it already exists
|
||||
*/
|
||||
protected
|
||||
Menu addMenu_(final String menuText, final File imagePath) {
|
||||
// some implementations of appindicator, do NOT like having a menu added, which has no menu items yet.
|
||||
// see: https://bugs.launchpad.net/glipper/+bug/1203888
|
||||
|
||||
if (menuText == null) {
|
||||
throw new NullPointerException("Menu text cannot be null");
|
||||
}
|
||||
|
||||
final AtomicReference<Menu> value = new AtomicReference<Menu>();
|
||||
|
||||
dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
synchronized (menuEntries) {
|
||||
Entry menuEntry = get(menuText);
|
||||
if (menuEntry == null) {
|
||||
// some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator.
|
||||
// To work around this issue, we destroy then recreate the menu every time something is changed.
|
||||
deleteMenu();
|
||||
|
||||
GtkMenu subMenu = new GtkMenu(getSystemTray(), GtkMenu.this);
|
||||
subMenu.setText(menuText);
|
||||
subMenu.setImage(imagePath);
|
||||
|
||||
menuEntries.add(subMenu);
|
||||
|
||||
value.set(subMenu);
|
||||
|
||||
createMenu();
|
||||
} else if (menuEntry instanceof GtkMenu) {
|
||||
menuEntry.setText(menuText);
|
||||
menuEntry.setImage(imagePath);
|
||||
|
||||
value.set(((GtkMenu) menuEntry));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return value.get();
|
||||
}
|
||||
|
||||
// a child will always remove itself from the parent.
|
||||
@Override
|
||||
public
|
||||
void remove() {
|
||||
dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
GtkMenu parent = (GtkMenu) getParent();
|
||||
|
||||
// have to remove from the parent.menuEntries first
|
||||
for (Iterator<Entry> iterator = parent.menuEntries.iterator(); iterator.hasNext(); ) {
|
||||
final Entry entry = iterator.next();
|
||||
if (entry == GtkMenu.this) {
|
||||
iterator.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// cleans up the menu
|
||||
// parent.remove__(null);
|
||||
|
||||
// delete all of the children of this submenu (must happen before the menuEntry is removed)
|
||||
obliterateMenu();
|
||||
|
||||
// remove the gtk entry item from our parent menu NATIVE components
|
||||
// NOTE: this will rebuild the parent menu
|
||||
if (menuEntry != null) {
|
||||
menuEntry.remove();
|
||||
} else {
|
||||
// have to rebuild the menu now...
|
||||
parent.deleteMenu();
|
||||
parent.createMenu();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
30
src/dorkbox/systemTray/nativeUI/NativeUI.java
Normal file
30
src/dorkbox/systemTray/nativeUI/NativeUI.java
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2016 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.nativeUI;
|
||||
|
||||
/**
|
||||
* Represents a System Tray or menu, that will have it's menu rendered via the native subsystem.
|
||||
* <p>
|
||||
* This is does not have as many features as the swing-based UI, however the trade off is that this will always have the native L&F of
|
||||
* the system (with the exception of Windows, whose native menu looks absolutely terrible).
|
||||
* <p>
|
||||
* Noticeable differences that are limitations for the NativeUI only:
|
||||
* - AppIndicator Status entries must be plain text (they are not bold as they are everywhere else).
|
||||
* - MacOS cannot have images in their menu or sub-menu's -- only plain text is possible
|
||||
*/
|
||||
public
|
||||
interface NativeUI
|
||||
{}
|
185
src/dorkbox/systemTray/nativeUI/_AppIndicatorNativeTray.java
Normal file
185
src/dorkbox/systemTray/nativeUI/_AppIndicatorNativeTray.java
Normal file
@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright 2014 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.nativeUI;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
import dorkbox.systemTray.SystemTray;
|
||||
import dorkbox.systemTray.linux.jna.AppIndicator;
|
||||
import dorkbox.systemTray.linux.jna.AppIndicatorInstanceStruct;
|
||||
import dorkbox.systemTray.linux.jna.Gobject;
|
||||
import dorkbox.systemTray.linux.jna.Gtk;
|
||||
import dorkbox.systemTray.util.ImageUtils;
|
||||
|
||||
/**
|
||||
* Class for handling all system tray interactions.
|
||||
* specialization for using app indicators in ubuntu unity
|
||||
*
|
||||
* Derived from
|
||||
* Lantern: https://github.com/getlantern/lantern/ Apache 2.0 License Copyright 2010 Brave New Software Project, Inc.
|
||||
*
|
||||
* AppIndicators DO NOT support anything other than plain gtk-menus, because of how they use dbus so no tooltips AND no custom widgets
|
||||
*
|
||||
*
|
||||
*
|
||||
* As a result of this decision by Canonical, we have to resort to hacks to get it to do what we want. BY NO MEANS IS THIS PERFECT.
|
||||
*
|
||||
*
|
||||
* We still cannot have tooltips, but we *CAN* have custom widgets in the menu (because it's our swing menu now...)
|
||||
*
|
||||
*
|
||||
* It would be too much work to re-implement AppIndicators, or even to use LD_PRELOAD + restart service to do what we want.
|
||||
*
|
||||
* As a result, we have some wicked little hacks which are rather effective (but have a small side-effect of very briefly
|
||||
* showing a blank menu)
|
||||
*
|
||||
* // What are AppIndicators?
|
||||
* http://unity.ubuntu.com/projects/appindicators/
|
||||
*
|
||||
*
|
||||
* // Entry-point into appindicators
|
||||
* http://bazaar.launchpad.net/~unity-team/unity/trunk/view/head:/services/panel-main.c
|
||||
*
|
||||
*
|
||||
* // The idiocy of appindicators
|
||||
* https://bugs.launchpad.net/screenlets/+bug/522152
|
||||
*
|
||||
* // Code of how the dbus menus work
|
||||
* http://bazaar.launchpad.net/~dbusmenu-team/libdbusmenu/trunk.16.10/view/head:/libdbusmenu-gtk/client.c
|
||||
* https://developer.ubuntu.com/api/devel/ubuntu-12.04/c/dbusmenugtk/index.html
|
||||
*
|
||||
* // more info about trying to put widgets into GTK menus
|
||||
* http://askubuntu.com/questions/16431/putting-an-arbitrary-gtk-widget-into-an-appindicator-indicator
|
||||
*
|
||||
* // possible idea on how to get GTK widgets into GTK menus
|
||||
* https://launchpad.net/ido
|
||||
* http://bazaar.launchpad.net/~canonical-dx-team/ido/trunk/view/head:/src/idoentrymenuitem.c
|
||||
* http://bazaar.launchpad.net/~ubuntu-desktop/ido/gtk3/files
|
||||
*/
|
||||
@SuppressWarnings("Duplicates")
|
||||
public
|
||||
class _AppIndicatorNativeTray extends GtkMenu {
|
||||
private volatile AppIndicatorInstanceStruct appIndicator;
|
||||
private boolean isActive = false;
|
||||
|
||||
// This is required if we have JavaFX or SWT shutdown hooks (to prevent us from shutting down twice...)
|
||||
private AtomicBoolean shuttingDown = new AtomicBoolean();
|
||||
|
||||
// is the system tray visible or not.
|
||||
private volatile boolean visible = true;
|
||||
|
||||
// appindicators DO NOT support anything other than PLAIN gtk-menus (which we hack to support swing menus)
|
||||
// they ALSO do not support tooltips, so we cater to the lowest common denominator
|
||||
// trayIcon.setToolTip("app name");
|
||||
|
||||
public
|
||||
_AppIndicatorNativeTray(final SystemTray systemTray) {
|
||||
super(systemTray, null);
|
||||
|
||||
Gtk.startGui();
|
||||
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
// we initialize with a blank image
|
||||
File image = ImageUtils.getTransparentImage(ImageUtils.ENTRY_SIZE);
|
||||
String id = System.nanoTime() + "DBST";
|
||||
appIndicator = AppIndicator.app_indicator_new(id, image.getAbsolutePath(), AppIndicator.CATEGORY_APPLICATION_STATUS);
|
||||
}
|
||||
});
|
||||
|
||||
Gtk.waitForStartup();
|
||||
}
|
||||
|
||||
public final
|
||||
void shutdown() {
|
||||
if (!shuttingDown.getAndSet(true)) {
|
||||
// must happen asap, so our hook properly notices we are in shutdown mode
|
||||
final AppIndicatorInstanceStruct savedAppIndicator = appIndicator;
|
||||
appIndicator = null;
|
||||
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
// STATUS_PASSIVE hides the indicator
|
||||
AppIndicator.app_indicator_set_status(savedAppIndicator, AppIndicator.STATUS_PASSIVE);
|
||||
Pointer p = savedAppIndicator.getPointer();
|
||||
Gobject.g_object_unref(p);
|
||||
}
|
||||
});
|
||||
|
||||
super.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
boolean hasImage() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setImage_(final File imageFile) {
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
AppIndicator.app_indicator_set_icon(appIndicator, imageFile.getAbsolutePath());
|
||||
|
||||
if (!isActive) {
|
||||
isActive = true;
|
||||
|
||||
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_ACTIVE);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setEnabled(final boolean setEnabled) {
|
||||
visible = !setEnabled;
|
||||
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
if (visible && !setEnabled) {
|
||||
// STATUS_PASSIVE hides the indicator
|
||||
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_PASSIVE);
|
||||
}
|
||||
else if (!visible && setEnabled) {
|
||||
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_ACTIVE);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* MUST BE AFTER THE ITEM IS ADDED/CHANGED from the menu
|
||||
*/
|
||||
protected final
|
||||
void onMenuAdded(final Pointer menu) {
|
||||
// see: https://code.launchpad.net/~mterry/libappindicator/fix-menu-leak/+merge/53247
|
||||
AppIndicator.app_indicator_set_menu(appIndicator, menu);
|
||||
}
|
||||
}
|
125
src/dorkbox/systemTray/nativeUI/_AwtTray.java
Normal file
125
src/dorkbox/systemTray/nativeUI/_AwtTray.java
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright 2014 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.nativeUI;
|
||||
|
||||
import java.awt.AWTException;
|
||||
import java.awt.Image;
|
||||
import java.awt.PopupMenu;
|
||||
import java.awt.SystemTray;
|
||||
import java.awt.TrayIcon;
|
||||
import java.io.File;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
/**
|
||||
* Class for handling all system tray interaction, via AWT.
|
||||
*
|
||||
* It doesn't work well on linux. See bugs:
|
||||
* http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6267936
|
||||
* http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6453521
|
||||
* https://stackoverflow.com/questions/331407/java-trayicon-using-image-with-transparent-background/3882028#3882028
|
||||
*/
|
||||
@SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "WeakerAccess"})
|
||||
public
|
||||
class _AwtTray extends AwtMenu {
|
||||
private volatile SystemTray tray;
|
||||
private volatile TrayIcon trayIcon;
|
||||
|
||||
// is the system tray visible or not.
|
||||
private volatile boolean visible = true;
|
||||
|
||||
// Called in the EDT
|
||||
public
|
||||
_AwtTray(final dorkbox.systemTray.SystemTray systemTray) {
|
||||
super(systemTray, null, new PopupMenu());
|
||||
|
||||
if (!SystemTray.isSupported()) {
|
||||
throw new RuntimeException("System Tray is not supported in this configuration! Please write an issue and include your OS " +
|
||||
"type and configuration");
|
||||
}
|
||||
|
||||
_AwtTray.this.tray = SystemTray.getSystemTray();
|
||||
}
|
||||
|
||||
public
|
||||
void shutdown() {
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
removeAll();
|
||||
remove();
|
||||
|
||||
tray.remove(trayIcon);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public
|
||||
void setImage_(final File iconFile) {
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
// stupid java won't scale it right away, so we have to do this twice to get the correct size
|
||||
final Image trayImage = new ImageIcon(iconFile.getAbsolutePath()).getImage();
|
||||
trayImage.flush();
|
||||
|
||||
if (trayIcon == null) {
|
||||
// here we init. everything
|
||||
trayIcon = new TrayIcon(trayImage);
|
||||
|
||||
// appindicators DO NOT support anything other than PLAIN gtk-menus (which we hack to support swing menus)
|
||||
// they ALSO do not support tooltips, so we cater to the lowest common denominator
|
||||
// trayIcon.setToolTip("app name");
|
||||
|
||||
trayIcon.setPopupMenu((PopupMenu) _native);
|
||||
|
||||
try {
|
||||
tray.add(trayIcon);
|
||||
} catch (AWTException e) {
|
||||
dorkbox.systemTray.SystemTray.logger.error("TrayIcon could not be added.", e);
|
||||
}
|
||||
} else {
|
||||
trayIcon.setImage(trayImage);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
public
|
||||
void setEnabled(final boolean setEnabled) {
|
||||
visible = !setEnabled;
|
||||
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
if (visible && !setEnabled) {
|
||||
tray.remove(trayIcon);
|
||||
}
|
||||
else if (!visible && setEnabled) {
|
||||
try {
|
||||
tray.add(trayIcon);
|
||||
} catch (AWTException e) {
|
||||
dorkbox.systemTray.SystemTray.logger.error("Error adding the icon back to the tray");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
186
src/dorkbox/systemTray/nativeUI/_GtkStatusIconNativeTray.java
Normal file
186
src/dorkbox/systemTray/nativeUI/_GtkStatusIconNativeTray.java
Normal file
@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright 2014 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.nativeUI;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import com.sun.jna.NativeLong;
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
import dorkbox.systemTray.SystemTray;
|
||||
import dorkbox.systemTray.linux.jna.GEventCallback;
|
||||
import dorkbox.systemTray.linux.jna.GdkEventButton;
|
||||
import dorkbox.systemTray.linux.jna.Gobject;
|
||||
import dorkbox.systemTray.linux.jna.Gtk;
|
||||
|
||||
/**
|
||||
* Class for handling all system tray interactions via GTK.
|
||||
* <p/>
|
||||
* This is the "old" way to do it, and does not work with some newer desktop environments.
|
||||
*/
|
||||
@SuppressWarnings("Duplicates")
|
||||
public
|
||||
class _GtkStatusIconNativeTray extends GtkMenu {
|
||||
private volatile Pointer trayIcon;
|
||||
|
||||
// http://code.metager.de/source/xref/gnome/Platform/gtk%2B/gtk/deprecated/gtkstatusicon.c
|
||||
// https://github.com/djdeath/glib/blob/master/gobject/gobject.c
|
||||
|
||||
// have to save these in a field to prevent GC on the objects (since they go out-of-scope from java)
|
||||
private final List<Object> gtkCallbacks = new ArrayList<Object>();
|
||||
|
||||
// This is required if we have JavaFX or SWT shutdown hooks (to prevent us from shutting down twice...)
|
||||
private AtomicBoolean shuttingDown = new AtomicBoolean();
|
||||
|
||||
private volatile boolean isActive = false;
|
||||
|
||||
// is the system tray visible or not.
|
||||
private volatile boolean visible = true;
|
||||
|
||||
// called on the EDT
|
||||
public
|
||||
_GtkStatusIconNativeTray(final SystemTray systemTray) {
|
||||
super(systemTray, null);
|
||||
|
||||
// appindicators DO NOT support anything other than PLAIN gtk-menus (which we hack to support swing menus)
|
||||
// they ALSO do not support tooltips, so we cater to the lowest common denominator
|
||||
// trayIcon.setToolTip("app name");
|
||||
|
||||
Gtk.startGui();
|
||||
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
trayIcon = Gtk.gtk_status_icon_new();
|
||||
|
||||
final GEventCallback gtkCallback = new GEventCallback() {
|
||||
@Override
|
||||
public
|
||||
void callback(Pointer notUsed, final GdkEventButton event) {
|
||||
// show the swing menu on the EDT
|
||||
// BUTTON_PRESS only (any mouse click)
|
||||
if (event.type == 4) {
|
||||
Gtk.gtk_menu_popup(_native, null, null, Gtk.gtk_status_icon_position_menu, trayIcon, 0, event.time);
|
||||
}
|
||||
}
|
||||
};
|
||||
final NativeLong button_press_event = Gobject.g_signal_connect_object(trayIcon, "button_press_event",
|
||||
gtkCallback, null, 0);
|
||||
|
||||
// have to do this to prevent GC on these objects
|
||||
gtkCallbacks.add(gtkCallback);
|
||||
gtkCallbacks.add(button_press_event);
|
||||
}
|
||||
});
|
||||
|
||||
Gtk.waitForStartup();
|
||||
|
||||
// we have to be able to set our title, otherwise the gnome-shell extension WILL NOT work
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
// by default, the title/name of the tray icon is "java". We are the only java-based tray icon, so we just use that.
|
||||
// If you change "SystemTray" to something else, make sure to change it in extension.js as well
|
||||
|
||||
// necessary for gnome icon detection/placement because we move tray icons around by title. This is hardcoded
|
||||
// in extension.js, so don't change it
|
||||
Gtk.gtk_status_icon_set_title(trayIcon, "SystemTray");
|
||||
|
||||
// can cause
|
||||
// Gdk-CRITICAL **: gdk_window_thaw_toplevel_updates: assertion 'window->update_and_descendants_freeze_count > 0' failed
|
||||
// Gdk-CRITICAL **: IA__gdk_window_thaw_toplevel_updates_libgtk_only: assertion 'private->update_and_descendants_freeze_count > 0' failed
|
||||
|
||||
// ... so, bizzaro things going on here. These errors DO NOT happen if JavaFX is dispatching the events.
|
||||
// BUT this is REQUIRED when running JavaFX. For unknown reasons, the title isn't pushed to GTK, so our
|
||||
// gnome-shell extension cannot see our tray icon -- so naturally, it won't move it to the "top" area and
|
||||
// we appear broken.
|
||||
if (SystemTray.isJavaFxLoaded) {
|
||||
Gtk.gtk_status_icon_set_name(trayIcon, "SystemTray");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("FieldRepeatedlyAccessedInMethod")
|
||||
public final
|
||||
void shutdown() {
|
||||
if (!shuttingDown.getAndSet(true)) {
|
||||
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
// this hides the indicator
|
||||
Gtk.gtk_status_icon_set_visible(trayIcon, false);
|
||||
Gobject.g_object_unref(trayIcon);
|
||||
|
||||
// mark for GC
|
||||
trayIcon = null;
|
||||
gtkCallbacks.clear();
|
||||
}
|
||||
});
|
||||
|
||||
super.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
boolean hasImage() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setImage_(final File iconFile) {
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
Gtk.gtk_status_icon_set_from_file(trayIcon, iconFile.getAbsolutePath());
|
||||
|
||||
if (!isActive) {
|
||||
isActive = true;
|
||||
Gtk.gtk_status_icon_set_visible(trayIcon, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setEnabled(final boolean setEnabled) {
|
||||
visible = !setEnabled;
|
||||
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
if (visible && !setEnabled) {
|
||||
Gtk.gtk_status_icon_set_visible(trayIcon, setEnabled);
|
||||
} else if (!visible && setEnabled) {
|
||||
Gtk.gtk_status_icon_set_visible(trayIcon, setEnabled);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,317 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.swing;
|
||||
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JMenuItem;
|
||||
|
||||
import dorkbox.systemTray.Entry;
|
||||
import dorkbox.systemTray.Menu;
|
||||
import dorkbox.systemTray.util.ImageUtils;
|
||||
|
||||
abstract
|
||||
class EntryImpl implements Entry {
|
||||
private final int id = MenuImpl.MENU_ID_COUNTER.getAndIncrement();
|
||||
|
||||
private final MenuImpl parent;
|
||||
final JComponent _native;
|
||||
|
||||
// this have to be volatile, because they can be changed from any thread
|
||||
private volatile String text;
|
||||
|
||||
// this is ALWAYS called on the EDT.
|
||||
EntryImpl(final MenuImpl parent, final JComponent menuItem) {
|
||||
this.parent = parent;
|
||||
this._native = menuItem;
|
||||
|
||||
parent._native.add(menuItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
Menu getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* must always be called in the EDT thread
|
||||
*/
|
||||
abstract
|
||||
void renderText(final String text);
|
||||
|
||||
/**
|
||||
* Not always called on the EDT thread
|
||||
*/
|
||||
abstract
|
||||
void setImage_(final File imageFile);
|
||||
|
||||
/**
|
||||
* Enables, or disables the sub-menu entry.
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
void setEnabled(final boolean enabled) {
|
||||
_native.setEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setShortcut(final char key) {
|
||||
if (_native instanceof JMenuItem) {
|
||||
// yikes...
|
||||
final int vKey = getVkKey(key);
|
||||
|
||||
parent.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
((JMenuItem) _native).setMnemonic(vKey);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setText(final String newText) {
|
||||
this.text = newText;
|
||||
|
||||
parent.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
renderText(newText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setImage(final File imageFile) {
|
||||
if (imageFile == null) {
|
||||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageFile));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setImage(final String imagePath) {
|
||||
if (imagePath == null) {
|
||||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imagePath));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setImage(final URL imageUrl) {
|
||||
if (imageUrl == null) {
|
||||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageUrl));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setImage(final String cacheName, final InputStream imageStream) {
|
||||
if (imageStream == null) {
|
||||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, cacheName, imageStream));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setImage(final InputStream imageStream) {
|
||||
if (imageStream == null) {
|
||||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageStream));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void remove() {
|
||||
parent.dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
removePrivate();
|
||||
parent._native.remove(_native);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// called when this item is removed. Necessary to cleanup/remove itself
|
||||
abstract
|
||||
void removePrivate();
|
||||
|
||||
@Override
|
||||
public final
|
||||
int hashCode() {
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final
|
||||
boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EntryImpl other = (EntryImpl) obj;
|
||||
return this.id == other.id;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts a key character into it's corresponding VK entry
|
||||
*/
|
||||
static
|
||||
int getVkKey(final char key) {
|
||||
switch (key) {
|
||||
case 0x08: return KeyEvent.VK_BACK_SPACE;
|
||||
case 0x09: return KeyEvent.VK_TAB;
|
||||
case 0x0a: return KeyEvent.VK_ENTER;
|
||||
case 0x1B: return KeyEvent.VK_ESCAPE;
|
||||
case 0x20AC: return KeyEvent.VK_EURO_SIGN;
|
||||
case 0x20: return KeyEvent.VK_SPACE;
|
||||
case 0x21: return KeyEvent.VK_EXCLAMATION_MARK;
|
||||
case 0x22: return KeyEvent.VK_QUOTEDBL;
|
||||
case 0x23: return KeyEvent.VK_NUMBER_SIGN;
|
||||
case 0x24: return KeyEvent.VK_DOLLAR;
|
||||
case 0x26: return KeyEvent.VK_AMPERSAND;
|
||||
case 0x27: return KeyEvent.VK_QUOTE;
|
||||
case 0x28: return KeyEvent.VK_LEFT_PARENTHESIS;
|
||||
case 0x29: return KeyEvent.VK_RIGHT_PARENTHESIS;
|
||||
case 0x2A: return KeyEvent.VK_ASTERISK;
|
||||
case 0x2B: return KeyEvent.VK_PLUS;
|
||||
case 0x2C: return KeyEvent.VK_COMMA;
|
||||
case 0x2D: return KeyEvent.VK_MINUS;
|
||||
case 0x2E: return KeyEvent.VK_PERIOD;
|
||||
case 0x2F: return KeyEvent.VK_SLASH;
|
||||
case 0x30: return KeyEvent.VK_0;
|
||||
case 0x31: return KeyEvent.VK_1;
|
||||
case 0x32: return KeyEvent.VK_2;
|
||||
case 0x33: return KeyEvent.VK_3;
|
||||
case 0x34: return KeyEvent.VK_4;
|
||||
case 0x35: return KeyEvent.VK_5;
|
||||
case 0x36: return KeyEvent.VK_6;
|
||||
case 0x37: return KeyEvent.VK_7;
|
||||
case 0x38: return KeyEvent.VK_8;
|
||||
case 0x39: return KeyEvent.VK_9;
|
||||
case 0x3A: return KeyEvent.VK_COLON;
|
||||
case 0x3B: return KeyEvent.VK_SEMICOLON;
|
||||
case 0x3C: return KeyEvent.VK_LESS;
|
||||
case 0x3D: return KeyEvent.VK_EQUALS;
|
||||
case 0x3E: return KeyEvent.VK_GREATER;
|
||||
case 0x40: return KeyEvent.VK_AT;
|
||||
case 0x41: return KeyEvent.VK_A;
|
||||
case 0x42: return KeyEvent.VK_B;
|
||||
case 0x43: return KeyEvent.VK_C;
|
||||
case 0x44: return KeyEvent.VK_D;
|
||||
case 0x45: return KeyEvent.VK_E;
|
||||
case 0x46: return KeyEvent.VK_F;
|
||||
case 0x47: return KeyEvent.VK_G;
|
||||
case 0x48: return KeyEvent.VK_H;
|
||||
case 0x49: return KeyEvent.VK_I;
|
||||
case 0x4A: return KeyEvent.VK_J;
|
||||
case 0x4B: return KeyEvent.VK_K;
|
||||
case 0x4C: return KeyEvent.VK_L;
|
||||
case 0x4D: return KeyEvent.VK_M;
|
||||
case 0x4E: return KeyEvent.VK_N;
|
||||
case 0x4F: return KeyEvent.VK_O;
|
||||
case 0x50: return KeyEvent.VK_P;
|
||||
case 0x51: return KeyEvent.VK_Q;
|
||||
case 0x52: return KeyEvent.VK_R;
|
||||
case 0x53: return KeyEvent.VK_S;
|
||||
case 0x54: return KeyEvent.VK_T;
|
||||
case 0x55: return KeyEvent.VK_U;
|
||||
case 0x56: return KeyEvent.VK_V;
|
||||
case 0x57: return KeyEvent.VK_W;
|
||||
case 0x58: return KeyEvent.VK_X;
|
||||
case 0x59: return KeyEvent.VK_Y;
|
||||
case 0x5A: return KeyEvent.VK_Z;
|
||||
case 0x5B: return KeyEvent.VK_OPEN_BRACKET;
|
||||
case 0x5C: return KeyEvent.VK_BACK_SLASH;
|
||||
case 0x5D: return KeyEvent.VK_CLOSE_BRACKET;
|
||||
case 0x5E: return KeyEvent.VK_CIRCUMFLEX;
|
||||
case 0x5F: return KeyEvent.VK_UNDERSCORE;
|
||||
case 0x60: return KeyEvent.VK_BACK_QUOTE;
|
||||
case 0x61: return KeyEvent.VK_A;
|
||||
case 0x62: return KeyEvent.VK_B;
|
||||
case 0x63: return KeyEvent.VK_C;
|
||||
case 0x64: return KeyEvent.VK_D;
|
||||
case 0x65: return KeyEvent.VK_E;
|
||||
case 0x66: return KeyEvent.VK_F;
|
||||
case 0x67: return KeyEvent.VK_G;
|
||||
case 0x68: return KeyEvent.VK_H;
|
||||
case 0x69: return KeyEvent.VK_I;
|
||||
case 0x6A: return KeyEvent.VK_J;
|
||||
case 0x6B: return KeyEvent.VK_K;
|
||||
case 0x6C: return KeyEvent.VK_L;
|
||||
case 0x6D: return KeyEvent.VK_M;
|
||||
case 0x6E: return KeyEvent.VK_N;
|
||||
case 0x6F: return KeyEvent.VK_O;
|
||||
case 0x70: return KeyEvent.VK_P;
|
||||
case 0x71: return KeyEvent.VK_Q;
|
||||
case 0x72: return KeyEvent.VK_R;
|
||||
case 0x73: return KeyEvent.VK_S;
|
||||
case 0x74: return KeyEvent.VK_T;
|
||||
case 0x75: return KeyEvent.VK_U;
|
||||
case 0x76: return KeyEvent.VK_V;
|
||||
case 0x77: return KeyEvent.VK_W;
|
||||
case 0x78: return KeyEvent.VK_X;
|
||||
case 0x79: return KeyEvent.VK_Y;
|
||||
case 0x7A: return KeyEvent.VK_Z;
|
||||
case 0x7B: return KeyEvent.VK_BRACELEFT;
|
||||
case 0x7D: return KeyEvent.VK_BRACERIGHT;
|
||||
case 0x7F: return KeyEvent.VK_DELETE;
|
||||
case 0xA1: return KeyEvent.VK_INVERTED_EXCLAMATION_MARK;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.swing;
|
||||
package dorkbox.systemTray.swingUI;
|
||||
|
||||
import java.awt.Insets;
|
||||
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.swing;
|
||||
package dorkbox.systemTray.swingUI;
|
||||
|
||||
import java.awt.Insets;
|
||||
|
208
src/dorkbox/systemTray/swingUI/SwingEntry.java
Normal file
208
src/dorkbox/systemTray/swingUI/SwingEntry.java
Normal file
@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Copyright 2014 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.swingUI;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JMenuItem;
|
||||
|
||||
import dorkbox.systemTray.Entry;
|
||||
import dorkbox.systemTray.Menu;
|
||||
import dorkbox.systemTray.util.ImageUtils;
|
||||
import dorkbox.systemTray.util.MenuBase;
|
||||
import dorkbox.systemTray.util.SystemTrayFixes;
|
||||
|
||||
abstract
|
||||
class SwingEntry implements Entry, SwingUI {
|
||||
private final int id = MenuBase.MENU_ID_COUNTER.getAndIncrement();
|
||||
|
||||
private final SwingMenu parent;
|
||||
final JComponent _native;
|
||||
|
||||
// this have to be volatile, because they can be changed from any thread
|
||||
private volatile String text;
|
||||
|
||||
// this is ALWAYS called on the EDT.
|
||||
SwingEntry(final SwingMenu parent, final JComponent menuItem) {
|
||||
this.parent = parent;
|
||||
this._native = menuItem;
|
||||
|
||||
parent._native.add(menuItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
Menu getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* must always be called in the EDT thread
|
||||
*/
|
||||
abstract
|
||||
void renderText(final String text);
|
||||
|
||||
/**
|
||||
* Not always called on the EDT thread
|
||||
*/
|
||||
abstract
|
||||
void setImage_(final File imageFile);
|
||||
|
||||
/**
|
||||
* Enables, or disables the sub-menu entry.
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
void setEnabled(final boolean enabled) {
|
||||
_native.setEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setShortcut(final char key) {
|
||||
if (_native instanceof JMenuItem) {
|
||||
// yikes...
|
||||
final int vKey = SystemTrayFixes.getVirtualKey(key);
|
||||
|
||||
parent.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
((JMenuItem) _native).setMnemonic(vKey);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setText(final String newText) {
|
||||
this.text = newText;
|
||||
|
||||
parent.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
renderText(newText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setImage(final File imageFile) {
|
||||
if (imageFile == null) {
|
||||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageFile));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setImage(final String imagePath) {
|
||||
if (imagePath == null) {
|
||||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imagePath));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setImage(final URL imageUrl) {
|
||||
if (imageUrl == null) {
|
||||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageUrl));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setImage(final String cacheName, final InputStream imageStream) {
|
||||
if (imageStream == null) {
|
||||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, cacheName, imageStream));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setImage(final InputStream imageStream) {
|
||||
if (imageStream == null) {
|
||||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageStream));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void remove() {
|
||||
parent.dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
removePrivate();
|
||||
parent._native.remove(_native);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// called when this item is removed. Necessary to cleanup/remove itself
|
||||
abstract
|
||||
void removePrivate();
|
||||
|
||||
@Override
|
||||
public final
|
||||
int hashCode() {
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final
|
||||
boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SwingEntry other = (SwingEntry) obj;
|
||||
return this.id == other.id;
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.swing;
|
||||
package dorkbox.systemTray.swingUI;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
@ -25,7 +25,7 @@ import javax.swing.JMenuItem;
|
||||
import dorkbox.systemTray.Action;
|
||||
import dorkbox.util.SwingUtil;
|
||||
|
||||
class EntryItem extends EntryImpl {
|
||||
class SwingEntryItem extends SwingEntry {
|
||||
|
||||
private final ActionListener swingCallback;
|
||||
|
||||
@ -33,7 +33,7 @@ class EntryItem extends EntryImpl {
|
||||
private volatile Action callback;
|
||||
|
||||
// this is ALWAYS called on the EDT.
|
||||
EntryItem(final MenuImpl parent, final Action callback) {
|
||||
SwingEntryItem(final SwingMenu parent, final Action callback) {
|
||||
super(parent, new AdjustedJMenuItem());
|
||||
this.callback = callback;
|
||||
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.swing;
|
||||
package dorkbox.systemTray.swingUI;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@ -21,10 +21,10 @@ import javax.swing.JSeparator;
|
||||
|
||||
import dorkbox.systemTray.Action;
|
||||
|
||||
class EntrySeparator extends EntryImpl implements dorkbox.systemTray.Separator {
|
||||
class SwingEntrySeparator extends SwingEntry implements dorkbox.systemTray.Separator {
|
||||
|
||||
// this is ALWAYS called on the EDT.
|
||||
EntrySeparator(final MenuImpl parent) {
|
||||
SwingEntrySeparator(final SwingMenu parent) {
|
||||
super(parent, new JSeparator(JSeparator.HORIZONTAL));
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.swing;
|
||||
package dorkbox.systemTray.swingUI;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.io.File;
|
||||
@ -23,10 +23,10 @@ import javax.swing.JMenuItem;
|
||||
import dorkbox.systemTray.Action;
|
||||
import dorkbox.systemTray.Status;
|
||||
|
||||
class EntryStatus extends EntryImpl implements Status {
|
||||
class SwingEntryStatus extends SwingEntry implements Status {
|
||||
|
||||
// this is ALWAYS called on the EDT.
|
||||
EntryStatus(final MenuImpl parent, final String label) {
|
||||
SwingEntryStatus(final SwingMenu parent, final String label) {
|
||||
super(parent, new JMenuItem());
|
||||
setText(label);
|
||||
}
|
||||
@ -34,11 +34,12 @@ class EntryStatus extends EntryImpl implements Status {
|
||||
// called in the EDT thread
|
||||
@Override
|
||||
void renderText(final String text) {
|
||||
((JMenuItem) _native).setText(text);
|
||||
Font font = _native.getFont();
|
||||
Font font1 = font.deriveFont(Font.BOLD);
|
||||
_native.setFont(font1);
|
||||
|
||||
((JMenuItem) _native).setText(text);
|
||||
|
||||
// this makes sure it can't be selected
|
||||
_native.setEnabled(false);
|
||||
}
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.swing;
|
||||
package dorkbox.systemTray.swingUI;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@ -22,10 +22,10 @@ import javax.swing.JComponent;
|
||||
import dorkbox.systemTray.Action;
|
||||
|
||||
// TODO: buggy. The menu will **sometimes** stop responding to the "enter" key after this. Mnemonics still work however.
|
||||
class EntryWidget extends EntryImpl implements dorkbox.systemTray.Separator {
|
||||
class SwingEntryWidget extends SwingEntry implements dorkbox.systemTray.Separator {
|
||||
|
||||
// this is ALWAYS called on the EDT.
|
||||
EntryWidget(final MenuImpl parent, JComponent widget) {
|
||||
SwingEntryWidget(final SwingMenu parent, JComponent widget) {
|
||||
super(parent, widget);
|
||||
|
||||
_native.setEnabled(true);
|
349
src/dorkbox/systemTray/swingUI/SwingMenu.java
Normal file
349
src/dorkbox/systemTray/swingUI/SwingMenu.java
Normal file
@ -0,0 +1,349 @@
|
||||
/*
|
||||
* Copyright 2014 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.swingUI;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JMenuItem;
|
||||
|
||||
import dorkbox.systemTray.Action;
|
||||
import dorkbox.systemTray.Entry;
|
||||
import dorkbox.systemTray.Menu;
|
||||
import dorkbox.systemTray.Status;
|
||||
import dorkbox.systemTray.SystemTray;
|
||||
import dorkbox.systemTray.util.MenuBase;
|
||||
import dorkbox.systemTray.util.SystemTrayFixes;
|
||||
import dorkbox.util.SwingUtil;
|
||||
|
||||
// this is a weird composite class, because it must be a Menu, but ALSO a Entry -- so it has both
|
||||
@SuppressWarnings("ForLoopReplaceableByForEach")
|
||||
class SwingMenu extends MenuBase implements SwingUI {
|
||||
|
||||
// sub-menu = AdjustedJMenu
|
||||
// systemtray = TrayPopup
|
||||
volatile JComponent _native;
|
||||
|
||||
// this have to be volatile, because they can be changed from any thread
|
||||
private volatile String text;
|
||||
private volatile boolean hasLegitIcon = false;
|
||||
|
||||
/**
|
||||
* Called in the EDT
|
||||
*
|
||||
* @param systemTray the system tray (which is the object that sits in the system tray)
|
||||
* @param parent the parent of this menu, null if the parent is the system tray
|
||||
* @param _native the native element that represents this menu
|
||||
*/
|
||||
SwingMenu(final SystemTray systemTray, final Menu parent, final JComponent _native) {
|
||||
super(systemTray, parent);
|
||||
this._native = _native;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final
|
||||
void dispatch(final Runnable runnable) {
|
||||
// this will properly check if we are running on the EDT
|
||||
SwingUtil.invokeLater(runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final
|
||||
void dispatchAndWait(final Runnable runnable) {
|
||||
// this will properly check if we are running on the EDT
|
||||
try {
|
||||
SwingUtil.invokeAndWait(runnable);
|
||||
} catch (Exception e) {
|
||||
SystemTray.logger.error("Error processing event on the dispatch thread.", e);
|
||||
}
|
||||
}
|
||||
|
||||
// always called in the EDT
|
||||
protected final
|
||||
void renderText(final String text) {
|
||||
((JMenuItem) _native).setText(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setText(final String newText) {
|
||||
text = newText;
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
renderText(newText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Will add a new menu entry, or update one if it already exists
|
||||
* NOT ALWAYS CALLED ON EDT
|
||||
*/
|
||||
protected final
|
||||
Entry addEntry_(final String menuText, final File imagePath, final Action callback) {
|
||||
if (menuText == null) {
|
||||
throw new NullPointerException("Menu text cannot be null");
|
||||
}
|
||||
|
||||
final AtomicReference<Entry> value = new AtomicReference<Entry>();
|
||||
|
||||
dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
synchronized (menuEntries) {
|
||||
Entry entry = get(menuText);
|
||||
|
||||
if (entry == null) {
|
||||
// must always be called on the EDT
|
||||
entry = new SwingEntryItem(SwingMenu.this, callback);
|
||||
entry.setText(menuText);
|
||||
entry.setImage(imagePath);
|
||||
|
||||
menuEntries.add(entry);
|
||||
} else if (entry instanceof SwingEntryItem) {
|
||||
entry.setText(menuText);
|
||||
entry.setImage(imagePath);
|
||||
}
|
||||
|
||||
value.set(entry);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return value.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Will add a new sub-menu entry, or update one if it already exists
|
||||
* NOT ALWAYS CALLED ON EDT
|
||||
*/
|
||||
protected final
|
||||
Menu addMenu_(final String menuText, final File imagePath) {
|
||||
if (menuText == null) {
|
||||
throw new NullPointerException("Menu text cannot be null");
|
||||
}
|
||||
|
||||
final AtomicReference<Menu> value = new AtomicReference<Menu>();
|
||||
|
||||
dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
synchronized (menuEntries) {
|
||||
Entry entry = get(menuText);
|
||||
|
||||
if (entry == null) {
|
||||
// must always be called on the EDT
|
||||
entry = new SwingMenu(getSystemTray(), SwingMenu.this, new AdjustedJMenu());
|
||||
_native.add(((SwingMenu) entry)._native); // have to add it separately
|
||||
|
||||
entry.setText(menuText);
|
||||
entry.setImage(imagePath);
|
||||
value.set((Menu) entry);
|
||||
|
||||
} else if (entry instanceof SwingMenu) {
|
||||
entry.setText(menuText);
|
||||
entry.setImage(imagePath);
|
||||
}
|
||||
|
||||
menuEntries.add(entry);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return value.get();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// public here so that Swing/Gtk/AppIndicator can override this
|
||||
public
|
||||
void setImage_(final File imageFile) {
|
||||
hasLegitIcon = imageFile != null;
|
||||
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
if (imageFile != null) {
|
||||
ImageIcon origIcon = new ImageIcon(imageFile.getAbsolutePath());
|
||||
((JMenuItem) _native).setIcon(origIcon);
|
||||
}
|
||||
else {
|
||||
((JMenuItem) _native).setIcon(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean hasImage() {
|
||||
return hasLegitIcon;
|
||||
}
|
||||
|
||||
// public here so that Swing/Gtk/AppIndicator can override this
|
||||
@Override
|
||||
public
|
||||
void setEnabled(final boolean enabled) {
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
_native.setEnabled(enabled);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* NOT ALWAYS CALLED ON EDT
|
||||
*/
|
||||
@Override
|
||||
public final
|
||||
void addSeparator() {
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
synchronized (menuEntries) {
|
||||
synchronized (menuEntries) {
|
||||
Entry entry = new SwingEntrySeparator(SwingMenu.this);
|
||||
menuEntries.add(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: buggy. The menu will **sometimes** stop responding to the "enter" key after this. Mnemonics still work however.
|
||||
// public
|
||||
// Entry addWidget(final JComponent widget) {
|
||||
// if (widget == null) {
|
||||
// throw new NullPointerException("Widget cannot be null");
|
||||
// }
|
||||
//
|
||||
// final AtomicReference<Entry> value = new AtomicReference<Entry>();
|
||||
//
|
||||
// dispatchAndWait(new Runnable() {
|
||||
// @Override
|
||||
// public
|
||||
// void run() {
|
||||
// synchronized (menuEntries) {
|
||||
// // must always be called on the EDT
|
||||
// Entry entry = new SwingEntryWidget(SwingMenu.this, widget);
|
||||
// value.set(entry);
|
||||
// menuEntries.add(entry);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// return value.get();
|
||||
// }
|
||||
|
||||
|
||||
// public here so that Swing/Gtk/AppIndicator can access this
|
||||
public final
|
||||
void setStatus(final String statusText) {
|
||||
final SwingMenu _this = this;
|
||||
dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
synchronized (menuEntries) {
|
||||
// status is ALWAYS at 0 index...
|
||||
SwingEntry menuEntry = null;
|
||||
if (!menuEntries.isEmpty()) {
|
||||
menuEntry = (SwingEntry) menuEntries.get(0);
|
||||
}
|
||||
|
||||
if (menuEntry instanceof Status) {
|
||||
// set the text or delete...
|
||||
|
||||
if (statusText == null) {
|
||||
// delete
|
||||
remove(menuEntry);
|
||||
}
|
||||
else {
|
||||
// set text
|
||||
menuEntry.setText(statusText);
|
||||
}
|
||||
|
||||
} else {
|
||||
// create a new one
|
||||
menuEntry = new SwingEntryStatus(_this, statusText);
|
||||
// status is ALWAYS at 0 index...
|
||||
menuEntries.add(0, menuEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setShortcut(final char key) {
|
||||
if (_native instanceof JMenuItem) {
|
||||
// yikes...
|
||||
final int vKey = SystemTrayFixes.getVirtualKey(key);
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
((JMenuItem) _native).setMnemonic(vKey);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void remove() {
|
||||
dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
_native.setVisible(false);
|
||||
if (_native instanceof TrayPopup) {
|
||||
((TrayPopup) _native).close();
|
||||
}
|
||||
|
||||
SwingMenu parent = (SwingMenu) getParent();
|
||||
if (parent != null) {
|
||||
parent._native.remove(_native);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
30
src/dorkbox/systemTray/swingUI/SwingUI.java
Normal file
30
src/dorkbox/systemTray/swingUI/SwingUI.java
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2016 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.swingUI;
|
||||
|
||||
/**
|
||||
* Represents a System Tray or menu, that will have it's menu rendered via Swing.
|
||||
* <p>
|
||||
* This has the most standard L&F across all systems (as all systems will render this menu the exact same way), however the tradeoff is that
|
||||
* one loses the native L&F of the system (with the exception of Windows, whose native menu looks absolutely terrible).
|
||||
* <p>
|
||||
* Noticeable differences that are limitations for the NativeUI only:
|
||||
* - AppIndicator Status entries must be plain text (they are not bold as they are everywhere else).
|
||||
* - MacOS cannot have images in their menu or sub-menu's -- only plain text is possible
|
||||
*/
|
||||
public
|
||||
interface SwingUI
|
||||
{}
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.swing;
|
||||
package dorkbox.systemTray.swingUI;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Frame;
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.swing;
|
||||
package dorkbox.systemTray.swingUI;
|
||||
|
||||
import java.awt.MouseInfo;
|
||||
import java.awt.Point;
|
||||
@ -79,8 +79,9 @@ import dorkbox.util.SwingUtil;
|
||||
* http://bazaar.launchpad.net/~canonical-dx-team/ido/trunk/view/head:/src/idoentrymenuitem.c
|
||||
* http://bazaar.launchpad.net/~ubuntu-desktop/ido/gtk3/files
|
||||
*/
|
||||
@SuppressWarnings("Duplicates")
|
||||
public
|
||||
class _AppIndicatorTray extends MenuImpl {
|
||||
class _AppIndicatorTray extends SwingMenu {
|
||||
private volatile AppIndicatorInstanceStruct appIndicator;
|
||||
private boolean isActive = false;
|
||||
private final Runnable popupRunnable;
|
||||
@ -89,7 +90,9 @@ class _AppIndicatorTray extends MenuImpl {
|
||||
private AtomicBoolean shuttingDown = new AtomicBoolean();
|
||||
|
||||
// necessary to prevent GC on these objects
|
||||
@SuppressWarnings({"FieldCanBeLocal", "unused"})
|
||||
private NativeLong nativeLong;
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private GEventCallback gtkCallback;
|
||||
|
||||
|
||||
@ -107,12 +110,6 @@ class _AppIndicatorTray extends MenuImpl {
|
||||
_AppIndicatorTray(final SystemTray systemTray) {
|
||||
super(systemTray,null, new TrayPopup());
|
||||
|
||||
if (SystemTray.FORCE_TRAY_TYPE != 0 && SystemTray.FORCE_TRAY_TYPE != SystemTray.TYPE_APP_INDICATOR) {
|
||||
throw new IllegalArgumentException("Unable to start AppIndicator Tray if 'SystemTray.FORCE_TRAY_TYPE' does not match");
|
||||
}
|
||||
|
||||
ImageUtils.determineIconSize();
|
||||
|
||||
TrayPopup popupMenu = (TrayPopup) _native;
|
||||
popupMenu.pack();
|
||||
popupMenu.setFocusable(true);
|
||||
@ -198,11 +195,11 @@ class _AppIndicatorTray extends MenuImpl {
|
||||
AppIndicator.app_indicator_set_menu(appIndicator, dummyMenu);
|
||||
}
|
||||
|
||||
public
|
||||
public final
|
||||
void shutdown() {
|
||||
if (!shuttingDown.getAndSet(true)) {
|
||||
// must happen asap, so our hook properly notices we are in shutdown mode
|
||||
final AppIndicatorInstanceStruct savedAppI = appIndicator;
|
||||
final AppIndicatorInstanceStruct savedAppIndicator = appIndicator;
|
||||
appIndicator = null;
|
||||
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@ -210,8 +207,8 @@ class _AppIndicatorTray extends MenuImpl {
|
||||
public
|
||||
void run() {
|
||||
// STATUS_PASSIVE hides the indicator
|
||||
AppIndicator.app_indicator_set_status(savedAppI, AppIndicator.STATUS_PASSIVE);
|
||||
Pointer p = savedAppI.getPointer();
|
||||
AppIndicator.app_indicator_set_status(savedAppIndicator, AppIndicator.STATUS_PASSIVE);
|
||||
Pointer p = savedAppIndicator.getPointer();
|
||||
Gobject.g_object_unref(p);
|
||||
}
|
||||
});
|
||||
@ -224,7 +221,14 @@ class _AppIndicatorTray extends MenuImpl {
|
||||
}
|
||||
}
|
||||
|
||||
public
|
||||
@Override
|
||||
public final
|
||||
boolean hasImage() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setImage_(final File imageFile) {
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
@ -252,7 +256,8 @@ class _AppIndicatorTray extends MenuImpl {
|
||||
});
|
||||
}
|
||||
|
||||
public
|
||||
@Override
|
||||
public final
|
||||
void setEnabled(final boolean setEnabled) {
|
||||
visible = !setEnabled;
|
||||
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.swing;
|
||||
package dorkbox.systemTray.swingUI;
|
||||
|
||||
import java.awt.MouseInfo;
|
||||
import java.awt.Point;
|
||||
@ -32,7 +32,6 @@ import dorkbox.systemTray.linux.jna.GEventCallback;
|
||||
import dorkbox.systemTray.linux.jna.GdkEventButton;
|
||||
import dorkbox.systemTray.linux.jna.Gobject;
|
||||
import dorkbox.systemTray.linux.jna.Gtk;
|
||||
import dorkbox.systemTray.util.ImageUtils;
|
||||
|
||||
/**
|
||||
* Class for handling all system tray interactions via GTK.
|
||||
@ -40,8 +39,9 @@ import dorkbox.systemTray.util.ImageUtils;
|
||||
* This is the "old" way to do it, and does not work with some desktop environments. This is a hybrid class, because we want to show the
|
||||
* swing menu popup INSTEAD of GTK menu popups. The "golden standard" is our swing menu popup, since we have 100% control over it.
|
||||
*/
|
||||
@SuppressWarnings("Duplicates")
|
||||
public
|
||||
class _GtkStatusIconTray extends MenuImpl {
|
||||
class _GtkStatusIconTray extends SwingMenu {
|
||||
private volatile Pointer trayIcon;
|
||||
|
||||
// http://code.metager.de/source/xref/gnome/Platform/gtk%2B/gtk/deprecated/gtkstatusicon.c
|
||||
@ -63,12 +63,6 @@ class _GtkStatusIconTray extends MenuImpl {
|
||||
_GtkStatusIconTray(final SystemTray systemTray) {
|
||||
super(systemTray, null, new TrayPopup());
|
||||
|
||||
if (SystemTray.FORCE_TRAY_TYPE != 0 && SystemTray.FORCE_TRAY_TYPE != SystemTray.TYPE_GTK_STATUSICON) {
|
||||
throw new IllegalArgumentException("Unable to start GtkStatusIcon if 'SystemTray.FORCE_TRAY_TYPE' does not match");
|
||||
}
|
||||
|
||||
ImageUtils.determineIconSize();
|
||||
|
||||
JPopupMenu popupMenu = (JPopupMenu) _native;
|
||||
popupMenu.pack();
|
||||
popupMenu.setFocusable(true);
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.swing;
|
||||
package dorkbox.systemTray.swingUI;
|
||||
|
||||
import java.awt.AWTException;
|
||||
import java.awt.Image;
|
||||
@ -26,19 +26,17 @@ import java.io.File;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JPopupMenu;
|
||||
|
||||
import dorkbox.systemTray.util.ImageUtils;
|
||||
|
||||
/**
|
||||
* Class for handling all system tray interaction, via SWING.
|
||||
* Class for handling all system tray interaction, via Swing.
|
||||
*
|
||||
* It doesn't work well on linux. See bugs:
|
||||
* It doesn't work well AT ALL on linux. See bugs:
|
||||
* http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6267936
|
||||
* http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6453521
|
||||
* https://stackoverflow.com/questions/331407/java-trayicon-using-image-with-transparent-background/3882028#3882028
|
||||
*/
|
||||
@SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "WeakerAccess"})
|
||||
public
|
||||
class _SwingTray extends MenuImpl {
|
||||
class _SwingTray extends SwingMenu {
|
||||
private volatile SystemTray tray;
|
||||
private volatile TrayIcon trayIcon;
|
||||
|
||||
@ -50,12 +48,11 @@ class _SwingTray extends MenuImpl {
|
||||
_SwingTray(final dorkbox.systemTray.SystemTray systemTray) {
|
||||
super(systemTray, null, new TrayPopup());
|
||||
|
||||
if (dorkbox.systemTray.SystemTray.FORCE_TRAY_TYPE != 0 && dorkbox.systemTray.SystemTray.FORCE_TRAY_TYPE != dorkbox.systemTray.SystemTray.TYPE_SWING) {
|
||||
throw new IllegalArgumentException("Unable to start Swing SystemTray if 'SystemTray.FORCE_TRAY_TYPE' does not match");
|
||||
if (!SystemTray.isSupported()) {
|
||||
throw new RuntimeException("System Tray is not supported in this configuration! Please write an issue and include your OS " +
|
||||
"type and configuration");
|
||||
}
|
||||
|
||||
ImageUtils.determineIconSize();
|
||||
|
||||
_SwingTray.this.tray = SystemTray.getSystemTray();
|
||||
}
|
||||
|
||||
@ -65,10 +62,10 @@ class _SwingTray extends MenuImpl {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
tray.remove(trayIcon);
|
||||
|
||||
removeAll();
|
||||
remove();
|
||||
|
||||
tray.remove(trayIcon);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -118,6 +115,7 @@ class _SwingTray extends MenuImpl {
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
public
|
||||
void setEnabled(final boolean setEnabled) {
|
||||
visible = !setEnabled;
|
||||
@ -126,7 +124,6 @@ class _SwingTray extends MenuImpl {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
|
||||
if (visible && !setEnabled) {
|
||||
tray.remove(trayIcon);
|
||||
}
|
@ -13,246 +13,103 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.swing;
|
||||
package dorkbox.systemTray.util;
|
||||
|
||||
|
||||
import static dorkbox.systemTray.swing.EntryImpl.getVkKey;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JMenuItem;
|
||||
|
||||
import dorkbox.systemTray.Action;
|
||||
import dorkbox.systemTray.Entry;
|
||||
import dorkbox.systemTray.Menu;
|
||||
import dorkbox.systemTray.Separator;
|
||||
import dorkbox.systemTray.Status;
|
||||
import dorkbox.systemTray.SystemTray;
|
||||
import dorkbox.systemTray.util.ImageUtils;
|
||||
import dorkbox.util.SwingUtil;
|
||||
|
||||
// this is a weird composite class, because it must be a Menu, but ALSO a Entry -- so it has both
|
||||
@SuppressWarnings("ForLoopReplaceableByForEach")
|
||||
class MenuImpl implements Menu {
|
||||
static final AtomicInteger MENU_ID_COUNTER = new AtomicInteger();
|
||||
private final int id = MenuImpl.MENU_ID_COUNTER.getAndIncrement();
|
||||
public abstract
|
||||
class MenuBase implements Menu {
|
||||
public static final AtomicInteger MENU_ID_COUNTER = new AtomicInteger();
|
||||
private final int id = MenuBase.MENU_ID_COUNTER.getAndIncrement();
|
||||
|
||||
private final java.util.List<Entry> menuEntries = new ArrayList<Entry>();
|
||||
protected final java.util.List<Entry> menuEntries = new ArrayList<Entry>();
|
||||
|
||||
private final SystemTray systemTray;
|
||||
private final Menu parent;
|
||||
|
||||
// sub-menu = AdjustedJMenu
|
||||
// systemtray = TrayPopup
|
||||
volatile JComponent _native;
|
||||
|
||||
// this have to be volatile, because they can be changed from any thread
|
||||
private volatile String text;
|
||||
private volatile boolean hasLegitIcon = false;
|
||||
|
||||
/**
|
||||
* Called in the EDT
|
||||
* Called in the EDT/GTK dispatch threads
|
||||
*
|
||||
* @param systemTray the system tray (which is the object that sits in the system tray)
|
||||
* @param parent the parent of this menu, null if the parent is the system tray
|
||||
* @param _native the native element that represents this menu
|
||||
*/
|
||||
MenuImpl(final SystemTray systemTray, final Menu parent, final JComponent _native) {
|
||||
public
|
||||
MenuBase(final SystemTray systemTray, final Menu parent) {
|
||||
this.systemTray = systemTray;
|
||||
this.parent = parent;
|
||||
this._native = _native;
|
||||
}
|
||||
|
||||
void dispatch(final Runnable runnable) {
|
||||
// this will properly check if we are running on the EDT
|
||||
SwingUtil.invokeLater(runnable);
|
||||
}
|
||||
|
||||
void dispatchAndWait(final Runnable runnable) {
|
||||
// this will properly check if we are running on the EDT
|
||||
try {
|
||||
SwingUtil.invokeAndWait(runnable);
|
||||
} catch (Exception e) {
|
||||
SystemTray.logger.error("Error processing event on the dispatch thread.", e);
|
||||
}
|
||||
}
|
||||
|
||||
// always called in the EDT
|
||||
private
|
||||
void renderText(final String text) {
|
||||
((JMenuItem) _native).setText(text);
|
||||
}
|
||||
protected abstract
|
||||
void dispatch(final Runnable runnable);
|
||||
|
||||
protected abstract
|
||||
void dispatchAndWait(final Runnable runnable);
|
||||
|
||||
|
||||
/**
|
||||
* Will add a new menu entry, or update one if it already exists
|
||||
* NOT ALWAYS CALLED ON EDT
|
||||
* NOT ALWAYS CALLED ON DISPATCH
|
||||
*/
|
||||
private
|
||||
Entry addEntry_(final String menuText, final File imagePath, final Action callback) {
|
||||
if (menuText == null) {
|
||||
throw new NullPointerException("Menu text cannot be null");
|
||||
}
|
||||
|
||||
final AtomicReference<Entry> value = new AtomicReference<Entry>();
|
||||
|
||||
dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
synchronized (menuEntries) {
|
||||
Entry entry = get(menuText);
|
||||
|
||||
if (entry == null) {
|
||||
// must always be called on the EDT
|
||||
entry = new EntryItem(MenuImpl.this, callback);
|
||||
entry.setText(menuText);
|
||||
entry.setImage(imagePath);
|
||||
|
||||
menuEntries.add(entry);
|
||||
} else if (entry instanceof EntryItem) {
|
||||
entry.setText(menuText);
|
||||
entry.setImage(imagePath);
|
||||
}
|
||||
|
||||
value.set(entry);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return value.get();
|
||||
}
|
||||
protected abstract
|
||||
Entry addEntry_(final String menuText, final File imagePath, final Action callback);
|
||||
|
||||
/**
|
||||
* Will add a new sub-menu entry, or update one if it already exists
|
||||
* NOT ALWAYS CALLED ON EDT
|
||||
* NOT ALWAYS CALLED ON DISPATCH
|
||||
*/
|
||||
private
|
||||
Menu addMenu_(final String menuText, final File imagePath) {
|
||||
if (menuText == null) {
|
||||
throw new NullPointerException("Menu text cannot be null");
|
||||
}
|
||||
|
||||
final AtomicReference<Menu> value = new AtomicReference<Menu>();
|
||||
|
||||
dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
synchronized (menuEntries) {
|
||||
Entry entry = get(menuText);
|
||||
|
||||
if (entry == null) {
|
||||
// must always be called on the EDT
|
||||
entry = new MenuImpl(getSystemTray(), MenuImpl.this, new AdjustedJMenu());
|
||||
_native.add(((MenuImpl) entry)._native); // have to add it separately
|
||||
|
||||
entry.setText(menuText);
|
||||
entry.setImage(imagePath);
|
||||
value.set((Menu) entry);
|
||||
|
||||
} else if (entry instanceof MenuImpl) {
|
||||
entry.setText(menuText);
|
||||
entry.setImage(imagePath);
|
||||
}
|
||||
|
||||
menuEntries.add(entry);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return value.get();
|
||||
}
|
||||
|
||||
protected abstract
|
||||
Menu addMenu_(final String menuText, final File imagePath);
|
||||
|
||||
|
||||
// public here so that Swing/Gtk/AppIndicator can override this
|
||||
public
|
||||
void setImage_(final File imageFile) {
|
||||
hasLegitIcon = imageFile != null;
|
||||
protected abstract
|
||||
void setImage_(final File imageFile);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
if (imageFile != null) {
|
||||
ImageIcon origIcon = new ImageIcon(imageFile.getAbsolutePath());
|
||||
((JMenuItem) _native).setIcon(origIcon);
|
||||
}
|
||||
else {
|
||||
((JMenuItem) _native).setIcon(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public
|
||||
public final
|
||||
Menu getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public
|
||||
@Override
|
||||
public final
|
||||
SystemTray getSystemTray() {
|
||||
return systemTray;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean hasImage() {
|
||||
return hasLegitIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables, or disables the sub-menu entry.
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
void setEnabled(final boolean enabled) {
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
_native.setEnabled(enabled);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* NOT ALWAYS CALLED ON EDT
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
void addSeparator() {
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
// public here so that Swing/Gtk/AppIndicator can access this
|
||||
public final
|
||||
String getStatus() {
|
||||
synchronized (menuEntries) {
|
||||
synchronized (menuEntries) {
|
||||
Entry entry = new EntrySeparator(MenuImpl.this);
|
||||
menuEntries.add(entry);
|
||||
Entry entry = menuEntries.get(0);
|
||||
if (entry instanceof Status) {
|
||||
return entry.getText();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: buggy. The menu will **sometimes** stop responding to the "enter" key after this. Mnemonics still work however.
|
||||
@ -281,7 +138,8 @@ class MenuImpl implements Menu {
|
||||
// }
|
||||
|
||||
|
||||
public
|
||||
@Override
|
||||
public final
|
||||
Entry get(final String menuText) {
|
||||
if (menuText == null || menuText.isEmpty()) {
|
||||
return null;
|
||||
@ -291,6 +149,11 @@ class MenuImpl implements Menu {
|
||||
synchronized (menuEntries) {
|
||||
for (int i = 0, menuEntriesSize = menuEntries.size(); i < menuEntriesSize; i++) {
|
||||
final Entry entry = menuEntries.get(i);
|
||||
|
||||
if (entry instanceof Separator || entry instanceof Status) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String text = entry.getText();
|
||||
|
||||
// text can be null
|
||||
@ -304,13 +167,15 @@ class MenuImpl implements Menu {
|
||||
}
|
||||
|
||||
// ignores status + separators
|
||||
public
|
||||
@Override
|
||||
public final
|
||||
Entry getFirst() {
|
||||
return get(0);
|
||||
}
|
||||
|
||||
// ignores status + separators
|
||||
public
|
||||
@Override
|
||||
public final
|
||||
Entry getLast() {
|
||||
// Must be wrapped in a synchronized block for object visibility
|
||||
synchronized (menuEntries) {
|
||||
@ -319,7 +184,7 @@ class MenuImpl implements Menu {
|
||||
for (int i = menuEntries.size()-1; i >= 0; i--) {
|
||||
entry = menuEntries.get(i);
|
||||
|
||||
if (!(entry instanceof dorkbox.systemTray.Separator || entry instanceof Status)) {
|
||||
if (!(entry instanceof Separator || entry instanceof Status)) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
@ -330,7 +195,8 @@ class MenuImpl implements Menu {
|
||||
}
|
||||
|
||||
// ignores status + separators
|
||||
public
|
||||
@Override
|
||||
public final
|
||||
Entry get(final int menuIndex) {
|
||||
if (menuIndex < 0) {
|
||||
return null;
|
||||
@ -341,7 +207,7 @@ class MenuImpl implements Menu {
|
||||
if (!menuEntries.isEmpty()) {
|
||||
int count = 0;
|
||||
for (Entry entry : menuEntries) {
|
||||
if (entry instanceof dorkbox.systemTray.Separator || entry instanceof Status) {
|
||||
if (entry instanceof Separator || entry instanceof Status) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -357,13 +223,14 @@ class MenuImpl implements Menu {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public
|
||||
@Override
|
||||
public final
|
||||
Entry addEntry(String menuText, Action callback) {
|
||||
return addEntry(menuText, (String) null, callback);
|
||||
}
|
||||
|
||||
public
|
||||
@Override
|
||||
public final
|
||||
Entry addEntry(String menuText, String imagePath, Action callback) {
|
||||
if (imagePath == null) {
|
||||
return addEntry_(menuText, null, callback);
|
||||
@ -373,7 +240,8 @@ class MenuImpl implements Menu {
|
||||
}
|
||||
}
|
||||
|
||||
public
|
||||
@Override
|
||||
public final
|
||||
Entry addEntry(String menuText, URL imageUrl, Action callback) {
|
||||
if (imageUrl == null) {
|
||||
return addEntry_(menuText, null, callback);
|
||||
@ -383,7 +251,8 @@ class MenuImpl implements Menu {
|
||||
}
|
||||
}
|
||||
|
||||
public
|
||||
@Override
|
||||
public final
|
||||
Entry addEntry(String menuText, String cacheName, InputStream imageStream, Action callback) {
|
||||
if (imageStream == null) {
|
||||
return addEntry_(menuText, null, callback);
|
||||
@ -393,7 +262,8 @@ class MenuImpl implements Menu {
|
||||
}
|
||||
}
|
||||
|
||||
public
|
||||
@Override
|
||||
public final
|
||||
Entry addEntry(String menuText, InputStream imageStream, Action callback) {
|
||||
if (imageStream == null) {
|
||||
return addEntry_(menuText, null, callback);
|
||||
@ -407,13 +277,14 @@ class MenuImpl implements Menu {
|
||||
|
||||
|
||||
|
||||
|
||||
public
|
||||
@Override
|
||||
public final
|
||||
Menu addMenu(String menuText) {
|
||||
return addMenu(menuText, (String) null);
|
||||
}
|
||||
|
||||
public
|
||||
@Override
|
||||
public final
|
||||
Menu addMenu(String menuText, String imagePath) {
|
||||
if (imagePath == null) {
|
||||
return addMenu_(menuText, null);
|
||||
@ -423,7 +294,8 @@ class MenuImpl implements Menu {
|
||||
}
|
||||
}
|
||||
|
||||
public
|
||||
@Override
|
||||
public final
|
||||
Menu addMenu(String menuText, URL imageUrl) {
|
||||
if (imageUrl == null) {
|
||||
return addMenu_(menuText, null);
|
||||
@ -433,7 +305,8 @@ class MenuImpl implements Menu {
|
||||
}
|
||||
}
|
||||
|
||||
public
|
||||
@Override
|
||||
public final
|
||||
Menu addMenu(String menuText, String cacheName, InputStream imageStream) {
|
||||
if (imageStream == null) {
|
||||
return addMenu_(menuText, null);
|
||||
@ -443,7 +316,8 @@ class MenuImpl implements Menu {
|
||||
}
|
||||
}
|
||||
|
||||
public
|
||||
@Override
|
||||
public final
|
||||
Menu addMenu(String menuText, InputStream imageStream) {
|
||||
if (imageStream == null) {
|
||||
return addMenu_(menuText, null);
|
||||
@ -511,97 +385,16 @@ class MenuImpl implements Menu {
|
||||
}
|
||||
}
|
||||
|
||||
public
|
||||
String getStatus() {
|
||||
synchronized (menuEntries) {
|
||||
Entry entry = menuEntries.get(0);
|
||||
if (entry instanceof EntryStatus) {
|
||||
return entry.getText();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public
|
||||
void setStatus(final String statusText) {
|
||||
final MenuImpl _this = this;
|
||||
dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
synchronized (menuEntries) {
|
||||
// status is ALWAYS at 0 index...
|
||||
EntryImpl menuEntry = null;
|
||||
if (!menuEntries.isEmpty()) {
|
||||
menuEntry = (EntryImpl) menuEntries.get(0);
|
||||
}
|
||||
|
||||
if (menuEntry instanceof EntryStatus) {
|
||||
// set the text or delete...
|
||||
|
||||
if (statusText == null) {
|
||||
// delete
|
||||
remove(menuEntry);
|
||||
}
|
||||
else {
|
||||
// set text
|
||||
menuEntry.setText(statusText);
|
||||
}
|
||||
|
||||
} else {
|
||||
// create a new one
|
||||
menuEntry = new EntryStatus(_this, statusText);
|
||||
// status is ALWAYS at 0 index...
|
||||
menuEntries.add(0, menuEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public
|
||||
String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setText(final String newText) {
|
||||
text = newText;
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
renderText(newText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
public final
|
||||
void setCallback(final Action callback) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setShortcut(final char key) {
|
||||
if (_native instanceof JMenuItem) {
|
||||
// yikes...
|
||||
final int vKey = getVkKey(key);
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
((JMenuItem) _native).setMnemonic(vKey);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -628,7 +421,8 @@ class MenuImpl implements Menu {
|
||||
*
|
||||
* @param entry This is the menu entry to remove
|
||||
*/
|
||||
public
|
||||
@Override
|
||||
public final
|
||||
void remove(final Entry entry) {
|
||||
if (entry == null) {
|
||||
throw new NullPointerException("No menu entry exists for entry");
|
||||
@ -649,7 +443,7 @@ class MenuImpl implements Menu {
|
||||
* @param menu This is the menu entry to remove
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
public final
|
||||
void remove(final Menu menu) {
|
||||
final Menu parent = getParent();
|
||||
if (parent == null) {
|
||||
@ -668,14 +462,14 @@ class MenuImpl implements Menu {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
((MenuImpl) parent).remove__(_this);
|
||||
((MenuBase) parent).remove__(_this);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// NOT ALWAYS CALLED ON EDT
|
||||
private
|
||||
protected
|
||||
void remove__(final Object menuEntry) {
|
||||
try {
|
||||
synchronized (menuEntries) {
|
||||
@ -715,7 +509,7 @@ class MenuImpl implements Menu {
|
||||
*
|
||||
* @param menuText This is the label for the menu entry or sub-menu to remove
|
||||
*/
|
||||
public
|
||||
public final
|
||||
void remove(final String menuText) {
|
||||
dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
@ -733,28 +527,28 @@ class MenuImpl implements Menu {
|
||||
}
|
||||
|
||||
|
||||
// @Override
|
||||
// public final
|
||||
// void remove() {
|
||||
// dispatchAndWait(new Runnable() {
|
||||
// @Override
|
||||
// public
|
||||
// void run() {
|
||||
// _native.setVisible(false);
|
||||
// if (_native instanceof TrayPopup) {
|
||||
// ((TrayPopup) _native).close();
|
||||
// }
|
||||
//
|
||||
// MenuBase parent = (MenuBase) getParent();
|
||||
// if (parent != null) {
|
||||
// parent._native.remove(_native);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
@Override
|
||||
public final
|
||||
void remove() {
|
||||
dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
_native.setVisible(false);
|
||||
if (_native instanceof TrayPopup) {
|
||||
((TrayPopup) _native).close();
|
||||
}
|
||||
|
||||
MenuImpl parent = (MenuImpl) getParent();
|
||||
if (parent != null) {
|
||||
parent._native.remove(_native);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void removeAll() {
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
@ -762,7 +556,7 @@ class MenuImpl implements Menu {
|
||||
void run() {
|
||||
synchronized (menuEntries) {
|
||||
// have to make copy because we are deleting all of them, and sub-menus remove themselves from parents
|
||||
ArrayList<Entry> menuEntriesCopy = new ArrayList<Entry>(MenuImpl.this.menuEntries);
|
||||
ArrayList<Entry> menuEntriesCopy = new ArrayList<Entry>(MenuBase.this.menuEntries);
|
||||
for (Entry entry : menuEntriesCopy) {
|
||||
entry.remove();
|
||||
}
|
||||
@ -792,7 +586,7 @@ class MenuImpl implements Menu {
|
||||
return false;
|
||||
}
|
||||
|
||||
MenuImpl other = (MenuImpl) obj;
|
||||
MenuBase other = (MenuBase) obj;
|
||||
return this.id == other.id;
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ package dorkbox.systemTray.util;
|
||||
import static dorkbox.systemTray.SystemTray.logger;
|
||||
|
||||
import java.awt.Robot;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.Locale;
|
||||
|
||||
import dorkbox.systemTray.SystemTray;
|
||||
@ -31,11 +32,11 @@ import javassist.CtMethod;
|
||||
* Fixes issues with some java runtimes
|
||||
*/
|
||||
public
|
||||
class WindowsSystemTraySwing {
|
||||
class SystemTrayFixes {
|
||||
|
||||
// oh my. Java likes to think that ALL windows tray icons are 16x16.... Lets fix that!
|
||||
public static void fix() {
|
||||
// if we are using swing (in windows only) the icon size is usually incorrect. Here we have to fix that.
|
||||
// https://stackoverflow.com/questions/16378886/java-trayicon-right-click-disabled-on-mac-osx/35919788#35919788
|
||||
public static void fixWindows() {
|
||||
if (!OS.isWindows()) {
|
||||
return;
|
||||
}
|
||||
@ -61,7 +62,7 @@ class WindowsSystemTraySwing {
|
||||
(null != m.invoke(cl, "java.awt.SystemTray"));
|
||||
} catch (Throwable e) {
|
||||
if (SystemTray.DEBUG) {
|
||||
logger.debug("Error detecting javaFX/SWT mode", e);
|
||||
logger.debug("Error detecting if the Swing SystemTray is loaded", e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -200,4 +201,152 @@ class WindowsSystemTraySwing {
|
||||
logger.error("Error setting tray icon size to: {}", ImageUtils.TRAY_SIZE, e);
|
||||
}
|
||||
}
|
||||
|
||||
// MacOS AWT is hardcoded to respond only to "popup trigger" for menus, where it should be any mouse button
|
||||
// https://stackoverflow.com/questions/16378886/java-trayicon-right-click-disabled-on-mac-osx/35919788#35919788
|
||||
// https://bugs.openjdk.java.net/browse/JDK-7158615
|
||||
public static void fixMacOS() {
|
||||
if (!OS.isWindows()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String vendor = System.getProperty("java.vendor").toLowerCase(Locale.US);
|
||||
// spaces at the end to make sure we check for words
|
||||
if (!(vendor.contains("sun ") || vendor.contains("oracle "))) {
|
||||
// not fixing things that are not broken.
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
boolean isMacSwingTrayLoaded = false;
|
||||
|
||||
try {
|
||||
// this is important to use reflection, because if JavaFX is not being used, calling getToolkit() will initialize it...
|
||||
java.lang.reflect.Method m = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
|
||||
m.setAccessible(true);
|
||||
ClassLoader cl = ClassLoader.getSystemClassLoader();
|
||||
|
||||
// if we are using swing (in windows only) the icon size is usually incorrect. We cannot fix that if it's already loaded.
|
||||
isMacSwingTrayLoaded = (null != m.invoke(cl, "sun.lwawt.macosx.CTrayIcon")) ||
|
||||
(null != m.invoke(cl, "java.awt.SystemTray"));
|
||||
} catch (Throwable e) {
|
||||
if (SystemTray.DEBUG) {
|
||||
logger.debug("Error detecting if the MacOS SystemTray is loaded", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (isMacSwingTrayLoaded) {
|
||||
throw new RuntimeException("Unable to initialize the swing tray in windows, it has already been created!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a key character into it's corresponding VK entry
|
||||
*/
|
||||
public static
|
||||
int getVirtualKey(final char key) {
|
||||
switch (key) {
|
||||
case 0x08: return KeyEvent.VK_BACK_SPACE;
|
||||
case 0x09: return KeyEvent.VK_TAB;
|
||||
case 0x0a: return KeyEvent.VK_ENTER;
|
||||
case 0x1B: return KeyEvent.VK_ESCAPE;
|
||||
case 0x20AC: return KeyEvent.VK_EURO_SIGN;
|
||||
case 0x20: return KeyEvent.VK_SPACE;
|
||||
case 0x21: return KeyEvent.VK_EXCLAMATION_MARK;
|
||||
case 0x22: return KeyEvent.VK_QUOTEDBL;
|
||||
case 0x23: return KeyEvent.VK_NUMBER_SIGN;
|
||||
case 0x24: return KeyEvent.VK_DOLLAR;
|
||||
case 0x26: return KeyEvent.VK_AMPERSAND;
|
||||
case 0x27: return KeyEvent.VK_QUOTE;
|
||||
case 0x28: return KeyEvent.VK_LEFT_PARENTHESIS;
|
||||
case 0x29: return KeyEvent.VK_RIGHT_PARENTHESIS;
|
||||
case 0x2A: return KeyEvent.VK_ASTERISK;
|
||||
case 0x2B: return KeyEvent.VK_PLUS;
|
||||
case 0x2C: return KeyEvent.VK_COMMA;
|
||||
case 0x2D: return KeyEvent.VK_MINUS;
|
||||
case 0x2E: return KeyEvent.VK_PERIOD;
|
||||
case 0x2F: return KeyEvent.VK_SLASH;
|
||||
case 0x30: return KeyEvent.VK_0;
|
||||
case 0x31: return KeyEvent.VK_1;
|
||||
case 0x32: return KeyEvent.VK_2;
|
||||
case 0x33: return KeyEvent.VK_3;
|
||||
case 0x34: return KeyEvent.VK_4;
|
||||
case 0x35: return KeyEvent.VK_5;
|
||||
case 0x36: return KeyEvent.VK_6;
|
||||
case 0x37: return KeyEvent.VK_7;
|
||||
case 0x38: return KeyEvent.VK_8;
|
||||
case 0x39: return KeyEvent.VK_9;
|
||||
case 0x3A: return KeyEvent.VK_COLON;
|
||||
case 0x3B: return KeyEvent.VK_SEMICOLON;
|
||||
case 0x3C: return KeyEvent.VK_LESS;
|
||||
case 0x3D: return KeyEvent.VK_EQUALS;
|
||||
case 0x3E: return KeyEvent.VK_GREATER;
|
||||
case 0x40: return KeyEvent.VK_AT;
|
||||
case 0x41: return KeyEvent.VK_A;
|
||||
case 0x42: return KeyEvent.VK_B;
|
||||
case 0x43: return KeyEvent.VK_C;
|
||||
case 0x44: return KeyEvent.VK_D;
|
||||
case 0x45: return KeyEvent.VK_E;
|
||||
case 0x46: return KeyEvent.VK_F;
|
||||
case 0x47: return KeyEvent.VK_G;
|
||||
case 0x48: return KeyEvent.VK_H;
|
||||
case 0x49: return KeyEvent.VK_I;
|
||||
case 0x4A: return KeyEvent.VK_J;
|
||||
case 0x4B: return KeyEvent.VK_K;
|
||||
case 0x4C: return KeyEvent.VK_L;
|
||||
case 0x4D: return KeyEvent.VK_M;
|
||||
case 0x4E: return KeyEvent.VK_N;
|
||||
case 0x4F: return KeyEvent.VK_O;
|
||||
case 0x50: return KeyEvent.VK_P;
|
||||
case 0x51: return KeyEvent.VK_Q;
|
||||
case 0x52: return KeyEvent.VK_R;
|
||||
case 0x53: return KeyEvent.VK_S;
|
||||
case 0x54: return KeyEvent.VK_T;
|
||||
case 0x55: return KeyEvent.VK_U;
|
||||
case 0x56: return KeyEvent.VK_V;
|
||||
case 0x57: return KeyEvent.VK_W;
|
||||
case 0x58: return KeyEvent.VK_X;
|
||||
case 0x59: return KeyEvent.VK_Y;
|
||||
case 0x5A: return KeyEvent.VK_Z;
|
||||
case 0x5B: return KeyEvent.VK_OPEN_BRACKET;
|
||||
case 0x5C: return KeyEvent.VK_BACK_SLASH;
|
||||
case 0x5D: return KeyEvent.VK_CLOSE_BRACKET;
|
||||
case 0x5E: return KeyEvent.VK_CIRCUMFLEX;
|
||||
case 0x5F: return KeyEvent.VK_UNDERSCORE;
|
||||
case 0x60: return KeyEvent.VK_BACK_QUOTE;
|
||||
case 0x61: return KeyEvent.VK_A;
|
||||
case 0x62: return KeyEvent.VK_B;
|
||||
case 0x63: return KeyEvent.VK_C;
|
||||
case 0x64: return KeyEvent.VK_D;
|
||||
case 0x65: return KeyEvent.VK_E;
|
||||
case 0x66: return KeyEvent.VK_F;
|
||||
case 0x67: return KeyEvent.VK_G;
|
||||
case 0x68: return KeyEvent.VK_H;
|
||||
case 0x69: return KeyEvent.VK_I;
|
||||
case 0x6A: return KeyEvent.VK_J;
|
||||
case 0x6B: return KeyEvent.VK_K;
|
||||
case 0x6C: return KeyEvent.VK_L;
|
||||
case 0x6D: return KeyEvent.VK_M;
|
||||
case 0x6E: return KeyEvent.VK_N;
|
||||
case 0x6F: return KeyEvent.VK_O;
|
||||
case 0x70: return KeyEvent.VK_P;
|
||||
case 0x71: return KeyEvent.VK_Q;
|
||||
case 0x72: return KeyEvent.VK_R;
|
||||
case 0x73: return KeyEvent.VK_S;
|
||||
case 0x74: return KeyEvent.VK_T;
|
||||
case 0x75: return KeyEvent.VK_U;
|
||||
case 0x76: return KeyEvent.VK_V;
|
||||
case 0x77: return KeyEvent.VK_W;
|
||||
case 0x78: return KeyEvent.VK_X;
|
||||
case 0x79: return KeyEvent.VK_Y;
|
||||
case 0x7A: return KeyEvent.VK_Z;
|
||||
case 0x7B: return KeyEvent.VK_BRACELEFT;
|
||||
case 0x7D: return KeyEvent.VK_BRACERIGHT;
|
||||
case 0x7F: return KeyEvent.VK_DELETE;
|
||||
case 0xA1: return KeyEvent.VK_INVERTED_EXCLAMATION_MARK;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user