diff --git a/src/dorkbox/systemTray/Menu.java b/src/dorkbox/systemTray/Menu.java index 205f12e..bcfa76b 100644 --- a/src/dorkbox/systemTray/Menu.java +++ b/src/dorkbox/systemTray/Menu.java @@ -20,7 +20,6 @@ import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.Iterator; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import dorkbox.systemTray.util.ImageUtils; @@ -33,7 +32,7 @@ public abstract class Menu { public static final AtomicInteger MENU_ID_COUNTER = new AtomicInteger(); private final int id = Menu.MENU_ID_COUNTER.getAndIncrement(); -// TODO: enable/disable entries and menus? + protected final java.util.List menuEntries = new ArrayList(); private final SystemTray systemTray; @@ -92,6 +91,12 @@ class Menu { protected abstract Menu addMenu_(final String menuText, final File imagePath); + /* + * Called when this menu is removed from it's parent menu + */ + protected abstract + void removePrivate(); + /* * Necessary to guarantee all updates occur on the dispatch thread */ @@ -104,6 +109,13 @@ class Menu { protected abstract void dispatchAndWait(Runnable runnable); + /** + * Enables, or disables the sub-menu entry. + */ + public abstract + void setEnabled(final boolean enabled); + + /** * Gets the menu entry for a specified text * @@ -353,9 +365,6 @@ class Menu { } - - - /** * This removes a menu entry from the dropdown menu. * @@ -367,9 +376,6 @@ class Menu { throw new NullPointerException("No menu entry exists for menuEntry"); } - // have to wait for the value - final AtomicBoolean hasValue = new AtomicBoolean(false); - dispatchAndWait(new Runnable() { @Override public @@ -383,7 +389,6 @@ class Menu { // this will also reset the menu menuEntry.remove(); - hasValue.set(true); break; } } @@ -406,10 +411,6 @@ class Menu { } } }); - - if (!hasValue.get()) { - throw new NullPointerException("Menu entry '" + menuEntry.getText() + "'not found in list while trying to remove it."); - } } /** @@ -417,17 +418,51 @@ class 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 menu entry exists for menuEntry"); } + dispatchAndWait(new Runnable() { + @SuppressWarnings("Duplicates") + @Override + public + void run() { + try { + synchronized (menuEntries) { + for (Iterator iterator = menuEntries.iterator(); iterator.hasNext(); ) { + final MenuEntry entry = iterator.next(); + if (entry == menu) { + iterator.remove(); + // this will also reset the menu + menu.removePrivate(); + 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 menu entry from list.", e); + } + } + }); } - /** * This removes a menu entry or sub-menu (via the text label) from the dropdown menu. * @@ -435,9 +470,6 @@ class Menu { */ public void remove(final String menuText) { - // have to wait for the value - final AtomicBoolean hasValue = new AtomicBoolean(true); - dispatchAndWait(new Runnable() { @Override public @@ -445,18 +477,11 @@ class Menu { synchronized (menuEntries) { MenuEntry menuEntry = get(menuText); - if (menuEntry == null) { - hasValue.set(false); - } - else { + if (menuEntry != null) { remove(menuEntry); } } } }); - - if (!hasValue.get()) { - throw new NullPointerException("No menu entry exists for string '" + menuText + "'"); - } } } diff --git a/src/dorkbox/systemTray/MenuEntry.java b/src/dorkbox/systemTray/MenuEntry.java index 496122a..2b3e37e 100644 --- a/src/dorkbox/systemTray/MenuEntry.java +++ b/src/dorkbox/systemTray/MenuEntry.java @@ -31,6 +31,11 @@ interface MenuEntry { */ Menu getParent(); + /** + * Enables, or disables the entry. + */ + void setEnabled(final boolean enabled); + /** * @return the text label that the menu entry has assigned */ diff --git a/src/dorkbox/systemTray/SystemTray.java b/src/dorkbox/systemTray/SystemTray.java index c6d02c0..ebb11ec 100644 --- a/src/dorkbox/systemTray/SystemTray.java +++ b/src/dorkbox/systemTray/SystemTray.java @@ -104,7 +104,7 @@ class SystemTray extends Menu { *

* This is an advanced feature, and it is recommended to leave at 0. */ - public static int FORCE_TRAY_TYPE = 0; + public static int FORCE_TRAY_TYPE = 3; @Property /** @@ -812,6 +812,14 @@ class SystemTray extends Menu { return null; } + /** + * @return the attached menu to this system tray + */ + public + Menu getMenu() { + return systemTrayMenu; + } + /** * 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 * menu, will also be removed. For example: @@ -825,34 +833,73 @@ class SystemTray extends Menu { * Entry3 (deleted) */ public - void addMenuSpacer() { - systemTrayMenu.addMenuSpacer(); + void addSeparator() { + systemTrayMenu.addSeparator(); } + + // 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. + @Override + public + void setEnabled(final boolean enabled) { + } + + // NO OP. + @Override + protected + void dispatch(final Runnable runnable) { + } + + // NO OP. + @Override + protected + void dispatchAndWait(final Runnable runnable) { + } + + // NO OP. + protected + void removePrivate() { + } + + /** * 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 getMenuEntry(final String menuText) { - return systemTrayMenu.getMenuEntry(menuText); + MenuEntry get(final String menuText) { + return systemTrayMenu.get(menuText); } /** * Gets the first menu entry, ignoring status and spacers */ public final - MenuEntry getFirstMenuEntry() { - return systemTrayMenu.getFirstMenuEntry(); + MenuEntry getFirst() { + return systemTrayMenu.getFirst(); } /** * Gets the last menu entry, ignoring status and spacers */ public final - MenuEntry getLastMenuEntry() { - return systemTrayMenu.getLastMenuEntry(); + MenuEntry getLast() { + return systemTrayMenu.getLast(); } /** @@ -861,10 +908,14 @@ class SystemTray extends Menu { * @param menuIndex the menu entry index to use to retrieve the menu entry. */ public final - MenuEntry getMenuEntry(final int menuIndex) { - return systemTrayMenu.getMenuEntry(menuIndex); + MenuEntry get(final int menuIndex) { + return systemTrayMenu.get(menuIndex); } + + + + /** * Adds a menu entry to the tray icon with text (no image) * @@ -872,8 +923,8 @@ class SystemTray extends Menu { * @param callback callback that will be executed when this menu entry is clicked */ public final - void addMenuEntry(String menuText, SystemTrayMenuAction callback) { - addMenuEntry(menuText, (String) null, callback); + MenuEntry addEntry(String menuText, SystemTrayMenuAction callback) { + return addEntry(menuText, (String) null, callback); } /** @@ -884,8 +935,8 @@ class SystemTray extends Menu { * @param callback callback that will be executed when this menu entry is clicked */ public final - void addMenuEntry(String menuText, String imagePath, SystemTrayMenuAction callback) { - systemTrayMenu.addMenuEntry(menuText, imagePath, callback); + MenuEntry addEntry(String menuText, String imagePath, SystemTrayMenuAction callback) { + return systemTrayMenu.addEntry(menuText, imagePath, callback); } /** @@ -896,8 +947,8 @@ class SystemTray extends Menu { * @param callback callback that will be executed when this menu entry is clicked */ public final - void addMenuEntry(String menuText, URL imageUrl, SystemTrayMenuAction callback) { - systemTrayMenu.addMenuEntry(menuText, imageUrl, callback); + MenuEntry addEntry(String menuText, URL imageUrl, SystemTrayMenuAction callback) { + return systemTrayMenu.addEntry(menuText, imageUrl, callback); } /** @@ -909,8 +960,8 @@ class SystemTray extends Menu { * @param callback callback that will be executed when this menu entry is clicked */ public - void addMenuEntry(String menuText, String cacheName, InputStream imageStream, SystemTrayMenuAction callback) { - systemTrayMenu.addMenuEntry(menuText, cacheName, imageStream, callback); + MenuEntry addEntry(String menuText, String cacheName, InputStream imageStream, SystemTrayMenuAction callback) { + return systemTrayMenu.addEntry(menuText, cacheName, imageStream, callback); } /** @@ -921,18 +972,80 @@ class SystemTray extends Menu { * @param callback callback that will be executed when this menu entry is clicked */ public final - void addMenuEntry(String menuText, InputStream imageStream, SystemTrayMenuAction callback) { - systemTrayMenu.addMenuEntry(menuText, imageStream, callback); + MenuEntry addEntry(String menuText, InputStream imageStream, SystemTrayMenuAction callback) { + return systemTrayMenu.addEntry(menuText, imageStream, callback); } + + + + + /** + * Adds a sub-menu entry with text (no image) + * + * @param menuText string of the text you want to appear + */ + public + Menu addMenu(String menuText) { + return addMenu(menuText, (String) null); + } + + /** + * Adds a sub-menu entry with text + image + * + * @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) { + return systemTrayMenu.addMenu(menuText, imagePath); + } + + /** + * Adds a sub-menu entry with text + image + * + * @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) { + return systemTrayMenu.addMenu(menuText, imageUrl); + } + + /** + * Adds a sub-menu entry with text + image + * + * @param menuText string of the text you want to appear + * @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) { + return systemTrayMenu.addMenu(menuText, cacheName, imageStream); + } + + /** + * Adds a sub-menu entry with text + image + * + * @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) { + return systemTrayMenu.addMenu(menuText, imageStream); + } + + + + /** * This removes a menu entry from the dropdown menu. * * @param menuEntry This is the menu entry to remove */ public final - void removeMenuEntry(final MenuEntry menuEntry) { - systemTrayMenu.removeMenuEntry(menuEntry); + void remove(final MenuEntry menuEntry) { + systemTrayMenu.remove(menuEntry); } @@ -942,8 +1055,8 @@ class SystemTray extends Menu { * @param menuText This is the label for the menu entry to remove */ public final - void removeMenuEntry(final String menuText) { - systemTrayMenu.removeMenuEntry(menuText); + void remove(final String menuText) { + systemTrayMenu.remove(menuText); } } diff --git a/src/dorkbox/systemTray/linux/GtkEntry.java b/src/dorkbox/systemTray/linux/GtkEntry.java index ab280b5..8d92469 100644 --- a/src/dorkbox/systemTray/linux/GtkEntry.java +++ b/src/dorkbox/systemTray/linux/GtkEntry.java @@ -67,6 +67,18 @@ class GtkEntry implements MenuEntry { abstract void setImage_(final File imageFile); + /** + * Enables, or disables the sub-menu entry. + */ + @Override + public + void setEnabled(final boolean enabled) { + if (enabled) { + Gtk.gtk_widget_set_sensitive(_native, Gtk.TRUE); + } else { + Gtk.gtk_widget_set_sensitive(_native, Gtk.FALSE); + } + } @Override public diff --git a/src/dorkbox/systemTray/linux/GtkEntrySeparator.java b/src/dorkbox/systemTray/linux/GtkEntrySeparator.java index 94bca52..7b5e481 100644 --- a/src/dorkbox/systemTray/linux/GtkEntrySeparator.java +++ b/src/dorkbox/systemTray/linux/GtkEntrySeparator.java @@ -57,6 +57,10 @@ class GtkEntrySeparator extends GtkEntry implements MenuSpacer { @Override public void setCallback(final SystemTrayMenuAction callback) { + } + @Override + public + void setEnabled(final boolean enabled) { } } diff --git a/src/dorkbox/systemTray/linux/GtkEntryStatus.java b/src/dorkbox/systemTray/linux/GtkEntryStatus.java index a493e8f..f50a599 100644 --- a/src/dorkbox/systemTray/linux/GtkEntryStatus.java +++ b/src/dorkbox/systemTray/linux/GtkEntryStatus.java @@ -53,4 +53,9 @@ class GtkEntryStatus extends GtkEntryItem { public void setCallback(final SystemTrayMenuAction callback) { } + + @Override + public + void setEnabled(final boolean enabled) { + } } diff --git a/src/dorkbox/systemTray/linux/GtkMenu.java b/src/dorkbox/systemTray/linux/GtkMenu.java index c276146..b223b38 100644 --- a/src/dorkbox/systemTray/linux/GtkMenu.java +++ b/src/dorkbox/systemTray/linux/GtkMenu.java @@ -105,6 +105,19 @@ class GtkMenu extends Menu implements MenuEntry { }); } + /** + * Enables, or disables the sub-menu entry. + */ + @Override + public + void setEnabled(final boolean enabled) { + if (enabled) { + Gtk.gtk_widget_set_sensitive(menuEntry._native, Gtk.TRUE); + } else { + Gtk.gtk_widget_set_sensitive(menuEntry._native, Gtk.FALSE); + } + } + @Override public void addSeparator() { diff --git a/src/dorkbox/systemTray/swing/SwingEntry.java b/src/dorkbox/systemTray/swing/SwingEntry.java index 0787085..f39e975 100644 --- a/src/dorkbox/systemTray/swing/SwingEntry.java +++ b/src/dorkbox/systemTray/swing/SwingEntry.java @@ -59,6 +59,14 @@ class SwingEntry implements MenuEntry { abstract void setImage_(final File imageFile); + /** + * Enables, or disables the sub-menu entry. + */ + @Override + public + void setEnabled(final boolean enabled) { + _native.setEnabled(enabled); + } @Override public diff --git a/src/dorkbox/systemTray/swing/SwingMenu.java b/src/dorkbox/systemTray/swing/SwingMenu.java index d713e0e..63f766b 100644 --- a/src/dorkbox/systemTray/swing/SwingMenu.java +++ b/src/dorkbox/systemTray/swing/SwingMenu.java @@ -58,11 +58,11 @@ class SwingMenu extends Menu implements MenuEntry { public void run() { if (parent != null) { - if (!OS.isLinux()) { - _native = new AdjustedJMenu(null); + if (OS.isLinux()) { + _native = new AdjustedJMenu((SwingSystemTrayLinuxMenuPopup)((SwingMenu) systemTray.getMenu())._native); } else { - _native = new AdjustedJMenu((SwingSystemTrayLinuxMenuPopup)((SwingMenu) systemTray.getMenu())._native); + _native = new AdjustedJMenu(null); } ((SwingMenu) parent)._native.add(_native); @@ -108,6 +108,15 @@ class SwingMenu extends Menu implements 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 */ @@ -303,8 +312,27 @@ class SwingMenu extends Menu implements MenuEntry { @Override public void run() { - ((SwingMenu) getParent())._native.remove(_native); + _native.setVisible(false); + if (_native instanceof SwingSystemTrayMenuPopup) { + ((SwingSystemTrayMenuPopup) _native).close(); + } + else if (_native instanceof SwingSystemTrayLinuxMenuPopup) { + ((SwingSystemTrayLinuxMenuPopup) _native).close(); + } + + SwingMenu parent = (SwingMenu) getParent(); + if (parent != null) { + parent._native.remove(_native); + } } }); } + + /* + * Called when this menu is removed from it's parent menu + */ + protected + void removePrivate() { + remove(); + } } diff --git a/src/dorkbox/systemTray/swing/SwingSystemTray.java b/src/dorkbox/systemTray/swing/SwingSystemTray.java index 6f042fd..47757e7 100644 --- a/src/dorkbox/systemTray/swing/SwingSystemTray.java +++ b/src/dorkbox/systemTray/swing/SwingSystemTray.java @@ -80,6 +80,8 @@ class SwingSystemTray extends SwingMenu { } menuEntries.clear(); } + + remove(); } }); } diff --git a/src/dorkbox/systemTray/swing/SwingSystemTrayLinuxMenuPopup.java b/src/dorkbox/systemTray/swing/SwingSystemTrayLinuxMenuPopup.java index 7613fce..fb92bc5 100644 --- a/src/dorkbox/systemTray/swing/SwingSystemTrayLinuxMenuPopup.java +++ b/src/dorkbox/systemTray/swing/SwingSystemTrayLinuxMenuPopup.java @@ -156,4 +156,9 @@ class SwingSystemTrayLinuxMenuPopup extends JPopupMenu { // restart the timer SwingSystemTrayLinuxMenuPopup.this.timer.delay(POPUP_HIDE_DELAY); } + + public + void close() { + this.timer.cancel(); + } } diff --git a/src/dorkbox/systemTray/swing/SwingSystemTrayMenuPopup.java b/src/dorkbox/systemTray/swing/SwingSystemTrayMenuPopup.java index 4a9b9ab..4ad7ab4 100644 --- a/src/dorkbox/systemTray/swing/SwingSystemTrayMenuPopup.java +++ b/src/dorkbox/systemTray/swing/SwingSystemTrayMenuPopup.java @@ -46,6 +46,7 @@ class SwingSystemTrayMenuPopup extends JPopupMenu { this.hiddenDialog = new JDialog((Frame)null); this.hiddenDialog.setEnabled(false); this.hiddenDialog.setUndecorated(true); + this.hiddenDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); this.hiddenDialog.setSize(1, 1); @@ -68,4 +69,10 @@ class SwingSystemTrayMenuPopup extends JPopupMenu { super.setVisible(makeVisible); } + + public + void close() { + this.hiddenDialog.setVisible(false); + this.hiddenDialog.dispatchEvent(new WindowEvent(this.hiddenDialog, WindowEvent.WINDOW_CLOSING)); + } }