Added setEnabled() for menu+entries, misc bug fixes for windows

This commit is contained in:
nathan 2016-10-03 23:12:00 +02:00
parent ad8cd3709c
commit 7e2834c0cb
12 changed files with 281 additions and 54 deletions

View File

@ -20,7 +20,6 @@ import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import dorkbox.systemTray.util.ImageUtils; import dorkbox.systemTray.util.ImageUtils;
@ -33,7 +32,7 @@ public abstract
class Menu { class Menu {
public static final AtomicInteger MENU_ID_COUNTER = new AtomicInteger(); public static final AtomicInteger MENU_ID_COUNTER = new AtomicInteger();
private final int id = Menu.MENU_ID_COUNTER.getAndIncrement(); private final int id = Menu.MENU_ID_COUNTER.getAndIncrement();
// TODO: enable/disable entries and menus?
protected final java.util.List<MenuEntry> menuEntries = new ArrayList<MenuEntry>(); protected final java.util.List<MenuEntry> menuEntries = new ArrayList<MenuEntry>();
private final SystemTray systemTray; private final SystemTray systemTray;
@ -92,6 +91,12 @@ class Menu {
protected abstract protected abstract
Menu addMenu_(final String menuText, final File imagePath); 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 * Necessary to guarantee all updates occur on the dispatch thread
*/ */
@ -104,6 +109,13 @@ class Menu {
protected abstract protected abstract
void dispatchAndWait(Runnable runnable); 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 * Gets the menu entry for a specified text
* *
@ -353,9 +365,6 @@ class Menu {
} }
/** /**
* This removes a menu entry from the dropdown 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"); throw new NullPointerException("No menu entry exists for menuEntry");
} }
// have to wait for the value
final AtomicBoolean hasValue = new AtomicBoolean(false);
dispatchAndWait(new Runnable() { dispatchAndWait(new Runnable() {
@Override @Override
public public
@ -383,7 +389,6 @@ class Menu {
// this will also reset the menu // this will also reset the menu
menuEntry.remove(); menuEntry.remove();
hasValue.set(true);
break; 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,16 +418,50 @@ class Menu {
* *
* @param menu This is the menu entry to remove * @param menu This is the menu entry to remove
*/ */
@SuppressWarnings("Duplicates")
public public
void remove(final Menu menu) { void remove(final Menu menu) {
if (menu == null) { if (menu == null) {
throw new NullPointerException("No menu entry exists for menuEntry"); throw new NullPointerException("No menu entry exists for menuEntry");
} }
dispatchAndWait(new Runnable() {
@SuppressWarnings("Duplicates")
@Override
public
void run() {
try {
synchronized (menuEntries) {
for (Iterator<MenuEntry> 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. * This removes a menu entry or sub-menu (via the text label) from the dropdown menu.
@ -435,9 +470,6 @@ class Menu {
*/ */
public public
void remove(final String menuText) { void remove(final String menuText) {
// have to wait for the value
final AtomicBoolean hasValue = new AtomicBoolean(true);
dispatchAndWait(new Runnable() { dispatchAndWait(new Runnable() {
@Override @Override
public public
@ -445,18 +477,11 @@ class Menu {
synchronized (menuEntries) { synchronized (menuEntries) {
MenuEntry menuEntry = get(menuText); MenuEntry menuEntry = get(menuText);
if (menuEntry == null) { if (menuEntry != null) {
hasValue.set(false);
}
else {
remove(menuEntry); remove(menuEntry);
} }
} }
} }
}); });
if (!hasValue.get()) {
throw new NullPointerException("No menu entry exists for string '" + menuText + "'");
}
} }
} }

View File

@ -31,6 +31,11 @@ interface MenuEntry {
*/ */
Menu getParent(); Menu getParent();
/**
* Enables, or disables the entry.
*/
void setEnabled(final boolean enabled);
/** /**
* @return the text label that the menu entry has assigned * @return the text label that the menu entry has assigned
*/ */

View File

@ -104,7 +104,7 @@ class SystemTray extends Menu {
* <p> * <p>
* This is an advanced feature, and it is recommended to leave at 0. * This is an advanced feature, and it is recommended to leave at 0.
*/ */
public static int FORCE_TRAY_TYPE = 0; public static int FORCE_TRAY_TYPE = 3;
@Property @Property
/** /**
@ -812,6 +812,14 @@ class SystemTray extends Menu {
return null; 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 * 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: * menu, will also be removed. For example:
@ -825,34 +833,73 @@ class SystemTray extends Menu {
* Entry3 (deleted) * Entry3 (deleted)
*/ */
public public
void addMenuSpacer() { void addSeparator() {
systemTrayMenu.addMenuSpacer(); 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 * 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 getMenuEntry(final String menuText) { MenuEntry get(final String menuText) {
return systemTrayMenu.getMenuEntry(menuText); return systemTrayMenu.get(menuText);
} }
/** /**
* Gets the first menu entry, ignoring status and spacers * Gets the first menu entry, ignoring status and spacers
*/ */
public final public final
MenuEntry getFirstMenuEntry() { MenuEntry getFirst() {
return systemTrayMenu.getFirstMenuEntry(); return systemTrayMenu.getFirst();
} }
/** /**
* Gets the last menu entry, ignoring status and spacers * Gets the last menu entry, ignoring status and spacers
*/ */
public final public final
MenuEntry getLastMenuEntry() { MenuEntry getLast() {
return systemTrayMenu.getLastMenuEntry(); return systemTrayMenu.getLast();
} }
/** /**
@ -861,10 +908,14 @@ 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 getMenuEntry(final int menuIndex) { MenuEntry get(final int menuIndex) {
return systemTrayMenu.getMenuEntry(menuIndex); return systemTrayMenu.get(menuIndex);
} }
/** /**
* Adds a menu entry to the tray icon with text (no image) * 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 * @param callback callback that will be executed when this menu entry is clicked
*/ */
public final public final
void addMenuEntry(String menuText, SystemTrayMenuAction callback) { MenuEntry addEntry(String menuText, SystemTrayMenuAction callback) {
addMenuEntry(menuText, (String) null, 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 * @param callback callback that will be executed when this menu entry is clicked
*/ */
public final public final
void addMenuEntry(String menuText, String imagePath, SystemTrayMenuAction callback) { MenuEntry addEntry(String menuText, String imagePath, SystemTrayMenuAction callback) {
systemTrayMenu.addMenuEntry(menuText, imagePath, 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 * @param callback callback that will be executed when this menu entry is clicked
*/ */
public final public final
void addMenuEntry(String menuText, URL imageUrl, SystemTrayMenuAction callback) { MenuEntry addEntry(String menuText, URL imageUrl, SystemTrayMenuAction callback) {
systemTrayMenu.addMenuEntry(menuText, imageUrl, 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 * @param callback callback that will be executed when this menu entry is clicked
*/ */
public public
void addMenuEntry(String menuText, String cacheName, InputStream imageStream, SystemTrayMenuAction callback) { MenuEntry addEntry(String menuText, String cacheName, InputStream imageStream, SystemTrayMenuAction callback) {
systemTrayMenu.addMenuEntry(menuText, cacheName, imageStream, 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 * @param callback callback that will be executed when this menu entry is clicked
*/ */
public final public final
void addMenuEntry(String menuText, InputStream imageStream, SystemTrayMenuAction callback) { MenuEntry addEntry(String menuText, InputStream imageStream, SystemTrayMenuAction callback) {
systemTrayMenu.addMenuEntry(menuText, imageStream, 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. * This removes a menu entry from the dropdown menu.
* *
* @param menuEntry This is the menu entry to remove * @param menuEntry This is the menu entry to remove
*/ */
public final public final
void removeMenuEntry(final MenuEntry menuEntry) { void remove(final MenuEntry menuEntry) {
systemTrayMenu.removeMenuEntry(menuEntry); systemTrayMenu.remove(menuEntry);
} }
@ -942,8 +1055,8 @@ class SystemTray extends Menu {
* @param menuText This is the label for the menu entry to remove * @param menuText This is the label for the menu entry to remove
*/ */
public final public final
void removeMenuEntry(final String menuText) { void remove(final String menuText) {
systemTrayMenu.removeMenuEntry(menuText); systemTrayMenu.remove(menuText);
} }
} }

View File

@ -67,6 +67,18 @@ class GtkEntry implements MenuEntry {
abstract abstract
void setImage_(final File imageFile); 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 @Override
public public

View File

@ -57,6 +57,10 @@ class GtkEntrySeparator extends GtkEntry implements MenuSpacer {
@Override @Override
public public
void setCallback(final SystemTrayMenuAction callback) { void setCallback(final SystemTrayMenuAction callback) {
}
@Override
public
void setEnabled(final boolean enabled) {
} }
} }

View File

@ -53,4 +53,9 @@ class GtkEntryStatus extends GtkEntryItem {
public public
void setCallback(final SystemTrayMenuAction callback) { void setCallback(final SystemTrayMenuAction callback) {
} }
@Override
public
void setEnabled(final boolean enabled) {
}
} }

View File

@ -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 @Override
public public
void addSeparator() { void addSeparator() {

View File

@ -59,6 +59,14 @@ class SwingEntry implements MenuEntry {
abstract abstract
void setImage_(final File imageFile); void setImage_(final File imageFile);
/**
* Enables, or disables the sub-menu entry.
*/
@Override
public
void setEnabled(final boolean enabled) {
_native.setEnabled(enabled);
}
@Override @Override
public public

View File

@ -58,11 +58,11 @@ class SwingMenu extends Menu implements MenuEntry {
public public
void run() { void run() {
if (parent != null) { if (parent != null) {
if (!OS.isLinux()) { if (OS.isLinux()) {
_native = new AdjustedJMenu(null); _native = new AdjustedJMenu((SwingSystemTrayLinuxMenuPopup)((SwingMenu) systemTray.getMenu())._native);
} }
else { else {
_native = new AdjustedJMenu((SwingSystemTrayLinuxMenuPopup)((SwingMenu) systemTray.getMenu())._native); _native = new AdjustedJMenu(null);
} }
((SwingMenu) parent)._native.add(_native); ((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 * Will add a new menu entry, or update one if it already exists
*/ */
@ -303,8 +312,27 @@ class SwingMenu extends Menu implements MenuEntry {
@Override @Override
public public
void run() { 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();
}
} }

View File

@ -80,6 +80,8 @@ class SwingSystemTray extends SwingMenu {
} }
menuEntries.clear(); menuEntries.clear();
} }
remove();
} }
}); });
} }

View File

@ -156,4 +156,9 @@ class SwingSystemTrayLinuxMenuPopup extends JPopupMenu {
// restart the timer // restart the timer
SwingSystemTrayLinuxMenuPopup.this.timer.delay(POPUP_HIDE_DELAY); SwingSystemTrayLinuxMenuPopup.this.timer.delay(POPUP_HIDE_DELAY);
} }
public
void close() {
this.timer.cancel();
}
} }

View File

@ -46,6 +46,7 @@ class SwingSystemTrayMenuPopup extends JPopupMenu {
this.hiddenDialog = new JDialog((Frame)null); this.hiddenDialog = new JDialog((Frame)null);
this.hiddenDialog.setEnabled(false); this.hiddenDialog.setEnabled(false);
this.hiddenDialog.setUndecorated(true); this.hiddenDialog.setUndecorated(true);
this.hiddenDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
this.hiddenDialog.setSize(1, 1); this.hiddenDialog.setSize(1, 1);
@ -68,4 +69,10 @@ class SwingSystemTrayMenuPopup extends JPopupMenu {
super.setVisible(makeVisible); super.setVisible(makeVisible);
} }
public
void close() {
this.hiddenDialog.setVisible(false);
this.hiddenDialog.dispatchEvent(new WindowEvent(this.hiddenDialog, WindowEvent.WINDOW_CLOSING));
}
} }