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
*/
public
interface MenuEntry {
interface Entry {
/**
* @return the menu that contains this menu entry

View File

@ -15,54 +15,24 @@
*/
package dorkbox.systemTray;
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 dorkbox.systemTray.util.ImageUtils;
/**
* Represents a cross-platform menu that is displayed by the tray-icon or as a sub-menu
*/
@SuppressWarnings({"WeakerAccess", "unused"})
public abstract
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;
}
public
interface Menu extends Entry {
/**
* @return the parent menu (of this menu) or null if we are the root menu
*/
public
Menu getParent() {
return parent;
}
Menu getParent();
/**
* @return the system tray that this menu is ultimately attached to
*/
public
SystemTray getSystemTray() {
return systemTray;
}
SystemTray getSystemTray();
/**
* 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)
* Entry3 (deleted)
*/
public abstract
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 setEnabled(final boolean enabled);
void clear();
/**
* 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
*/
public
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;
}
Entry get(final String menuText);
/**
* Gets the first menu entry or sub-menu, ignoring status and spacers
*/
public
MenuEntry getFirst() {
return get(0);
}
Entry getFirst();
/**
* Gets the last menu entry or sub-menu, ignoring status and spacers
*/
public
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;
}
Entry getLast();
/**
* 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.
*/
public
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;
}
Entry get(final int menuIndex);
@ -206,10 +86,7 @@ class Menu implements MenuEntry {
* @param menuText string of the text you want to appear
* @param callback callback that will be executed when this menu entry is clicked
*/
public
MenuEntry addEntry(String menuText, SystemTrayMenuAction callback) {
return addEntry(menuText, (String) null, callback);
}
Entry addEntry(String menuText, SystemTrayMenuAction callback);
/**
* 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 callback callback that will be executed when this menu entry is clicked
*/
public
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);
}
}
Entry addEntry(String menuText, String imagePath, SystemTrayMenuAction callback);
/**
* 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 callback callback that will be executed when this menu entry is clicked
*/
public
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);
}
}
Entry addEntry(String menuText, URL imageUrl, SystemTrayMenuAction callback);
/**
* 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 callback callback that will be executed when this menu entry is clicked
*/
public
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);
}
}
Entry addEntry(String menuText, String cacheName, InputStream imageStream, SystemTrayMenuAction callback);
/**
* 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 callback callback that will be executed when this menu entry is clicked
*/
public
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);
}
}
Entry addEntry(String menuText, InputStream imageStream, SystemTrayMenuAction callback);
@ -288,10 +133,7 @@ class Menu implements MenuEntry {
*
* @param menuText string of the text you want to appear
*/
public
Menu addMenu(String menuText) {
return addMenu(menuText, (String) null);
}
Menu addMenu(String menuText);
/**
* 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 imagePath the image (full path required) to use. If null, no image will be used
*/
public
Menu addMenu(String menuText, String imagePath) {
if (imagePath == null) {
return addMenu_(menuText, null);
}
else {
return addMenu_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imagePath));
}
}
Menu addMenu(String menuText, String imagePath);
/**
* 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 imageUrl the URL of the image to use. If null, no image will be used
*/
public
Menu addMenu(String menuText, URL imageUrl) {
if (imageUrl == null) {
return addMenu_(menuText, null);
}
else {
return addMenu_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageUrl));
}
}
Menu addMenu(String menuText, URL imageUrl);
/**
* 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 imageStream the InputStream of the image to use. If null, no image will be used
*/
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));
}
}
Menu addMenu(String menuText, String cacheName, InputStream imageStream);
/**
* 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 imageStream the InputStream of the image to use. If null, no image will be used
*/
public
Menu addMenu(String menuText, InputStream imageStream) {
if (imageStream == null) {
return addMenu_(menuText, null);
}
else {
return addMenu_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageStream));
}
}
Menu addMenu(String menuText, InputStream imageStream);
/**
* 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 MenuEntry menuEntry) {
if (menuEntry == null) {
throw new NullPointerException("No menu entry exists for menuEntry");
}
dispatchAndWait(new Runnable() {
@Override
public
void run() {
remove__(menuEntry);
}
});
}
void remove(final Entry entry);
/**
* This removes a sub-menu entry from the dropdown menu.
*
* @param menu This is the menu entry to remove
*/
@SuppressWarnings("Duplicates")
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);
}
}
void remove(final Menu 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
*/
public
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);
}
void remove(final String menuText);
}

View File

@ -20,6 +20,5 @@ package dorkbox.systemTray;
* This represents a common menu-spacer entry, that is cross platform in nature
*/
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
*/
public
interface MenuStatus {
interface Status {
}

View File

@ -24,6 +24,7 @@ import java.io.FileReader;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.URL;
import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -34,7 +35,6 @@ import dorkbox.systemTray.linux.jna.Gtk;
import dorkbox.systemTray.swing._AppIndicatorTray;
import dorkbox.systemTray.swing._GtkStatusIconTray;
import dorkbox.systemTray.swing._SwingTray;
import dorkbox.systemTray.util.ImageUtils;
import dorkbox.systemTray.util.JavaFX;
import dorkbox.systemTray.util.Swt;
import dorkbox.systemTray.util.WindowsSystemTraySwing;
@ -51,7 +51,7 @@ import dorkbox.util.process.ShellProcessBuilder;
*/
@SuppressWarnings({"unused", "Duplicates", "DanglingJavadoc", "WeakerAccess"})
public
class SystemTray extends Menu {
class SystemTray implements Menu {
public static final Logger logger = LoggerFactory.getLogger(SystemTray.class);
public static final int TYPE_AUTO_DETECT = 0;
@ -559,7 +559,7 @@ class SystemTray extends Menu {
systemTrayMenu = null;
}
else {
final Menu[] menuReference = new Menu[1];
final AtomicReference<Menu> reference = new AtomicReference<Menu>();
/*
* appIndicator/gtk require strings (which is the path)
@ -602,7 +602,7 @@ class SystemTray extends Menu {
public
void run() {
try {
menuReference[0] = (Menu) finalTrayType.getConstructors()[0].newInstance(systemTray);
reference.set((Menu) finalTrayType.getConstructors()[0].newInstance(systemTray));
logger.info("Successfully Loaded: {}", finalTrayType.getSimpleName());
} catch (Exception 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);
}
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.
@ -670,13 +670,6 @@ class SystemTray extends Menu {
return systemTray;
}
private
SystemTray() {
super(null, null);
}
public
void shutdown() {
final Menu menu = systemTrayMenu;
@ -717,6 +710,7 @@ class SystemTray extends Menu {
public
void setStatus(String statusText) {
final Menu menu = systemTrayMenu;
if (menu instanceof _AppIndicatorTray) {
((_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
*/
@ -750,6 +730,12 @@ class SystemTray extends Menu {
return null;
}
@Override
public
SystemTray getSystemTray() {
return this;
}
/**
* @return the attached menu to this system tray
*/
@ -776,107 +762,113 @@ class SystemTray extends Menu {
}
// NO OP.
@Override
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.
/**
* Does nothing. You cannot set the text for the system tray
*/
@Override
public
void setEnabled(final boolean enabled) {
}
/**
* Does nothing. You cannot get the text for the system tray
*/
@Override
public
String getText() {
return "";
}
// NO OP.
/**
* Does nothing. You cannot set the text for the system tray
*/
@Override
public
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,
* this will directly use the contents of the specified file.
*
* @param imageFile the path of the image to use
* @param imageFile the file of the image to use or null
*/
@Override
public
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,
* this will directly use the contents of the specified file.
*
* @param imagePath the path of the image to use
* @param imagePath the full path of the image to use or null
*/
@Override
public
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
* 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
* @param imageUrl the URL of the image to use or null
*/
@Override
public
void setImage(URL imageUrl) {
setImage_(ImageUtils.resizeAndCache(ImageUtils.TRAY_SIZE, imageUrl));
void setImage(final URL imageUrl) {
if (imageUrl == null) {
throw new NullPointerException("imageUrl cannot be null!");
}
systemTrayMenu.setImage(imageUrl);
}
/**
* Changes the tray image used.
*
* 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.
* Specifies the new image to set for a menu entry, NULL to delete the image
*
* @param cacheName the name to use for lookup in the cache for the imageStream
* @param imageStream the InputStream of the image to use
*/
@Override
public
void setImage(String cacheName, InputStream imageStream) {
setImage_(ImageUtils.resizeAndCache(ImageUtils.TRAY_SIZE, cacheName, imageStream));
void setImage(final String cacheName, final InputStream 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
* the imageStream to a temporary location on disk.
* This method **DOES NOT CACHE** the result, so multiple lookups for the same inputStream result in new files every time. This is
* also NOT RECOMMENDED, but is provided for simplicity.
*
* @param imageStream the InputStream of the image to use
*/
@Override
public
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;
}
// NO OP.
/**
* Does nothing. The system tray cannot have a callback when opened
*/
@Override
public
void setCallback(final SystemTrayMenuAction callback) {
// NO OP.
}
// NO OP.
/**
* Does nothing. The system tray cannot be opened via a shortcut key
*/
@Override
public
void setShortcut(final char key) {
}
// NO OP.
@Override
protected
void dispatch(final Runnable runnable) {
}
// NO OP.
@Override
protected
void dispatchAndWait(final Runnable runnable) {
}
// NO OP.
protected
void removePrivate() {
// NO OP.
}
/**
@ -925,14 +906,13 @@ class SystemTray extends Menu {
shutdown();
}
/**
* 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
*/
public final
MenuEntry get(final String menuText) {
Entry get(final String menuText) {
return systemTrayMenu.get(menuText);
}
@ -940,7 +920,7 @@ class SystemTray extends Menu {
* Gets the first menu entry, ignoring status and spacers
*/
public final
MenuEntry getFirst() {
Entry getFirst() {
return systemTrayMenu.getFirst();
}
@ -948,7 +928,7 @@ class SystemTray extends Menu {
* Gets the last menu entry, ignoring status and spacers
*/
public final
MenuEntry getLast() {
Entry 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.
*/
public final
MenuEntry get(final int menuIndex) {
Entry get(final int 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
*/
public final
MenuEntry addEntry(String menuText, SystemTrayMenuAction callback) {
Entry addEntry(String menuText, SystemTrayMenuAction 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
*/
public final
MenuEntry addEntry(String menuText, String imagePath, SystemTrayMenuAction callback) {
Entry addEntry(String menuText, String imagePath, SystemTrayMenuAction 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
*/
public final
MenuEntry addEntry(String menuText, URL imageUrl, SystemTrayMenuAction callback) {
Entry addEntry(String menuText, URL imageUrl, SystemTrayMenuAction 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
*/
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);
}
@ -1022,7 +1002,7 @@ class SystemTray extends Menu {
* @param callback callback that will be executed when this menu entry is clicked
*/
public final
MenuEntry addEntry(String menuText, InputStream imageStream, SystemTrayMenuAction callback) {
Entry addEntry(String menuText, InputStream imageStream, SystemTrayMenuAction callback) {
return systemTrayMenu.addEntry(menuText, imageStream, callback);
}
@ -1091,11 +1071,31 @@ class SystemTray extends 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
void remove(final MenuEntry menuEntry) {
systemTrayMenu.remove(menuEntry);
void remove(final Entry entry) {
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
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
* (GtkStatusIcon/AppIndicator based).
* This method will ALWAYS be called in the swing EDT
*
* @param systemTray this is the parent, system tray object
* @param parent this is the parent menu of this menu entry
* @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.JMenuItem;
import dorkbox.systemTray.Entry;
import dorkbox.systemTray.Menu;
import dorkbox.systemTray.MenuEntry;
import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.util.ImageUtils;
import dorkbox.util.SwingUtil;
abstract
class Entry implements MenuEntry {
private final int id = Menu.MENU_ID_COUNTER.getAndIncrement();
class EntryImpl implements Entry {
private final int id = MenuImpl.MENU_ID_COUNTER.getAndIncrement();
private final SwingMenu parent;
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.
Entry(final SwingMenu parent, final JComponent menuItem) {
EntryImpl(final MenuImpl parent, final JComponent menuItem) {
this.parent = parent;
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
void renderText(final String text);
/**
* Not always called on the EDT thread
*/
abstract
void setImage_(final File imageFile);
@ -204,7 +207,7 @@ class Entry implements MenuEntry {
return false;
}
Entry other = (Entry) obj;
EntryImpl other = (EntryImpl) obj;
return this.id == other.id;
}

View File

@ -25,7 +25,7 @@ import javax.swing.JMenuItem;
import dorkbox.systemTray.SystemTrayMenuAction;
import dorkbox.util.SwingUtil;
class EntryItem extends Entry {
class EntryItem extends EntryImpl {
private final ActionListener swingCallback;
@ -33,7 +33,7 @@ class EntryItem extends Entry {
private volatile SystemTrayMenuAction callback;
// 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());
this.callback = callback;

View File

@ -19,13 +19,12 @@ import java.io.File;
import javax.swing.JSeparator;
import dorkbox.systemTray.MenuSpacer;
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.
EntrySeparator(final SwingMenu parent) {
EntrySeparator(final MenuImpl parent) {
super(parent, new JSeparator(JSeparator.HORIZONTAL));
}

View File

@ -15,17 +15,18 @@
*/
package dorkbox.systemTray.swing;
import java.awt.Font;
import java.io.File;
import javax.swing.JMenuItem;
import dorkbox.systemTray.MenuStatus;
import dorkbox.systemTray.Status;
import dorkbox.systemTray.SystemTrayMenuAction;
class EntryStatus extends Entry implements MenuStatus {
class EntryStatus extends EntryImpl implements Status {
// 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());
setText(label);
}
@ -33,11 +34,12 @@ class EntryStatus extends Entry implements MenuStatus {
// called in the EDT thread
@Override
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);
Font font = _native.getFont();
Font font1 = font.deriveFont(Font.BOLD);
_native.setFont(font1);
// this makes sure it can't be selected
_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);
// 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);
// 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));
}
void doShow(final Point point, final int offset) {
void doShow(final Point point, int offset) {
Dimension size = getPreferredSize();
Rectangle bounds = ScreenUtil.getScreenBoundsAt(point);
int x = point.x;
int y = point.y;
if (y < bounds.y) {
y = bounds.y;
}
@ -175,22 +173,19 @@ class TrayPopup extends JPopupMenu {
y -= size.height; // snap to edge of mouse
}
if (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) {
// 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 += offset; // display over the stupid appindicator menu (which has to show, this is a major hack!)
} else {
x -= offset; // display over the stupid appindicator menu (which has to show, this is a major hack!)
offset = -offset; // flip offset
}
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
setInvoker(hiddenDialog.getContentPane());
@ -202,6 +197,4 @@ class TrayPopup extends JPopupMenu {
setVisible(true);
requestFocusInWindow();
}
}

View File

@ -80,17 +80,26 @@ import dorkbox.util.SwingUtil;
* http://bazaar.launchpad.net/~ubuntu-desktop/ido/gtk3/files
*/
public
class _AppIndicatorTray extends GenericTray {
private AppIndicatorInstanceStruct appIndicator;
class _AppIndicatorTray extends _Tray {
private volatile AppIndicatorInstanceStruct appIndicator;
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...)
private AtomicBoolean shuttingDown = new AtomicBoolean();
private volatile NativeLong nativeLong;
private volatile GEventCallback gtkCallback;
// necessary to prevent GC on these objects
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 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
_AppIndicatorTray(final SystemTray systemTray) {
@ -98,7 +107,7 @@ class _AppIndicatorTray extends GenericTray {
if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TYPE_GTK_STATUSICON) {
// 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;
@ -108,6 +117,11 @@ class _AppIndicatorTray extends GenericTray {
@Override
public
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.
Gtk.dispatch(new Runnable() {
@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.dispatch(new Runnable() {
@ -167,7 +177,7 @@ class _AppIndicatorTray extends GenericTray {
@Override
public
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);
}
};
@ -175,7 +185,8 @@ class _AppIndicatorTray extends GenericTray {
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();
Pointer item = Gtk.gtk_image_menu_item_new_with_mnemonic("");
Gtk.gtk_menu_shell_append(dummyMenu, item);
@ -223,7 +234,7 @@ class _AppIndicatorTray extends GenericTray {
// kindof lame, but necessary for KDE
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

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.
*/
public
class _GtkStatusIconTray extends GenericTray {
class _GtkStatusIconTray extends _Tray {
private volatile Pointer trayIcon;
// http://code.metager.de/source/xref/gnome/Platform/gtk%2B/gtk/deprecated/gtkstatusicon.c
@ -58,6 +58,7 @@ class _GtkStatusIconTray extends GenericTray {
public
_GtkStatusIconTray(final SystemTray systemTray) {
super(systemTray, null, new TrayPopup());
if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TYPE_APP_INDICATOR) {
// 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");
@ -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
// trayIcon.setToolTip(_SwingTray.this.appName);
// trayIcon.setToolTip("app name");
Gtk.startGui();

View File

@ -26,7 +26,7 @@ import java.io.File;
import javax.swing.ImageIcon;
import javax.swing.JPopupMenu;
import dorkbox.systemTray.MenuEntry;
import dorkbox.systemTray.Entry;
/**
* Class for handling all system tray interaction, via SWING.
@ -38,7 +38,7 @@ import dorkbox.systemTray.MenuEntry;
*/
@SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "WeakerAccess"})
public
class _SwingTray extends GenericTray {
class _SwingTray extends _Tray {
volatile SystemTray tray;
volatile TrayIcon trayIcon;
@ -59,8 +59,8 @@ class _SwingTray extends GenericTray {
tray.remove(trayIcon);
synchronized (menuEntries) {
for (MenuEntry menuEntry : menuEntries) {
menuEntry.remove();
for (Entry entry : menuEntries) {
entry.remove();
}
menuEntries.clear();
}
@ -88,9 +88,9 @@ class _SwingTray extends GenericTray {
popupMenu.pack();
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
// trayIcon.setToolTip(_SwingTray.this.appName);
// trayIcon.setToolTip("app name");
trayIcon.addMouseListener(new MouseAdapter() {
@Override

View File

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

View File

@ -18,8 +18,8 @@ package dorkbox;
import java.net.URL;
import dorkbox.systemTray.Entry;
import dorkbox.systemTray.Menu;
import dorkbox.systemTray.MenuEntry;
import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.SystemTrayMenuAction;
@ -56,13 +56,13 @@ class TestTray {
callbackGreen = new SystemTrayMenuAction() {
@Override
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.setImage(GREEN_MAIL);
menuEntry.setCallback(callbackGray);
menuEntry.setImage(BLACK_MAIL);
menuEntry.setText("Delete Mail");
entry.setCallback(callbackGray);
entry.setImage(BLACK_MAIL);
entry.setText("Delete Mail");
// systemTray.remove(menuEntry);
}
};
@ -70,18 +70,18 @@ class TestTray {
callbackGray = new SystemTrayMenuAction() {
@Override
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.setImage(BLACK_MAIL);
menuEntry.setCallback(null);
entry.setCallback(null);
// systemTray.setStatus("Mail Empty");
systemTray.remove(menuEntry);
systemTray.remove(entry);
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
menuEntry.setShortcut('G');
@ -91,14 +91,14 @@ class TestTray {
submenu.addEntry("Disable menu", LT_GRAY_MAIL, new SystemTrayMenuAction() {
@Override
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.addEntry("Remove menu", GREEN_MAIL, new SystemTrayMenuAction() {
@Override
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();
}
});
@ -107,7 +107,7 @@ class TestTray {
systemTray.addEntry("Quit", new SystemTrayMenuAction() {
@Override
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();
//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 dorkbox.systemTray.Entry;
import dorkbox.systemTray.Menu;
import dorkbox.systemTray.MenuEntry;
import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.SystemTrayMenuAction;
import javafx.application.Application;
@ -90,13 +90,13 @@ class TestTrayJavaFX extends Application {
callbackGreen = new SystemTrayMenuAction() {
@Override
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.setStatus("Some Mail!");
menuEntry.setCallback(callbackGray);
menuEntry.setImage(BLACK_MAIL);
menuEntry.setText("Delete Mail");
entry.setCallback(callbackGray);
entry.setImage(BLACK_MAIL);
entry.setText("Delete Mail");
// systemTray.remove(menuEntry);
}
};
@ -104,18 +104,18 @@ class TestTrayJavaFX extends Application {
callbackGray = new SystemTrayMenuAction() {
@Override
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.setImage(BLACK_MAIL);
menuEntry.setCallback(null);
entry.setCallback(null);
// systemTray.setStatus("Mail Empty");
systemTray.remove(menuEntry);
systemTray.remove(entry);
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
menuEntry.setShortcut('G');
@ -125,14 +125,14 @@ class TestTrayJavaFX extends Application {
submenu.addEntry("Disable menu", LT_GRAY_MAIL, new SystemTrayMenuAction() {
@Override
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.addEntry("Remove menu", GREEN_MAIL, new SystemTrayMenuAction() {
@Override
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();
}
});
@ -140,7 +140,7 @@ class TestTrayJavaFX extends Application {
systemTray.addEntry("Quit", new SystemTrayMenuAction() {
@Override
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();
Platform.exit(); // necessary to close javaFx
//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.Text;
import dorkbox.systemTray.Entry;
import dorkbox.systemTray.Menu;
import dorkbox.systemTray.MenuEntry;
import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.SystemTrayMenuAction;
@ -74,13 +74,13 @@ class TestTraySwt {
callbackGreen = new SystemTrayMenuAction() {
@Override
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.setImage(GREEN_MAIL);
menuEntry.setCallback(callbackGray);
menuEntry.setImage(BLACK_MAIL);
menuEntry.setText("Delete Mail");
entry.setCallback(callbackGray);
entry.setImage(BLACK_MAIL);
entry.setText("Delete Mail");
// systemTray.remove(menuEntry);
}
};
@ -88,18 +88,18 @@ class TestTraySwt {
callbackGray = new SystemTrayMenuAction() {
@Override
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.setImage(BLACK_MAIL);
menuEntry.setCallback(null);
entry.setCallback(null);
// systemTray.setStatus("Mail Empty");
systemTray.remove(menuEntry);
systemTray.remove(entry);
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
menuEntry.setShortcut('G');
@ -109,14 +109,14 @@ class TestTraySwt {
submenu.addEntry("Disable menu", LT_GRAY_MAIL, new SystemTrayMenuAction() {
@Override
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.addEntry("Remove menu", GREEN_MAIL, new SystemTrayMenuAction() {
@Override
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();
}
});
@ -124,7 +124,7 @@ class TestTraySwt {
systemTray.addEntry("Quit", new SystemTrayMenuAction() {
@Override
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();
display.asyncExec(new Runnable() {