Clean API

This commit is contained in:
nathan 2016-10-10 00:26:52 +02:00
parent 68a9e4edd7
commit 53a9110605
20 changed files with 959 additions and 839 deletions

View File

@ -24,7 +24,7 @@ import java.net.URL;
* This represents a common menu-entry, that is cross platform in nature * This represents a common menu-entry, that is cross platform in nature
*/ */
public public
interface MenuEntry { interface Entry {
/** /**
* @return the menu that contains this menu entry * @return the menu that contains this menu entry

View File

@ -15,54 +15,24 @@
*/ */
package dorkbox.systemTray; package dorkbox.systemTray;
import java.io.File;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicInteger;
import dorkbox.systemTray.util.ImageUtils;
/** /**
* Represents a cross-platform menu that is displayed by the tray-icon or as a sub-menu * Represents a cross-platform menu that is displayed by the tray-icon or as a sub-menu
*/ */
@SuppressWarnings({"WeakerAccess", "unused"}) public
public abstract interface Menu extends Entry {
class Menu implements MenuEntry {
public static final AtomicInteger MENU_ID_COUNTER = new AtomicInteger();
private final int id = Menu.MENU_ID_COUNTER.getAndIncrement();
protected final java.util.List<MenuEntry> menuEntries = new ArrayList<MenuEntry>();
private final SystemTray systemTray;
private final Menu parent;
/**
* @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
*/
public
Menu(final SystemTray systemTray, final Menu parent) {
this.systemTray = systemTray;
this.parent = parent;
}
/** /**
* @return the parent menu (of this menu) or null if we are the root menu * @return the parent menu (of this menu) or null if we are the root menu
*/ */
public Menu getParent();
Menu getParent() {
return parent;
}
/** /**
* @return the system tray that this menu is ultimately attached to * @return the system tray that this menu is ultimately attached to
*/ */
public SystemTray getSystemTray();
SystemTray getSystemTray() {
return systemTray;
}
/** /**
* Adds a spacer to the dropdown menu. When menu entries are removed, any menu spacer that ends up at the top/bottom of the drop-down * Adds a spacer to the dropdown menu. When menu entries are removed, any menu spacer that ends up at the top/bottom of the drop-down
@ -76,127 +46,37 @@ class Menu implements MenuEntry {
* <Spacer> (deleted) * <Spacer> (deleted)
* Entry3 (deleted) * Entry3 (deleted)
*/ */
public abstract
void addSeparator(); void addSeparator();
/*
* Will add a new menu entry, or update one if it already exists
*/
protected abstract
MenuEntry addEntry_(final String menuText, final File imagePath, final SystemTrayMenuAction callback);
/*
* Will add a new sub-menu entry, or update one if it already exists
*/
protected abstract
Menu addMenu_(final String menuText, final File imagePath);
/*
* Necessary to guarantee all updates occur on the dispatch thread
*/
protected abstract
void dispatch(Runnable runnable);
/*
* Necessary to guarantee all updates occur on the dispatch thread
*/
protected abstract
void dispatchAndWait(Runnable runnable);
/** /**
* Enables, or disables the sub-menu entry. * This removes al menu entries from this menu
*/ */
public abstract void clear();
void setEnabled(final boolean enabled);
/** /**
* Gets the menu entry for a specified text * Gets the menu entry for a specified text
* *
* @param menuText the menu entry text to use to find the menu entry. The first result found is returned * @param menuText the menu entry text to use to find the menu entry. The first result found is returned
*/ */
public Entry get(final String menuText);
MenuEntry get(final String menuText) {
if (menuText == null || menuText.isEmpty()) {
return null;
}
// Must be wrapped in a synchronized block for object visibility
synchronized (menuEntries) {
for (MenuEntry entry : menuEntries) {
String text = entry.getText();
// text can be null
if (menuText.equals(text)) {
return entry;
}
}
}
return null;
}
/** /**
* Gets the first menu entry or sub-menu, ignoring status and spacers * Gets the first menu entry or sub-menu, ignoring status and spacers
*/ */
public Entry getFirst();
MenuEntry getFirst() {
return get(0);
}
/** /**
* Gets the last menu entry or sub-menu, ignoring status and spacers * Gets the last menu entry or sub-menu, ignoring status and spacers
*/ */
public Entry getLast();
MenuEntry getLast() {
// Must be wrapped in a synchronized block for object visibility
synchronized (menuEntries) {
if (!menuEntries.isEmpty()) {
MenuEntry menuEntry = null;
for (int i = 0, menuEntriesSize = menuEntries.size(); i < menuEntriesSize; i++) {
menuEntry = menuEntries.get(i);
}
if (!(menuEntry instanceof MenuSpacer || menuEntry instanceof MenuStatus)) {
return menuEntry;
}
}
}
return null;
}
/** /**
* Gets the menu entry or sub-menu for a specified index (zero-index), ignoring status and spacers * Gets the menu entry or sub-menu for a specified index (zero-index), ignoring status and spacers
* *
* @param menuIndex the menu entry index to use to retrieve the menu entry. * @param menuIndex the menu entry index to use to retrieve the menu entry.
*/ */
public Entry get(final int menuIndex);
MenuEntry get(final int menuIndex) {
if (menuIndex < 0) {
return null;
}
// Must be wrapped in a synchronized block for object visibility
synchronized (menuEntries) {
if (!menuEntries.isEmpty()) {
int count = 0;
for (MenuEntry menuEntry : menuEntries) {
if (menuEntry instanceof MenuSpacer || menuEntry instanceof MenuStatus) {
continue;
}
if (count == menuIndex) {
return menuEntry;
}
count++;
}
}
}
return null;
}
@ -206,10 +86,7 @@ class Menu implements MenuEntry {
* @param menuText string of the text you want to appear * @param menuText string of the text you want to appear
* @param callback callback that will be executed when this menu entry is clicked * @param callback callback that will be executed when this menu entry is clicked
*/ */
public Entry addEntry(String menuText, SystemTrayMenuAction callback);
MenuEntry addEntry(String menuText, SystemTrayMenuAction callback) {
return addEntry(menuText, (String) null, callback);
}
/** /**
* Adds a menu entry with text + image * Adds a menu entry with text + image
@ -218,15 +95,7 @@ class Menu implements MenuEntry {
* @param imagePath the image (full path required) to use. If null, no image will be used * @param imagePath the image (full path required) to use. If null, no image will be used
* @param callback callback that will be executed when this menu entry is clicked * @param callback callback that will be executed when this menu entry is clicked
*/ */
public Entry addEntry(String menuText, String imagePath, SystemTrayMenuAction callback);
MenuEntry addEntry(String menuText, String imagePath, SystemTrayMenuAction callback) {
if (imagePath == null) {
return addEntry_(menuText, null, callback);
}
else {
return addEntry_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imagePath), callback);
}
}
/** /**
* Adds a menu entry with text + image * Adds a menu entry with text + image
@ -235,15 +104,7 @@ class Menu implements MenuEntry {
* @param imageUrl the URL of the image to use. If null, no image will be used * @param imageUrl the URL of the image to use. If null, no image will be used
* @param callback callback that will be executed when this menu entry is clicked * @param callback callback that will be executed when this menu entry is clicked
*/ */
public Entry addEntry(String menuText, URL imageUrl, SystemTrayMenuAction callback);
MenuEntry addEntry(String menuText, URL imageUrl, SystemTrayMenuAction callback) {
if (imageUrl == null) {
return addEntry_(menuText, null, callback);
}
else {
return addEntry_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageUrl), callback);
}
}
/** /**
* Adds a menu entry with text + image * Adds a menu entry with text + image
@ -253,15 +114,7 @@ class Menu implements MenuEntry {
* @param imageStream the InputStream of the image to use. If null, no image will be used * @param imageStream the InputStream of the image to use. If null, no image will be used
* @param callback callback that will be executed when this menu entry is clicked * @param callback callback that will be executed when this menu entry is clicked
*/ */
public Entry addEntry(String menuText, String cacheName, InputStream imageStream, SystemTrayMenuAction callback);
MenuEntry addEntry(String menuText, String cacheName, InputStream imageStream, SystemTrayMenuAction callback) {
if (imageStream == null) {
return addEntry_(menuText, null, callback);
}
else {
return addEntry_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, cacheName, imageStream), callback);
}
}
/** /**
* Adds a menu entry with text + image * Adds a menu entry with text + image
@ -270,15 +123,7 @@ class Menu implements MenuEntry {
* @param imageStream the InputStream of the image to use. If null, no image will be used * @param imageStream the InputStream of the image to use. If null, no image will be used
* @param callback callback that will be executed when this menu entry is clicked * @param callback callback that will be executed when this menu entry is clicked
*/ */
public Entry addEntry(String menuText, InputStream imageStream, SystemTrayMenuAction callback);
MenuEntry addEntry(String menuText, InputStream imageStream, SystemTrayMenuAction callback) {
if (imageStream == null) {
return addEntry_(menuText, null, callback);
}
else {
return addEntry_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageStream), callback);
}
}
@ -288,10 +133,7 @@ class Menu implements MenuEntry {
* *
* @param menuText string of the text you want to appear * @param menuText string of the text you want to appear
*/ */
public Menu addMenu(String menuText);
Menu addMenu(String menuText) {
return addMenu(menuText, (String) null);
}
/** /**
* Adds a sub-menu entry with text + image * Adds a sub-menu entry with text + image
@ -299,15 +141,7 @@ class Menu implements MenuEntry {
* @param menuText string of the text you want to appear * @param menuText string of the text you want to appear
* @param imagePath the image (full path required) to use. If null, no image will be used * @param imagePath the image (full path required) to use. If null, no image will be used
*/ */
public Menu addMenu(String menuText, String imagePath);
Menu addMenu(String menuText, String imagePath) {
if (imagePath == null) {
return addMenu_(menuText, null);
}
else {
return addMenu_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imagePath));
}
}
/** /**
* Adds a sub-menu entry with text + image * Adds a sub-menu entry with text + image
@ -315,15 +149,7 @@ class Menu implements MenuEntry {
* @param menuText string of the text you want to appear * @param menuText string of the text you want to appear
* @param imageUrl the URL of the image to use. If null, no image will be used * @param imageUrl the URL of the image to use. If null, no image will be used
*/ */
public Menu addMenu(String menuText, URL imageUrl);
Menu addMenu(String menuText, URL imageUrl) {
if (imageUrl == null) {
return addMenu_(menuText, null);
}
else {
return addMenu_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageUrl));
}
}
/** /**
* Adds a sub-menu entry with text + image * Adds a sub-menu entry with text + image
@ -332,15 +158,7 @@ class Menu implements MenuEntry {
* @param cacheName @param cacheName the name to use for lookup in the cache for the imageStream * @param cacheName @param cacheName the name to use for lookup in the cache for the imageStream
* @param imageStream the InputStream of the image to use. If null, no image will be used * @param imageStream the InputStream of the image to use. If null, no image will be used
*/ */
public Menu addMenu(String menuText, String cacheName, InputStream imageStream);
Menu addMenu(String menuText, String cacheName, InputStream imageStream) {
if (imageStream == null) {
return addMenu_(menuText, null);
}
else {
return addMenu_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, cacheName, imageStream));
}
}
/** /**
* Adds a sub-menu entry with text + image * Adds a sub-menu entry with text + image
@ -348,120 +166,27 @@ class Menu implements MenuEntry {
* @param menuText string of the text you want to appear * @param menuText string of the text you want to appear
* @param imageStream the InputStream of the image to use. If null, no image will be used * @param imageStream the InputStream of the image to use. If null, no image will be used
*/ */
public Menu addMenu(String menuText, InputStream imageStream);
Menu addMenu(String menuText, InputStream imageStream) {
if (imageStream == null) {
return addMenu_(menuText, null);
}
else {
return addMenu_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageStream));
}
}
/** /**
* This removes a menu entry from the dropdown menu. * This removes a menu entry from the dropdown menu.
* *
* @param menuEntry This is the menu entry to remove * @param entry This is the menu entry to remove
*/ */
public void remove(final Entry entry);
void remove(final MenuEntry menuEntry) {
if (menuEntry == null) {
throw new NullPointerException("No menu entry exists for menuEntry");
}
dispatchAndWait(new Runnable() {
@Override
public
void run() {
remove__(menuEntry);
}
});
}
/** /**
* This removes a sub-menu entry from the dropdown menu. * This removes a sub-menu entry from the dropdown menu.
* *
* @param menu This is the menu entry to remove * @param menu This is the menu entry to remove
*/ */
@SuppressWarnings("Duplicates") void remove(final Menu menu);
public
void remove(final Menu menu) {
if (menu == null) {
throw new NullPointerException("No sub-menu entry exists for menu");
}
dispatchAndWait(new Runnable() {
@Override
public
void run() {
remove__(menu);
}
});
}
protected
void remove__(final Object menuEntry) {
try {
synchronized (menuEntries) {
// null is passed in when a sub-menu is removing itself from us (because they have already called "remove" and have also
// removed themselves from the menuEntries)
if (menuEntry != null) {
for (Iterator<MenuEntry> iterator = menuEntries.iterator(); iterator.hasNext(); ) {
final MenuEntry entry = iterator.next();
if (entry == menuEntry) {
iterator.remove();
entry.remove();
break;
}
}
}
// now check to see if a spacer is at the top/bottom of the list (and remove it if so. This is a recursive function.
if (!menuEntries.isEmpty()) {
if (menuEntries.get(0) instanceof MenuSpacer) {
remove(menuEntries.get(0));
}
}
// now check to see if a spacer is at the top/bottom of the list (and remove it if so. This is a recursive function.
if (!menuEntries.isEmpty()) {
if (menuEntries.get(menuEntries.size()-1) instanceof MenuSpacer) {
remove(menuEntries.get(menuEntries.size() - 1));
}
}
}
} catch (Exception e) {
SystemTray.logger.error("Error removing entry from menu.", e);
}
}
/** /**
* This removes a menu entry or sub-menu (via the text label) from the dropdown menu. * This removes a menu entry or sub-menu (via the text label) from the dropdown menu.
* *
* @param menuText This is the label for the menu entry or sub-menu to remove * @param menuText This is the label for the menu entry or sub-menu to remove
*/ */
public void remove(final String menuText);
void remove(final String menuText) {
dispatchAndWait(new Runnable() {
@Override
public
void run() {
synchronized (menuEntries) {
MenuEntry menuEntry = get(menuText);
if (menuEntry != null) {
remove(menuEntry);
}
}
}
});
}
/**
* This removes the sub-menu entry from the dropdown menu.
*/
public
void remove() {
getParent().remove(this);
}
} }

View File

@ -20,6 +20,5 @@ package dorkbox.systemTray;
* This represents a common menu-spacer entry, that is cross platform in nature * This represents a common menu-spacer entry, that is cross platform in nature
*/ */
public public
interface MenuSpacer { interface Separator {
} }

View File

@ -20,6 +20,5 @@ package dorkbox.systemTray;
* This represents a common menu-status entry, that is cross platform in nature * This represents a common menu-status entry, that is cross platform in nature
*/ */
public public
interface MenuStatus { interface Status {
} }

View File

@ -24,6 +24,7 @@ import java.io.FileReader;
import java.io.InputStream; import java.io.InputStream;
import java.io.PrintStream; import java.io.PrintStream;
import java.net.URL; import java.net.URL;
import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -34,7 +35,6 @@ import dorkbox.systemTray.linux.jna.Gtk;
import dorkbox.systemTray.swing._AppIndicatorTray; import dorkbox.systemTray.swing._AppIndicatorTray;
import dorkbox.systemTray.swing._GtkStatusIconTray; import dorkbox.systemTray.swing._GtkStatusIconTray;
import dorkbox.systemTray.swing._SwingTray; import dorkbox.systemTray.swing._SwingTray;
import dorkbox.systemTray.util.ImageUtils;
import dorkbox.systemTray.util.JavaFX; import dorkbox.systemTray.util.JavaFX;
import dorkbox.systemTray.util.Swt; import dorkbox.systemTray.util.Swt;
import dorkbox.systemTray.util.WindowsSystemTraySwing; import dorkbox.systemTray.util.WindowsSystemTraySwing;
@ -51,7 +51,7 @@ import dorkbox.util.process.ShellProcessBuilder;
*/ */
@SuppressWarnings({"unused", "Duplicates", "DanglingJavadoc", "WeakerAccess"}) @SuppressWarnings({"unused", "Duplicates", "DanglingJavadoc", "WeakerAccess"})
public public
class SystemTray extends Menu { class SystemTray implements Menu {
public static final Logger logger = LoggerFactory.getLogger(SystemTray.class); public static final Logger logger = LoggerFactory.getLogger(SystemTray.class);
public static final int TYPE_AUTO_DETECT = 0; public static final int TYPE_AUTO_DETECT = 0;
@ -559,7 +559,7 @@ class SystemTray extends Menu {
systemTrayMenu = null; systemTrayMenu = null;
} }
else { else {
final Menu[] menuReference = new Menu[1]; final AtomicReference<Menu> reference = new AtomicReference<Menu>();
/* /*
* appIndicator/gtk require strings (which is the path) * appIndicator/gtk require strings (which is the path)
@ -602,7 +602,7 @@ class SystemTray extends Menu {
public public
void run() { void run() {
try { try {
menuReference[0] = (Menu) finalTrayType.getConstructors()[0].newInstance(systemTray); reference.set((Menu) finalTrayType.getConstructors()[0].newInstance(systemTray));
logger.info("Successfully Loaded: {}", finalTrayType.getSimpleName()); logger.info("Successfully Loaded: {}", finalTrayType.getSimpleName());
} catch (Exception e) { } catch (Exception e) {
logger.error("Unable to create tray type: '" + finalTrayType.getSimpleName() + "'", e); logger.error("Unable to create tray type: '" + finalTrayType.getSimpleName() + "'", e);
@ -613,7 +613,7 @@ class SystemTray extends Menu {
logger.error("Unable to create tray type: '" + trayType.getSimpleName() + "'", e); logger.error("Unable to create tray type: '" + trayType.getSimpleName() + "'", e);
} }
systemTrayMenu = menuReference[0]; systemTrayMenu = reference.get();
// These install a shutdown hook in JavaFX/SWT, so that when the main window is closed -- the system tray is ALSO closed. // These install a shutdown hook in JavaFX/SWT, so that when the main window is closed -- the system tray is ALSO closed.
@ -670,13 +670,6 @@ class SystemTray extends Menu {
return systemTray; return systemTray;
} }
private
SystemTray() {
super(null, null);
}
public public
void shutdown() { void shutdown() {
final Menu menu = systemTrayMenu; final Menu menu = systemTrayMenu;
@ -717,6 +710,7 @@ class SystemTray extends Menu {
public public
void setStatus(String statusText) { void setStatus(String statusText) {
final Menu menu = systemTrayMenu; final Menu menu = systemTrayMenu;
if (menu instanceof _AppIndicatorTray) { if (menu instanceof _AppIndicatorTray) {
((_AppIndicatorTray) menu).setStatus(statusText); ((_AppIndicatorTray) menu).setStatus(statusText);
} }
@ -728,20 +722,6 @@ class SystemTray extends Menu {
} }
} }
protected
void setImage_(File iconPath) {
final Menu menu = systemTrayMenu;
if (menu instanceof _AppIndicatorTray) {
((_AppIndicatorTray) menu).setImage_(iconPath);
}
else if (menu instanceof _GtkStatusIconTray) {
((_GtkStatusIconTray) menu).setImage_(iconPath);
} else {
// swing (windows/mac)
((_SwingTray) menu).setImage_(iconPath);
}
}
/** /**
* @return the parent menu (of this menu) or null if we are the root menu * @return the parent menu (of this menu) or null if we are the root menu
*/ */
@ -750,6 +730,12 @@ class SystemTray extends Menu {
return null; return null;
} }
@Override
public
SystemTray getSystemTray() {
return this;
}
/** /**
* @return the attached menu to this system tray * @return the attached menu to this system tray
*/ */
@ -776,107 +762,113 @@ class SystemTray extends Menu {
} }
// NO OP. /**
@Override * Does nothing. You cannot set the text for the system tray
protected */
MenuEntry addEntry_(final String menuText, final File imagePath, final SystemTrayMenuAction callback) {
return null;
}
// NO OP.
@Override
protected
Menu addMenu_(final String menuText, final File imagePath) {
return null;
}
// NO OP.
@Override @Override
public public
void setEnabled(final boolean enabled) { void setEnabled(final boolean enabled) {
} }
/**
* Does nothing. You cannot get the text for the system tray
*/
@Override @Override
public public
String getText() { String getText() {
return ""; return "";
} }
// NO OP. /**
* Does nothing. You cannot set the text for the system tray
*/
@Override @Override
public public
void setText(final String newText) { void setText(final String newText) {
// NO OP.
} }
/** /**
* Changes the tray image used. * Specifies the new image to set for a menu entry, NULL to delete the image
* *
* Because the cross-platform, underlying system uses a file path to load images for the system tray, * @param imageFile the file of the image to use or null
* this will directly use the contents of the specified file.
*
* @param imageFile the path of the image to use
*/ */
@Override @Override
public public
void setImage(final File imageFile) { void setImage(final File imageFile) {
setImage_(ImageUtils.resizeAndCache(ImageUtils.TRAY_SIZE, imageFile)); if (imageFile == null) {
throw new NullPointerException("imageFile cannot be null!");
}
systemTrayMenu.setImage(imageFile);
} }
/** /**
* Changes the tray image used. * Specifies the new image to set for a menu entry, NULL to delete the image
* *
* Because the cross-platform, underlying system uses a file path to load images for the system tray, * @param imagePath the full path of the image to use or null
* this will directly use the contents of the specified file.
*
* @param imagePath the path of the image to use
*/ */
@Override @Override
public public
void setImage(final String imagePath) { void setImage(final String imagePath) {
setImage_(ImageUtils.resizeAndCache(ImageUtils.TRAY_SIZE, imagePath)); if (imagePath == null) {
throw new NullPointerException("imagePath cannot be null!");
}
systemTrayMenu.setImage(imagePath);
} }
/** /**
* Changes the tray image used. * Specifies the new image to set for a menu entry, NULL to delete the image
* *
* Because the cross-platform, underlying system uses a file path to load images for the system tray, this will copy the contents of * @param imageUrl the URL of the image to use or null
* the URL to a temporary location on disk, based on the path specified by the URL.
*
* @param imageUrl the URL of the image to use
*/ */
@Override @Override
public public
void setImage(URL imageUrl) { void setImage(final URL imageUrl) {
setImage_(ImageUtils.resizeAndCache(ImageUtils.TRAY_SIZE, imageUrl)); if (imageUrl == null) {
throw new NullPointerException("imageUrl cannot be null!");
}
systemTrayMenu.setImage(imageUrl);
} }
/** /**
* Changes the tray image used. * Specifies the new image to set for a menu entry, NULL to delete the image
*
* Because the cross-platform, underlying system uses a file path to load images for the system tray, this will copy the contents of
* the imageStream to a temporary location on disk, based on the `cacheName` specified.
* *
* @param cacheName the name to use for lookup in the cache for the imageStream * @param cacheName the name to use for lookup in the cache for the imageStream
* @param imageStream the InputStream of the image to use * @param imageStream the InputStream of the image to use
*/ */
@Override @Override
public public
void setImage(String cacheName, InputStream imageStream) { void setImage(final String cacheName, final InputStream imageStream) {
setImage_(ImageUtils.resizeAndCache(ImageUtils.TRAY_SIZE, cacheName, imageStream)); if (cacheName == null) {
setImage(imageStream);
} else {
if (imageStream == null) {
throw new NullPointerException("imageStream cannot be null!");
}
systemTrayMenu.setImage(cacheName, imageStream);
}
} }
/** /**
* Changes the tray image used. * Specifies the new image to set for a menu entry, NULL to delete the image
* *
* Because the cross-platform, underlying system uses a file path to load images for the system tray, this will copy the contents of * This method **DOES NOT CACHE** the result, so multiple lookups for the same inputStream result in new files every time. This is
* the imageStream to a temporary location on disk. * also NOT RECOMMENDED, but is provided for simplicity.
* *
* @param imageStream the InputStream of the image to use * @param imageStream the InputStream of the image to use
*/ */
@Override @Override
public public
void setImage(final InputStream imageStream) { void setImage(final InputStream imageStream) {
setImage_(ImageUtils.resizeAndCache(ImageUtils.TRAY_SIZE, imageStream)); if (imageStream == null) {
throw new NullPointerException("imageStream cannot be null!");
}
systemTrayMenu.setImage(imageStream);
} }
/** /**
@ -888,33 +880,22 @@ class SystemTray extends Menu {
return true; return true;
} }
// NO OP. /**
* Does nothing. The system tray cannot have a callback when opened
*/
@Override @Override
public public
void setCallback(final SystemTrayMenuAction callback) { void setCallback(final SystemTrayMenuAction callback) {
// NO OP.
} }
// NO OP. /**
* Does nothing. The system tray cannot be opened via a shortcut key
*/
@Override @Override
public public
void setShortcut(final char key) { void setShortcut(final char key) {
} // NO OP.
// NO OP.
@Override
protected
void dispatch(final Runnable runnable) {
}
// NO OP.
@Override
protected
void dispatchAndWait(final Runnable runnable) {
}
// NO OP.
protected
void removePrivate() {
} }
/** /**
@ -925,14 +906,13 @@ class SystemTray extends Menu {
shutdown(); shutdown();
} }
/** /**
* Gets the menu entry for a specified text * Gets the menu entry for a specified text
* *
* @param menuText the menu entry text to use to find the menu entry. The first result found is returned * @param menuText the menu entry text to use to find the menu entry. The first result found is returned
*/ */
public final public final
MenuEntry get(final String menuText) { Entry get(final String menuText) {
return systemTrayMenu.get(menuText); return systemTrayMenu.get(menuText);
} }
@ -940,7 +920,7 @@ class SystemTray extends Menu {
* Gets the first menu entry, ignoring status and spacers * Gets the first menu entry, ignoring status and spacers
*/ */
public final public final
MenuEntry getFirst() { Entry getFirst() {
return systemTrayMenu.getFirst(); return systemTrayMenu.getFirst();
} }
@ -948,7 +928,7 @@ class SystemTray extends Menu {
* Gets the last menu entry, ignoring status and spacers * Gets the last menu entry, ignoring status and spacers
*/ */
public final public final
MenuEntry getLast() { Entry getLast() {
return systemTrayMenu.getLast(); return systemTrayMenu.getLast();
} }
@ -958,7 +938,7 @@ class SystemTray extends Menu {
* @param menuIndex the menu entry index to use to retrieve the menu entry. * @param menuIndex the menu entry index to use to retrieve the menu entry.
*/ */
public final public final
MenuEntry get(final int menuIndex) { Entry get(final int menuIndex) {
return systemTrayMenu.get(menuIndex); return systemTrayMenu.get(menuIndex);
} }
@ -973,7 +953,7 @@ class SystemTray extends Menu {
* @param callback callback that will be executed when this menu entry is clicked * @param callback callback that will be executed when this menu entry is clicked
*/ */
public final public final
MenuEntry addEntry(String menuText, SystemTrayMenuAction callback) { Entry addEntry(String menuText, SystemTrayMenuAction callback) {
return addEntry(menuText, (String) null, callback); return addEntry(menuText, (String) null, callback);
} }
@ -985,7 +965,7 @@ class SystemTray extends Menu {
* @param callback callback that will be executed when this menu entry is clicked * @param callback callback that will be executed when this menu entry is clicked
*/ */
public final public final
MenuEntry addEntry(String menuText, String imagePath, SystemTrayMenuAction callback) { Entry addEntry(String menuText, String imagePath, SystemTrayMenuAction callback) {
return systemTrayMenu.addEntry(menuText, imagePath, callback); return systemTrayMenu.addEntry(menuText, imagePath, callback);
} }
@ -997,7 +977,7 @@ class SystemTray extends Menu {
* @param callback callback that will be executed when this menu entry is clicked * @param callback callback that will be executed when this menu entry is clicked
*/ */
public final public final
MenuEntry addEntry(String menuText, URL imageUrl, SystemTrayMenuAction callback) { Entry addEntry(String menuText, URL imageUrl, SystemTrayMenuAction callback) {
return systemTrayMenu.addEntry(menuText, imageUrl, callback); return systemTrayMenu.addEntry(menuText, imageUrl, callback);
} }
@ -1010,7 +990,7 @@ class SystemTray extends Menu {
* @param callback callback that will be executed when this menu entry is clicked * @param callback callback that will be executed when this menu entry is clicked
*/ */
public public
MenuEntry addEntry(String menuText, String cacheName, InputStream imageStream, SystemTrayMenuAction callback) { Entry addEntry(String menuText, String cacheName, InputStream imageStream, SystemTrayMenuAction callback) {
return systemTrayMenu.addEntry(menuText, cacheName, imageStream, callback); return systemTrayMenu.addEntry(menuText, cacheName, imageStream, callback);
} }
@ -1022,7 +1002,7 @@ class SystemTray extends Menu {
* @param callback callback that will be executed when this menu entry is clicked * @param callback callback that will be executed when this menu entry is clicked
*/ */
public final public final
MenuEntry addEntry(String menuText, InputStream imageStream, SystemTrayMenuAction callback) { Entry addEntry(String menuText, InputStream imageStream, SystemTrayMenuAction callback) {
return systemTrayMenu.addEntry(menuText, imageStream, callback); return systemTrayMenu.addEntry(menuText, imageStream, callback);
} }
@ -1091,11 +1071,31 @@ class SystemTray extends Menu {
/** /**
* This removes a menu entry from the dropdown menu. * This removes a menu entry from the dropdown menu.
* *
* @param menuEntry This is the menu entry to remove * @param entry This is the menu entry to remove
*/ */
public final public final
void remove(final MenuEntry menuEntry) { void remove(final Entry entry) {
systemTrayMenu.remove(menuEntry); systemTrayMenu.remove(entry);
}
/**
* This removes a sub-menu entry from the dropdown menu.
*
* @param menu This is the menu entry to remove
*/
@Override
public final
void remove(final Menu menu) {
systemTrayMenu.remove(menu);
}
/**
* This removes al menu entries from this menu
*/
@Override
public final
void clear() {
systemTrayMenu.clear();
} }
/** /**

View File

@ -18,12 +18,11 @@ package dorkbox.systemTray;
public public
interface SystemTrayMenuAction { interface SystemTrayMenuAction {
/** /**
* This method will ALWAYS be called in the correct context, either in the swing EDT (if it's swing based), or in a separate thread * This method will ALWAYS be called in the swing EDT
* (GtkStatusIcon/AppIndicator based).
* *
* @param systemTray this is the parent, system tray object * @param systemTray this is the parent, system tray object
* @param parent this is the parent menu of this menu entry * @param parent this is the parent menu of this menu entry
* @param entry this is the menu entry that was clicked * @param entry this is the menu entry that was clicked
*/ */
void onClick(SystemTray systemTray, Menu parent, final MenuEntry entry); void onClick(SystemTray systemTray, Menu parent, final Entry entry);
} }

View File

@ -23,24 +23,24 @@ import java.net.URL;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JMenuItem; import javax.swing.JMenuItem;
import dorkbox.systemTray.Entry;
import dorkbox.systemTray.Menu; import dorkbox.systemTray.Menu;
import dorkbox.systemTray.MenuEntry;
import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.util.ImageUtils; import dorkbox.systemTray.util.ImageUtils;
import dorkbox.util.SwingUtil; import dorkbox.util.SwingUtil;
abstract abstract
class Entry implements MenuEntry { class EntryImpl implements Entry {
private final int id = Menu.MENU_ID_COUNTER.getAndIncrement(); private final int id = MenuImpl.MENU_ID_COUNTER.getAndIncrement();
private final SwingMenu parent; private final MenuImpl parent;
final JComponent _native; final JComponent _native;
// this have to be volatile, because they can be changed from any thread // this have to be volatile, because they can be changed from any thread
private volatile String text; private volatile String text;
// this is ALWAYS called on the EDT. // this is ALWAYS called on the EDT.
Entry(final SwingMenu parent, final JComponent menuItem) { EntryImpl(final MenuImpl parent, final JComponent menuItem) {
this.parent = parent; this.parent = parent;
this._native = menuItem; this._native = menuItem;
@ -54,11 +54,14 @@ class Entry implements MenuEntry {
} }
/** /**
* must always be called in the GTK thread * must always be called in the EDT thread
*/ */
abstract abstract
void renderText(final String text); void renderText(final String text);
/**
* Not always called on the EDT thread
*/
abstract abstract
void setImage_(final File imageFile); void setImage_(final File imageFile);
@ -204,7 +207,7 @@ class Entry implements MenuEntry {
return false; return false;
} }
Entry other = (Entry) obj; EntryImpl other = (EntryImpl) obj;
return this.id == other.id; return this.id == other.id;
} }

View File

@ -25,7 +25,7 @@ import javax.swing.JMenuItem;
import dorkbox.systemTray.SystemTrayMenuAction; import dorkbox.systemTray.SystemTrayMenuAction;
import dorkbox.util.SwingUtil; import dorkbox.util.SwingUtil;
class EntryItem extends Entry { class EntryItem extends EntryImpl {
private final ActionListener swingCallback; private final ActionListener swingCallback;
@ -33,7 +33,7 @@ class EntryItem extends Entry {
private volatile SystemTrayMenuAction callback; private volatile SystemTrayMenuAction callback;
// this is ALWAYS called on the EDT. // this is ALWAYS called on the EDT.
EntryItem(final SwingMenu parent, final SystemTrayMenuAction callback) { EntryItem(final MenuImpl parent, final SystemTrayMenuAction callback) {
super(parent, new AdjustedJMenuItem()); super(parent, new AdjustedJMenuItem());
this.callback = callback; this.callback = callback;

View File

@ -19,13 +19,12 @@ import java.io.File;
import javax.swing.JSeparator; import javax.swing.JSeparator;
import dorkbox.systemTray.MenuSpacer;
import dorkbox.systemTray.SystemTrayMenuAction; import dorkbox.systemTray.SystemTrayMenuAction;
class EntrySeparator extends Entry implements MenuSpacer { class EntrySeparator extends EntryImpl implements dorkbox.systemTray.Separator {
// this is ALWAYS called on the EDT. // this is ALWAYS called on the EDT.
EntrySeparator(final SwingMenu parent) { EntrySeparator(final MenuImpl parent) {
super(parent, new JSeparator(JSeparator.HORIZONTAL)); super(parent, new JSeparator(JSeparator.HORIZONTAL));
} }

View File

@ -15,17 +15,18 @@
*/ */
package dorkbox.systemTray.swing; package dorkbox.systemTray.swing;
import java.awt.Font;
import java.io.File; import java.io.File;
import javax.swing.JMenuItem; import javax.swing.JMenuItem;
import dorkbox.systemTray.MenuStatus; import dorkbox.systemTray.Status;
import dorkbox.systemTray.SystemTrayMenuAction; import dorkbox.systemTray.SystemTrayMenuAction;
class EntryStatus extends Entry implements MenuStatus { class EntryStatus extends EntryImpl implements Status {
// this is ALWAYS called on the EDT. // this is ALWAYS called on the EDT.
EntryStatus(final SwingMenu parent, final String label) { EntryStatus(final MenuImpl parent, final String label) {
super(parent, new JMenuItem()); super(parent, new JMenuItem());
setText(label); setText(label);
} }
@ -33,11 +34,12 @@ class EntryStatus extends Entry implements MenuStatus {
// called in the EDT thread // called in the EDT thread
@Override @Override
void renderText(final String text) { void renderText(final String text) {
// AppIndicator strips out markup text, so we disable bold in order to have all of the menus MOSTLY look the same
// https://mail.gnome.org/archives/commits-list/2016-March/msg05444.html
((JMenuItem) _native).setText(text); ((JMenuItem) _native).setText(text);
Font font = _native.getFont();
Font font1 = font.deriveFont(Font.BOLD);
_native.setFont(font1);
// this makes sure it can't be selected
_native.setEnabled(false); _native.setEnabled(false);
} }

View File

@ -0,0 +1,720 @@
/*
* 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 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.Entry;
import dorkbox.systemTray.Menu;
import dorkbox.systemTray.Status;
import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.SystemTrayMenuAction;
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
class MenuImpl implements Menu {
public static final AtomicInteger MENU_ID_COUNTER = new AtomicInteger();
private final int id = MenuImpl.MENU_ID_COUNTER.getAndIncrement();
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
*
* @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) {
this.systemTray = systemTray;
this.parent = parent;
this._native = _native;
}
protected
void dispatch(final Runnable runnable) {
// this will properly check if we are running on the EDT
SwingUtil.invokeLater(runnable);
}
protected
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);
}
/**
* Will add a new menu entry, or update one if it already exists
* NOT ALWAYS CALLED ON EDT
*/
private
Entry addEntry_(final String menuText, final File imagePath, final SystemTrayMenuAction 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();
}
/**
* Will add a new sub-menu entry, or update one if it already exists
* NOT ALWAYS CALLED ON EDT
*/
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);
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();
}
// 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);
}
}
});
}
public
Menu getParent() {
return parent;
}
public
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() {
synchronized (menuEntries) {
synchronized (menuEntries) {
Entry entry = new EntrySeparator(MenuImpl.this);
menuEntries.add(entry);
}
}
}
});
}
public
Entry get(final String menuText) {
if (menuText == null || menuText.isEmpty()) {
return null;
}
// Must be wrapped in a synchronized block for object visibility
synchronized (menuEntries) {
for (Entry entry : menuEntries) {
String text = entry.getText();
// text can be null
if (menuText.equals(text)) {
return entry;
}
}
}
return null;
}
public
Entry getFirst() {
return get(0);
}
public
Entry getLast() {
// Must be wrapped in a synchronized block for object visibility
synchronized (menuEntries) {
if (!menuEntries.isEmpty()) {
Entry entry = null;
for (int i = 0, menuEntriesSize = menuEntries.size(); i < menuEntriesSize; i++) {
entry = menuEntries.get(i);
}
if (!(entry instanceof dorkbox.systemTray.Separator || entry instanceof Status)) {
return entry;
}
}
}
return null;
}
public
Entry get(final int menuIndex) {
if (menuIndex < 0) {
return null;
}
// Must be wrapped in a synchronized block for object visibility
synchronized (menuEntries) {
if (!menuEntries.isEmpty()) {
int count = 0;
for (Entry entry : menuEntries) {
if (entry instanceof dorkbox.systemTray.Separator || entry instanceof Status) {
continue;
}
if (count == menuIndex) {
return entry;
}
count++;
}
}
}
return null;
}
public
Entry addEntry(String menuText, SystemTrayMenuAction callback) {
return addEntry(menuText, (String) null, callback);
}
public
Entry addEntry(String menuText, String imagePath, SystemTrayMenuAction callback) {
if (imagePath == null) {
return addEntry_(menuText, null, callback);
}
else {
return addEntry_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imagePath), callback);
}
}
public
Entry addEntry(String menuText, URL imageUrl, SystemTrayMenuAction callback) {
if (imageUrl == null) {
return addEntry_(menuText, null, callback);
}
else {
return addEntry_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageUrl), callback);
}
}
public
Entry addEntry(String menuText, String cacheName, InputStream imageStream, SystemTrayMenuAction callback) {
if (imageStream == null) {
return addEntry_(menuText, null, callback);
}
else {
return addEntry_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, cacheName, imageStream), callback);
}
}
public
Entry addEntry(String menuText, InputStream imageStream, SystemTrayMenuAction callback) {
if (imageStream == null) {
return addEntry_(menuText, null, callback);
}
else {
return addEntry_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageStream), callback);
}
}
public
Menu addMenu(String menuText) {
return addMenu(menuText, (String) null);
}
public
Menu addMenu(String menuText, String imagePath) {
if (imagePath == null) {
return addMenu_(menuText, null);
}
else {
return addMenu_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imagePath));
}
}
public
Menu addMenu(String menuText, URL imageUrl) {
if (imageUrl == null) {
return addMenu_(menuText, null);
}
else {
return addMenu_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageUrl));
}
}
public
Menu addMenu(String menuText, String cacheName, InputStream imageStream) {
if (imageStream == null) {
return addMenu_(menuText, null);
}
else {
return addMenu_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, cacheName, imageStream));
}
}
public
Menu addMenu(String menuText, InputStream imageStream) {
if (imageStream == null) {
return addMenu_(menuText, null);
}
else {
return addMenu_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageStream));
}
}
@Override
public final
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
String getText() {
return text;
}
@Override
public
void setText(final String newText) {
text = newText;
dispatch(new Runnable() {
@Override
public
void run() {
renderText(newText);
}
});
}
@Override
public
void setCallback(final SystemTrayMenuAction 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);
}
});
}
}
/**
* This removes a menu entry from the dropdown menu.
*
* @param entry This is the menu entry to remove
*/
public
void remove(final Entry entry) {
if (entry == null) {
throw new NullPointerException("No menu entry exists for entry");
}
dispatchAndWait(new Runnable() {
@Override
public
void run() {
remove__(entry);
}
});
}
/**
* This removes a sub-menu entry from the dropdown menu.
*
* @param menu This is the menu entry to remove
*/
@Override
public
void remove(final Menu menu) {
final Menu parent = getParent();
if (parent == null) {
// we are the system tray menu, so we just remove from ourselves
dispatchAndWait(new Runnable() {
@Override
public
void run() {
remove__(menu);
}
});
} else {
final Menu _this = this;
// we are a submenu
dispatchAndWait(new Runnable() {
@Override
public
void run() {
((MenuImpl) parent).remove__(_this);
}
});
}
}
// NOT ALWAYS CALLED ON EDT
private
void remove__(final Object menuEntry) {
try {
synchronized (menuEntries) {
// null is passed in when a sub-menu is removing itself from us (because they have already called "remove" and have also
// removed themselves from the menuEntries)
if (menuEntry != null) {
for (Iterator<Entry> iterator = menuEntries.iterator(); iterator.hasNext(); ) {
final Entry entry = iterator.next();
if (entry == menuEntry) {
iterator.remove();
entry.remove();
break;
}
}
}
// now check to see if a spacer is at the top/bottom of the list (and remove it if so. This is a recursive function.
if (!menuEntries.isEmpty()) {
if (menuEntries.get(0) instanceof dorkbox.systemTray.Separator) {
remove(menuEntries.get(0));
}
}
// now check to see if a spacer is at the top/bottom of the list (and remove it if so. This is a recursive function.
if (!menuEntries.isEmpty()) {
if (menuEntries.get(menuEntries.size()-1) instanceof dorkbox.systemTray.Separator) {
remove(menuEntries.get(menuEntries.size() - 1));
}
}
}
} catch (Exception e) {
SystemTray.logger.error("Error removing entry from menu.", e);
}
}
/**
* This removes a menu entry or sub-menu (via the text label) from the dropdown menu.
*
* @param menuText This is the label for the menu entry or sub-menu to remove
*/
public
void remove(final String menuText) {
dispatchAndWait(new Runnable() {
@Override
public
void run() {
synchronized (menuEntries) {
Entry entry = get(menuText);
if (entry != null) {
remove(entry);
}
}
}
});
}
@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 clear() {
dispatch(new Runnable() {
@Override
public
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);
for (Entry entry : menuEntriesCopy) {
entry.remove();
}
}
}
});
}
}

View File

@ -1,331 +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 static dorkbox.systemTray.swing.Entry.getVkKey;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.util.concurrent.atomic.AtomicReference;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import dorkbox.systemTray.Menu;
import dorkbox.systemTray.MenuEntry;
import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.SystemTrayMenuAction;
import dorkbox.systemTray.util.ImageUtils;
import dorkbox.util.SwingUtil;
// this is a weird composite class, because it must be a Menu, but ALSO a MenuEntry -- so it has both
class SwingMenu extends Menu {
// 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
*/
SwingMenu(final SystemTray systemTray, final Menu parent, final JComponent _native) {
super(systemTray, parent);
this._native = _native;
}
protected
void dispatch(final Runnable runnable) {
// this will properly check if we are running on the EDT
SwingUtil.invokeLater(runnable);
}
protected
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);
}
}
@Override
public
void addSeparator() {
dispatch(new Runnable() {
@Override
public
void run() {
synchronized (menuEntries) {
synchronized (menuEntries) {
MenuEntry menuEntry = new EntrySeparator(SwingMenu.this);
menuEntries.add(menuEntry);
}
}
}
});
}
/**
* Enables, or disables the sub-menu entry.
*/
@Override
public
void setEnabled(final boolean enabled) {
_native.setEnabled(enabled);
}
/**
* Will add a new menu entry, or update one if it already exists
*/
protected
MenuEntry addEntry_(final String menuText, final File imagePath, final SystemTrayMenuAction callback) {
if (menuText == null) {
throw new NullPointerException("Menu text cannot be null");
}
final AtomicReference<MenuEntry> value = new AtomicReference<MenuEntry>();
dispatchAndWait(new Runnable() {
@Override
public
void run() {
synchronized (menuEntries) {
MenuEntry menuEntry = get(menuText);
if (menuEntry == null) {
// must always be called on the EDT
menuEntry = new EntryItem(SwingMenu.this, callback);
menuEntry.setText(menuText);
menuEntry.setImage(imagePath);
menuEntries.add(menuEntry);
} else if (menuEntry instanceof EntryItem) {
menuEntry.setText(menuText);
menuEntry.setImage(imagePath);
}
value.set(menuEntry);
}
}
});
return value.get();
}
/**
* Will add a new sub-menu entry, or update one if it already exists
*/
protected
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) {
MenuEntry menuEntry = get(menuText);
if (menuEntry == null) {
// must always be called on the EDT
menuEntry = new SwingMenu(getSystemTray(), SwingMenu.this, new AdjustedJMenu());
_native.add(((SwingMenu) menuEntry)._native);
menuEntry.setText(menuText);
menuEntry.setImage(imagePath);
value.set((Menu)menuEntry);
} else if (menuEntry instanceof SwingMenu) {
menuEntry.setText(menuText);
menuEntry.setImage(imagePath);
}
menuEntries.add(menuEntry);
}
}
});
return value.get();
}
// always called in the EDT
private
void renderText(final String text) {
((JMenuItem) _native).setText(text);
}
@SuppressWarnings("Duplicates")
private
void setImage_(final File imageFile) {
hasLegitIcon = imageFile != null;
SwingUtil.invokeLater(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
String getText() {
return text;
}
@Override
public
void setText(final String newText) {
text = newText;
SwingUtil.invokeLater(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
boolean hasImage() {
return hasLegitIcon;
}
@Override
public
void setCallback(final SystemTrayMenuAction 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);
}
});
}
}
@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);
}
}
});
}
}

View File

@ -66,7 +66,6 @@ class TrayPopup extends JPopupMenu {
hiddenDialog.setAlwaysOnTop(true); hiddenDialog.setAlwaysOnTop(true);
// on Linux, the following two entries will **MOST OF THE TIME** prevent the hidden dialog from showing in the task-bar // on Linux, the following two entries will **MOST OF THE TIME** prevent the hidden dialog from showing in the task-bar
// on MacOS, you need "special permission" to not have a hidden dialog show on the dock.
hiddenDialog.getContentPane().setLayout(null); hiddenDialog.getContentPane().setLayout(null);
// this is java 1.7, so we have to use reflection. It's not critical for this to be set, but it helps hide the title in the taskbar // this is java 1.7, so we have to use reflection. It's not critical for this to be set, but it helps hide the title in the taskbar
@ -157,15 +156,14 @@ class TrayPopup extends JPopupMenu {
hiddenDialog.dispatchEvent(new WindowEvent(hiddenDialog, WindowEvent.WINDOW_CLOSING)); hiddenDialog.dispatchEvent(new WindowEvent(hiddenDialog, WindowEvent.WINDOW_CLOSING));
} }
void doShow(final Point point, final int offset) { void doShow(final Point point, int offset) {
Dimension size = getPreferredSize(); Dimension size = getPreferredSize();
Rectangle bounds = ScreenUtil.getScreenBoundsAt(point); Rectangle bounds = ScreenUtil.getScreenBoundsAt(point);
int x = point.x; int x = point.x;
int y = point.y; int y = point.y;
if (y < bounds.y) { if (y < bounds.y) {
y = bounds.y; y = bounds.y;
} }
@ -175,22 +173,19 @@ class TrayPopup extends JPopupMenu {
y -= size.height; // snap to edge of mouse y -= size.height; // snap to edge of mouse
} }
if (x < bounds.x) { if (x < bounds.x) {
x = bounds.x; x = bounds.x;
x -= offset; // display over the stupid appindicator menu (which has to show, this is a major hack!)
} }
else if (x + size.width > bounds.x + bounds.width) { else if (x + size.width > bounds.x + bounds.width) {
// our menu cannot have the left-edge snap to the mouse so we make the right-edge snap to the mouse // our menu cannot have the left-edge snap to the mouse so we make the right-edge snap to the mouse
x -= size.width; // snap right edge of menu to mouse x -= size.width; // snap right edge of menu to mouse
x += offset; // display over the stupid appindicator menu (which has to show, this is a major hack!) offset = -offset; // flip offset
} else {
x -= offset; // display over the stupid appindicator menu (which has to show, this is a major hack!)
} }
System.err.println("SHOWING POPUP @" + x + "," + y); // display over the stupid AppIndicator menu (which has to show, then we remove. THIS IS A HACK!)
x -= offset;
// critical to get the keyboard listeners working for the popup menu // critical to get the keyboard listeners working for the popup menu
setInvoker(hiddenDialog.getContentPane()); setInvoker(hiddenDialog.getContentPane());
@ -202,6 +197,4 @@ class TrayPopup extends JPopupMenu {
setVisible(true); setVisible(true);
requestFocusInWindow(); requestFocusInWindow();
} }
} }

View File

@ -80,17 +80,26 @@ import dorkbox.util.SwingUtil;
* http://bazaar.launchpad.net/~ubuntu-desktop/ido/gtk3/files * http://bazaar.launchpad.net/~ubuntu-desktop/ido/gtk3/files
*/ */
public public
class _AppIndicatorTray extends GenericTray { class _AppIndicatorTray extends _Tray {
private AppIndicatorInstanceStruct appIndicator; private volatile AppIndicatorInstanceStruct appIndicator;
private boolean isActive = false; private boolean isActive = false;
private final Runnable popupRunnable;
// This is required if we have JavaFX or SWT shutdown hooks (to prevent us from shutting down twice...) // This is required if we have JavaFX or SWT shutdown hooks (to prevent us from shutting down twice...)
private AtomicBoolean shuttingDown = new AtomicBoolean(); private AtomicBoolean shuttingDown = new AtomicBoolean();
private volatile NativeLong nativeLong; // necessary to prevent GC on these objects
private volatile GEventCallback gtkCallback; private NativeLong nativeLong;
private GEventCallback gtkCallback;
// necessary to provide a menu (which we draw over) so we get the "on open" event when the menu is opened via clicking
private Pointer dummyMenu; private Pointer dummyMenu;
private final Runnable popupRunnable;
// 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 public
_AppIndicatorTray(final SystemTray systemTray) { _AppIndicatorTray(final SystemTray systemTray) {
@ -98,7 +107,7 @@ class _AppIndicatorTray extends GenericTray {
if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TYPE_GTK_STATUSICON) { if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TYPE_GTK_STATUSICON) {
// if we force GTK type system tray, don't attempt to load AppIndicator libs // if we force GTK type system tray, don't attempt to load AppIndicator libs
throw new IllegalArgumentException("Unable to start AppIndicator if 'SystemTray.FORCE_TRAY_TYPE' is set to GtkStatusIcon"); throw new IllegalArgumentException("Unable to start AppIndicator Tray if 'SystemTray.FORCE_TRAY_TYPE' is set to GtkStatusIcon");
} }
TrayPopup popupMenu = (TrayPopup) _native; TrayPopup popupMenu = (TrayPopup) _native;
@ -108,6 +117,11 @@ class _AppIndicatorTray extends GenericTray {
@Override @Override
public public
void run() { void run() {
if (appIndicator == null) {
// if we are shutting down, don't hook the menu again
return;
}
// Such ugly hacks to get AppIndicator support properly working. This is so horrible I am ashamed. // Such ugly hacks to get AppIndicator support properly working. This is so horrible I am ashamed.
Gtk.dispatch(new Runnable() { Gtk.dispatch(new Runnable() {
@Override @Override
@ -132,10 +146,6 @@ class _AppIndicatorTray extends GenericTray {
} }
}; };
// appindicators DO NOT support anything other than PLAIN gtk-menus
// they ALSO do not support tooltips, so we cater to the lowest common denominator
// trayIcon.setToolTip(_SwingTray.this.appName);
Gtk.startGui(); Gtk.startGui();
Gtk.dispatch(new Runnable() { Gtk.dispatch(new Runnable() {
@ -167,7 +177,7 @@ class _AppIndicatorTray extends GenericTray {
@Override @Override
public public
void callback(Pointer notUsed, final GdkEventButton event) { void callback(Pointer notUsed, final GdkEventButton event) {
Gtk.gtk_widget_destroy(dummyMenu); Gtk.gtk_widget_destroy(dummyMenu); // destroy the menu, so it will disappear (and we then have focus on our swing menu)
SwingUtil.invokeLater(popupRunnable); SwingUtil.invokeLater(popupRunnable);
} }
}; };
@ -175,7 +185,8 @@ class _AppIndicatorTray extends GenericTray {
nativeLong = Gobject.g_signal_connect_object(rootMenuItem.getValue(), "about-to-show", gtkCallback, null, 0); nativeLong = Gobject.g_signal_connect_object(rootMenuItem.getValue(), "about-to-show", gtkCallback, null, 0);
} }
private void createAppIndicatorMenu() { private
void createAppIndicatorMenu() {
dummyMenu = Gtk.gtk_menu_new(); dummyMenu = Gtk.gtk_menu_new();
Pointer item = Gtk.gtk_image_menu_item_new_with_mnemonic(""); Pointer item = Gtk.gtk_image_menu_item_new_with_mnemonic("");
Gtk.gtk_menu_shell_append(dummyMenu, item); Gtk.gtk_menu_shell_append(dummyMenu, item);
@ -223,7 +234,7 @@ class _AppIndicatorTray extends GenericTray {
// kindof lame, but necessary for KDE // kindof lame, but necessary for KDE
if (Gtk.isKDE) { if (Gtk.isKDE) {
AppIndicator.app_indicator_set_label(appIndicator, "SystemTray", null); AppIndicator.app_indicator_set_label(appIndicator, "SystemTray", "SystemTray");
} }
// now we have to setup a way for us to catch the "activation" click on this menu. Must be after the menu is set // now we have to setup a way for us to catch the "activation" click on this menu. Must be after the menu is set

View File

@ -40,7 +40,7 @@ import dorkbox.systemTray.linux.jna.Gtk;
* swing menu popup INSTEAD of GTK menu popups. The "golden standard" is our swing menu popup, since we have 100% control over it. * swing menu popup INSTEAD of GTK menu popups. The "golden standard" is our swing menu popup, since we have 100% control over it.
*/ */
public public
class _GtkStatusIconTray extends GenericTray { class _GtkStatusIconTray extends _Tray {
private volatile Pointer trayIcon; private volatile Pointer trayIcon;
// http://code.metager.de/source/xref/gnome/Platform/gtk%2B/gtk/deprecated/gtkstatusicon.c // http://code.metager.de/source/xref/gnome/Platform/gtk%2B/gtk/deprecated/gtkstatusicon.c
@ -58,6 +58,7 @@ class _GtkStatusIconTray extends GenericTray {
public public
_GtkStatusIconTray(final SystemTray systemTray) { _GtkStatusIconTray(final SystemTray systemTray) {
super(systemTray, null, new TrayPopup()); super(systemTray, null, new TrayPopup());
if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TYPE_APP_INDICATOR) { if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TYPE_APP_INDICATOR) {
// if we force GTK type system tray, don't attempt to load AppIndicator libs // if we force GTK type system tray, don't attempt to load AppIndicator libs
throw new IllegalArgumentException("Unable to start GtkStatusIcon if 'SystemTray.FORCE_TRAY_TYPE' is set to AppIndicator"); throw new IllegalArgumentException("Unable to start GtkStatusIcon if 'SystemTray.FORCE_TRAY_TYPE' is set to AppIndicator");
@ -79,9 +80,9 @@ class _GtkStatusIconTray extends GenericTray {
} }
}; };
// appindicators DO NOT support anything other than PLAIN gtk-menus // 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 // they ALSO do not support tooltips, so we cater to the lowest common denominator
// trayIcon.setToolTip(_SwingTray.this.appName); // trayIcon.setToolTip("app name");
Gtk.startGui(); Gtk.startGui();

View File

@ -26,7 +26,7 @@ import java.io.File;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
import javax.swing.JPopupMenu; import javax.swing.JPopupMenu;
import dorkbox.systemTray.MenuEntry; import dorkbox.systemTray.Entry;
/** /**
* Class for handling all system tray interaction, via SWING. * Class for handling all system tray interaction, via SWING.
@ -38,7 +38,7 @@ import dorkbox.systemTray.MenuEntry;
*/ */
@SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "WeakerAccess"}) @SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "WeakerAccess"})
public public
class _SwingTray extends GenericTray { class _SwingTray extends _Tray {
volatile SystemTray tray; volatile SystemTray tray;
volatile TrayIcon trayIcon; volatile TrayIcon trayIcon;
@ -59,8 +59,8 @@ class _SwingTray extends GenericTray {
tray.remove(trayIcon); tray.remove(trayIcon);
synchronized (menuEntries) { synchronized (menuEntries) {
for (MenuEntry menuEntry : menuEntries) { for (Entry entry : menuEntries) {
menuEntry.remove(); entry.remove();
} }
menuEntries.clear(); menuEntries.clear();
} }
@ -88,9 +88,9 @@ class _SwingTray extends GenericTray {
popupMenu.pack(); popupMenu.pack();
popupMenu.setFocusable(true); popupMenu.setFocusable(true);
// appindicators DO NOT support anything other than PLAIN gtk-menus // 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 // they ALSO do not support tooltips, so we cater to the lowest common denominator
// trayIcon.setToolTip(_SwingTray.this.appName); // trayIcon.setToolTip("app name");
trayIcon.addMouseListener(new MouseAdapter() { trayIcon.addMouseListener(new MouseAdapter() {
@Override @Override

View File

@ -17,17 +17,17 @@ package dorkbox.systemTray.swing;
import javax.swing.JComponent; import javax.swing.JComponent;
import dorkbox.systemTray.Entry;
import dorkbox.systemTray.Menu; import dorkbox.systemTray.Menu;
import dorkbox.systemTray.MenuEntry;
import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.util.ImageUtils; import dorkbox.systemTray.util.ImageUtils;
public abstract public abstract
class GenericTray extends SwingMenu { class _Tray extends MenuImpl {
/** /**
* Called in the EDT * Called in the EDT
*/ */
GenericTray(final SystemTray systemTray, final Menu parent, final JComponent _native) { _Tray(final SystemTray systemTray, final Menu parent, final JComponent _native) {
super(systemTray, parent, _native); super(systemTray, parent, _native);
ImageUtils.determineIconSize(); ImageUtils.determineIconSize();
@ -36,9 +36,9 @@ class GenericTray extends SwingMenu {
public public
String getStatus() { String getStatus() {
synchronized (menuEntries) { synchronized (menuEntries) {
MenuEntry menuEntry = menuEntries.get(0); Entry entry = menuEntries.get(0);
if (menuEntry instanceof EntryStatus) { if (entry instanceof EntryStatus) {
return menuEntry.getText(); return entry.getText();
} }
} }
@ -47,16 +47,16 @@ class GenericTray extends SwingMenu {
public public
void setStatus(final String statusText) { void setStatus(final String statusText) {
final SwingMenu _this = this; final MenuImpl _this = this;
dispatchAndWait(new Runnable() { dispatchAndWait(new Runnable() {
@Override @Override
public public
void run() { void run() {
synchronized (menuEntries) { synchronized (menuEntries) {
// status is ALWAYS at 0 index... // status is ALWAYS at 0 index...
Entry menuEntry = null; EntryImpl menuEntry = null;
if (!menuEntries.isEmpty()) { if (!menuEntries.isEmpty()) {
menuEntry = (Entry) menuEntries.get(0); menuEntry = (EntryImpl) menuEntries.get(0);
} }
if (menuEntry instanceof EntryStatus) { if (menuEntry instanceof EntryStatus) {

View File

@ -18,8 +18,8 @@ package dorkbox;
import java.net.URL; import java.net.URL;
import dorkbox.systemTray.Entry;
import dorkbox.systemTray.Menu; import dorkbox.systemTray.Menu;
import dorkbox.systemTray.MenuEntry;
import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.SystemTrayMenuAction; import dorkbox.systemTray.SystemTrayMenuAction;
@ -56,13 +56,13 @@ class TestTray {
callbackGreen = new SystemTrayMenuAction() { callbackGreen = new SystemTrayMenuAction() {
@Override @Override
public public
void onClick(final SystemTray systemTray, final Menu parent, final MenuEntry menuEntry) { void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) {
systemTray.setStatus("Some Mail!"); systemTray.setStatus("Some Mail!");
systemTray.setImage(GREEN_MAIL); systemTray.setImage(GREEN_MAIL);
menuEntry.setCallback(callbackGray); entry.setCallback(callbackGray);
menuEntry.setImage(BLACK_MAIL); entry.setImage(BLACK_MAIL);
menuEntry.setText("Delete Mail"); entry.setText("Delete Mail");
// systemTray.remove(menuEntry); // systemTray.remove(menuEntry);
} }
}; };
@ -70,18 +70,18 @@ class TestTray {
callbackGray = new SystemTrayMenuAction() { callbackGray = new SystemTrayMenuAction() {
@Override @Override
public public
void onClick(final SystemTray systemTray, final Menu parent, final MenuEntry menuEntry) { void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) {
systemTray.setStatus(null); systemTray.setStatus(null);
systemTray.setImage(BLACK_MAIL); systemTray.setImage(BLACK_MAIL);
menuEntry.setCallback(null); entry.setCallback(null);
// systemTray.setStatus("Mail Empty"); // systemTray.setStatus("Mail Empty");
systemTray.remove(menuEntry); systemTray.remove(entry);
System.err.println("POW"); System.err.println("POW");
} }
}; };
MenuEntry menuEntry = this.systemTray.addEntry("Green Mail", GREEN_MAIL, callbackGreen); Entry menuEntry = this.systemTray.addEntry("Green Mail", GREEN_MAIL, callbackGreen);
// case does not matter // case does not matter
menuEntry.setShortcut('G'); menuEntry.setShortcut('G');
@ -91,14 +91,14 @@ class TestTray {
submenu.addEntry("Disable menu", LT_GRAY_MAIL, new SystemTrayMenuAction() { submenu.addEntry("Disable menu", LT_GRAY_MAIL, new SystemTrayMenuAction() {
@Override @Override
public public
void onClick(final SystemTray systemTray, final Menu parent, final MenuEntry entry) { void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) {
submenu.setEnabled(false); submenu.setEnabled(false);
} }
}); });
submenu.addEntry("Remove menu", GREEN_MAIL, new SystemTrayMenuAction() { submenu.addEntry("Remove menu", GREEN_MAIL, new SystemTrayMenuAction() {
@Override @Override
public public
void onClick(final SystemTray systemTray, final Menu parent, final MenuEntry entry) { void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) {
submenu.remove(); submenu.remove();
} }
}); });
@ -107,7 +107,7 @@ class TestTray {
systemTray.addEntry("Quit", new SystemTrayMenuAction() { systemTray.addEntry("Quit", new SystemTrayMenuAction() {
@Override @Override
public public
void onClick(final SystemTray systemTray, final Menu parent, final MenuEntry menuEntry) { void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) {
systemTray.shutdown(); systemTray.shutdown();
//System.exit(0); not necessary if all non-daemon threads have stopped. //System.exit(0); not necessary if all non-daemon threads have stopped.
} }

View File

@ -18,8 +18,8 @@ package dorkbox;
import java.net.URL; import java.net.URL;
import dorkbox.systemTray.Entry;
import dorkbox.systemTray.Menu; import dorkbox.systemTray.Menu;
import dorkbox.systemTray.MenuEntry;
import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.SystemTrayMenuAction; import dorkbox.systemTray.SystemTrayMenuAction;
import javafx.application.Application; import javafx.application.Application;
@ -90,13 +90,13 @@ class TestTrayJavaFX extends Application {
callbackGreen = new SystemTrayMenuAction() { callbackGreen = new SystemTrayMenuAction() {
@Override @Override
public public
void onClick(final SystemTray systemTray, final Menu parent, final MenuEntry menuEntry) { void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) {
systemTray.setImage(GREEN_MAIL); systemTray.setImage(GREEN_MAIL);
systemTray.setStatus("Some Mail!"); systemTray.setStatus("Some Mail!");
menuEntry.setCallback(callbackGray); entry.setCallback(callbackGray);
menuEntry.setImage(BLACK_MAIL); entry.setImage(BLACK_MAIL);
menuEntry.setText("Delete Mail"); entry.setText("Delete Mail");
// systemTray.remove(menuEntry); // systemTray.remove(menuEntry);
} }
}; };
@ -104,18 +104,18 @@ class TestTrayJavaFX extends Application {
callbackGray = new SystemTrayMenuAction() { callbackGray = new SystemTrayMenuAction() {
@Override @Override
public public
void onClick(final SystemTray systemTray, final Menu parent, final MenuEntry menuEntry) { void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) {
systemTray.setStatus(null); systemTray.setStatus(null);
systemTray.setImage(BLACK_MAIL); systemTray.setImage(BLACK_MAIL);
menuEntry.setCallback(null); entry.setCallback(null);
// systemTray.setStatus("Mail Empty"); // systemTray.setStatus("Mail Empty");
systemTray.remove(menuEntry); systemTray.remove(entry);
System.err.println("POW"); System.err.println("POW");
} }
}; };
MenuEntry menuEntry = this.systemTray.addEntry("Green Mail", GREEN_MAIL, callbackGreen); Entry menuEntry = this.systemTray.addEntry("Green Mail", GREEN_MAIL, callbackGreen);
// case does not matter // case does not matter
menuEntry.setShortcut('G'); menuEntry.setShortcut('G');
@ -125,14 +125,14 @@ class TestTrayJavaFX extends Application {
submenu.addEntry("Disable menu", LT_GRAY_MAIL, new SystemTrayMenuAction() { submenu.addEntry("Disable menu", LT_GRAY_MAIL, new SystemTrayMenuAction() {
@Override @Override
public public
void onClick(final SystemTray systemTray, final Menu parent, final MenuEntry entry) { void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) {
submenu.setEnabled(false); submenu.setEnabled(false);
} }
}); });
submenu.addEntry("Remove menu", GREEN_MAIL, new SystemTrayMenuAction() { submenu.addEntry("Remove menu", GREEN_MAIL, new SystemTrayMenuAction() {
@Override @Override
public public
void onClick(final SystemTray systemTray, final Menu parent, final MenuEntry entry) { void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) {
submenu.remove(); submenu.remove();
} }
}); });
@ -140,7 +140,7 @@ class TestTrayJavaFX extends Application {
systemTray.addEntry("Quit", new SystemTrayMenuAction() { systemTray.addEntry("Quit", new SystemTrayMenuAction() {
@Override @Override
public public
void onClick(final SystemTray systemTray, final Menu parent, final MenuEntry menuEntry) { void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) {
systemTray.shutdown(); systemTray.shutdown();
Platform.exit(); // necessary to close javaFx Platform.exit(); // necessary to close javaFx
//System.exit(0); not necessary if all non-daemon threads have stopped. //System.exit(0); not necessary if all non-daemon threads have stopped.

View File

@ -23,8 +23,8 @@ import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.Text;
import dorkbox.systemTray.Entry;
import dorkbox.systemTray.Menu; import dorkbox.systemTray.Menu;
import dorkbox.systemTray.MenuEntry;
import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.SystemTrayMenuAction; import dorkbox.systemTray.SystemTrayMenuAction;
@ -74,13 +74,13 @@ class TestTraySwt {
callbackGreen = new SystemTrayMenuAction() { callbackGreen = new SystemTrayMenuAction() {
@Override @Override
public public
void onClick(final SystemTray systemTray, final Menu parent, final MenuEntry menuEntry) { void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) {
systemTray.setStatus("Some Mail!"); systemTray.setStatus("Some Mail!");
systemTray.setImage(GREEN_MAIL); systemTray.setImage(GREEN_MAIL);
menuEntry.setCallback(callbackGray); entry.setCallback(callbackGray);
menuEntry.setImage(BLACK_MAIL); entry.setImage(BLACK_MAIL);
menuEntry.setText("Delete Mail"); entry.setText("Delete Mail");
// systemTray.remove(menuEntry); // systemTray.remove(menuEntry);
} }
}; };
@ -88,18 +88,18 @@ class TestTraySwt {
callbackGray = new SystemTrayMenuAction() { callbackGray = new SystemTrayMenuAction() {
@Override @Override
public public
void onClick(final SystemTray systemTray, final Menu parent, final MenuEntry menuEntry) { void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) {
systemTray.setStatus(null); systemTray.setStatus(null);
systemTray.setImage(BLACK_MAIL); systemTray.setImage(BLACK_MAIL);
menuEntry.setCallback(null); entry.setCallback(null);
// systemTray.setStatus("Mail Empty"); // systemTray.setStatus("Mail Empty");
systemTray.remove(menuEntry); systemTray.remove(entry);
System.err.println("POW"); System.err.println("POW");
} }
}; };
MenuEntry menuEntry = this.systemTray.addEntry("Green Mail", GREEN_MAIL, callbackGreen); Entry menuEntry = this.systemTray.addEntry("Green Mail", GREEN_MAIL, callbackGreen);
// case does not matter // case does not matter
menuEntry.setShortcut('G'); menuEntry.setShortcut('G');
@ -109,14 +109,14 @@ class TestTraySwt {
submenu.addEntry("Disable menu", LT_GRAY_MAIL, new SystemTrayMenuAction() { submenu.addEntry("Disable menu", LT_GRAY_MAIL, new SystemTrayMenuAction() {
@Override @Override
public public
void onClick(final SystemTray systemTray, final Menu parent, final MenuEntry entry) { void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) {
submenu.setEnabled(false); submenu.setEnabled(false);
} }
}); });
submenu.addEntry("Remove menu", GREEN_MAIL, new SystemTrayMenuAction() { submenu.addEntry("Remove menu", GREEN_MAIL, new SystemTrayMenuAction() {
@Override @Override
public public
void onClick(final SystemTray systemTray, final Menu parent, final MenuEntry entry) { void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) {
submenu.remove(); submenu.remove();
} }
}); });
@ -124,7 +124,7 @@ class TestTraySwt {
systemTray.addEntry("Quit", new SystemTrayMenuAction() { systemTray.addEntry("Quit", new SystemTrayMenuAction() {
@Override @Override
public public
void onClick(final SystemTray systemTray, final Menu parent, final MenuEntry menuEntry) { void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) {
systemTray.shutdown(); systemTray.shutdown();
display.asyncExec(new Runnable() { display.asyncExec(new Runnable() {