Refactored status entries, added spacers to menu

This commit is contained in:
nathan 2016-09-28 02:51:06 +02:00
parent 9fdee240f8
commit c9b0cce1e3
17 changed files with 839 additions and 331 deletions

View File

@ -0,0 +1,25 @@
/*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray;
/**
* This represents a common menu-spacer entry, that is cross platform in nature
*/
public
interface MenuSpacer {
}

View File

@ -0,0 +1,25 @@
/*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray;
/**
* This represents a common menu-status entry, that is cross platform in nature
*/
public
interface MenuStatus {
}

View File

@ -697,9 +697,16 @@ class SystemTray {
* Must be wrapped in a synchronized block for object visibility * Must be wrapped in a synchronized block for object visibility
*/ */
protected protected
MenuEntry getMenuEntry(String menuText) { MenuEntry getMenuEntry(final String menuText) {
if (menuText == null) {
return null;
}
for (MenuEntry entry : menuEntries) { for (MenuEntry entry : menuEntries) {
if (entry.getText().equals(menuText)) { String text = entry.getText();
// text can be null
if (menuText.equals(text)) {
return entry; return entry;
} }
} }
@ -832,7 +839,6 @@ class SystemTray {
* @param imageStream the InputStream of the image to use. If null, no image will be used * @param imageStream the InputStream of the image to use. If null, no image will be used
* @param callback callback that will be executed when this menu entry is clicked * @param callback callback that will be executed when this menu entry is clicked
*/ */
@Deprecated
public abstract public abstract
void addMenuEntry(String menuText, InputStream imageStream, SystemTrayMenuAction callback); void addMenuEntry(String menuText, InputStream imageStream, SystemTrayMenuAction callback);
@ -1169,8 +1175,6 @@ class SystemTray {
throw new NullPointerException("No menu entry exists for menuEntry"); throw new NullPointerException("No menu entry exists for menuEntry");
} }
final String label = menuEntry.getText();
// have to wait for the value // have to wait for the value
final CountDownLatch countDownLatch = new CountDownLatch(1); final CountDownLatch countDownLatch = new CountDownLatch(1);
final AtomicBoolean hasValue = new AtomicBoolean(false); final AtomicBoolean hasValue = new AtomicBoolean(false);
@ -1183,8 +1187,7 @@ class SystemTray {
synchronized (menuEntries) { synchronized (menuEntries) {
for (Iterator<MenuEntry> iterator = menuEntries.iterator(); iterator.hasNext(); ) { for (Iterator<MenuEntry> iterator = menuEntries.iterator(); iterator.hasNext(); ) {
final MenuEntry entry = iterator.next(); final MenuEntry entry = iterator.next();
if (entry.getText() if (entry == menuEntry) {
.equals(label)) {
iterator.remove(); iterator.remove();
// this will also reset the menu // this will also reset the menu
@ -1193,6 +1196,19 @@ class SystemTray {
break; 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) {
removeMenuEntry(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) {
removeMenuEntry(menuEntries.get(menuEntries.size()-1));
}
}
} }
} catch (Exception e) { } catch (Exception e) {
logger.error("Error removing menu entry from list.", e); logger.error("Error removing menu entry from list.", e);
@ -1209,11 +1225,11 @@ class SystemTray {
} }
} catch (InterruptedException e) { } catch (InterruptedException e) {
logger.error("Error removing menu entry: {}", label); logger.error("Error removing menu entry: {}", menuEntry.getText());
} }
if (!hasValue.get()) { if (!hasValue.get()) {
throw new NullPointerException("Menu entry '" + label + "'not found in list while trying to remove it."); throw new NullPointerException("Menu entry '" + menuEntry.getText() + "'not found in list while trying to remove it.");
} }
} }
@ -1261,5 +1277,21 @@ class SystemTray {
throw new NullPointerException("No menu entry exists for string '" + menuText + "'"); throw new NullPointerException("No menu entry exists for string '" + menuText + "'");
} }
} }
/**
* 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:
*
* Original Entry3 deleted Result
*
* <Status> <Status> <Status>
* Entry1 Entry1 Entry1
* Entry2 -> Entry2 -> Entry2
* <Spacer> (deleted)
* Entry3 (deleted)
*/
public abstract
void addMenuSpacer();
} }

View File

@ -18,108 +18,49 @@ package dorkbox.systemTray.linux;
import java.io.File; import java.io.File;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.util.concurrent.atomic.AtomicInteger;
import com.sun.jna.NativeLong;
import com.sun.jna.Pointer; import com.sun.jna.Pointer;
import dorkbox.systemTray.MenuEntry; import dorkbox.systemTray.MenuEntry;
import dorkbox.systemTray.SystemTrayMenuAction;
import dorkbox.systemTray.linux.jna.GCallback;
import dorkbox.systemTray.linux.jna.Gobject;
import dorkbox.systemTray.linux.jna.Gtk; import dorkbox.systemTray.linux.jna.Gtk;
import dorkbox.systemTray.util.ImageUtils; import dorkbox.systemTray.util.ImageUtils;
class GtkMenuEntry implements MenuEntry, GCallback { abstract
private static final AtomicInteger ID_COUNTER = new AtomicInteger(); class GtkMenuEntry implements MenuEntry {
private final int id = ID_COUNTER.getAndIncrement(); private final int id = GtkTypeSystemTray.MENU_ID_COUNTER.getAndIncrement();
final Pointer menuItem; final Pointer menuItem;
final GtkTypeSystemTray parent; final GtkTypeSystemTray systemTray;
@SuppressWarnings({"FieldCanBeLocal", "unused"}) // this have to be volatile, because they can be changed from any thread
private final NativeLong nativeLong;
// these have to be volatile, because they can be changed from any thread
private volatile String text; private volatile String text;
private volatile SystemTrayMenuAction callback;
private volatile Pointer image;
// these are necessary BECAUSE GTK menus look funky as hell when there are some menu entries WITH icons and some WITHOUT
private volatile boolean hasLegitIcon = true;
private static File transparentIcon = null;
/** /**
* called from inside dispatch thread. ONLY creates the menu item, but DOES NOT attach it! * called from inside dispatch thread. ONLY creates the menu item, but DOES NOT attach it!
* this is a FLOATING reference. See: https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#floating-ref * this is a FLOATING reference. See: https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#floating-ref
*/ */
GtkMenuEntry(final String label, final File imagePath, final SystemTrayMenuAction callback, final GtkTypeSystemTray parent) { GtkMenuEntry(Pointer menuItem, final GtkTypeSystemTray systemTray) {
this.parent = parent; this.systemTray = systemTray;
this.text = label; this.menuItem = menuItem;
this.callback = callback;
menuItem = Gtk.gtk_image_menu_item_new_with_label(label);
if (transparentIcon == null) {
transparentIcon = ImageUtils.getTransparentImage(ImageUtils.ENTRY_SIZE);
}
hasLegitIcon = imagePath != null;
if (hasLegitIcon) {
// NOTE: XFCE used to use appindicator3, which DOES NOT support images in the menu. This change was reverted.
// see: https://ask.fedoraproject.org/en/question/23116/how-to-fix-missing-icons-in-program-menus-and-context-menus/
// see: https://git.gnome.org/browse/gtk+/commit/?id=627a03683f5f41efbfc86cc0f10e1b7c11e9bb25
image = Gtk.gtk_image_new_from_file(imagePath.getAbsolutePath());
Gtk.gtk_image_menu_item_set_image(menuItem, image);
// must always re-set always-show after setting the image
Gtk.gtk_image_menu_item_set_always_show_image(menuItem, Gtk.TRUE);
}
nativeLong = Gobject.g_signal_connect_object(menuItem, "activate", this, null, 0);
} }
/** /**
* the menu entry looks FUNKY when there are a mis-match of entries WITH and WITHOUT images * the menu entry looks FUNKY when there are a mis-match of entries WITH and WITHOUT images
* *
* called on the DISPATCH thread * always called on the DISPATCH thread
*/ */
void setSpacerImage(final boolean everyoneElseHasImages) { abstract
if (hasLegitIcon) { void setSpacerImage(final boolean everyoneElseHasImages);
// we have a legit icon, so there is nothing else we can do.
return;
}
if (image != null) { /**
Gtk.gtk_widget_destroy(image); * must always be called in the GTK thread
image = null; */
Gtk.gtk_widget_show_all(menuItem); abstract
} void renderText(final String text);
if (everyoneElseHasImages) { abstract
image = Gtk.gtk_image_new_from_file(transparentIcon.getAbsolutePath()); void setImage_(final File imageFile);
Gtk.gtk_image_menu_item_set_image(menuItem, image);
// must always re-set always-show after setting the image
Gtk.gtk_image_menu_item_set_always_show_image(menuItem, Gtk.TRUE);
}
Gtk.gtk_widget_show_all(menuItem);
}
// called by native code
@Override
public
int callback(final Pointer instance, final Pointer data) {
final SystemTrayMenuAction cb = this.callback;
if (cb != null) {
Gtk.proxyClick(cb, parent, GtkMenuEntry.this);
}
return Gtk.TRUE;
}
@Override @Override
public public
@ -128,50 +69,21 @@ class GtkMenuEntry implements MenuEntry, GCallback {
} }
@Override @Override
public public final
void setText(final String newText) { void setText(final String newText) {
Gtk.dispatch(new Runnable() { text = newText;
@Override
public
void run() {
text = newText;
Gtk.gtk_menu_item_set_label(menuItem, newText);
Gtk.gtk_widget_show_all(menuItem);
}
});
}
private
void setImage_(final File imageFile) {
hasLegitIcon = imageFile != null;
Gtk.dispatch(new Runnable() { Gtk.dispatch(new Runnable() {
@Override @Override
public public
void run() { void run() {
if (image != null) { renderText(text);
Gtk.gtk_widget_destroy(image);
image = null;
Gtk.gtk_widget_show_all(menuItem);
}
if (hasLegitIcon) {
image = Gtk.gtk_image_new_from_file(imageFile.getAbsolutePath());
Gtk.gtk_image_menu_item_set_image(menuItem, image);
Gobject.g_object_ref_sink(image);
// must always re-set always-show after setting the image
Gtk.gtk_image_menu_item_set_always_show_image(menuItem, Gtk.TRUE);
}
Gtk.gtk_widget_show_all(menuItem);
} }
}); });
} }
@Override @Override
public public final
void setImage(final String imagePath) { void setImage(final String imagePath) {
if (imagePath == null) { if (imagePath == null) {
setImage_(null); setImage_(null);
@ -182,7 +94,7 @@ class GtkMenuEntry implements MenuEntry, GCallback {
} }
@Override @Override
public public final
void setImage(final URL imageUrl) { void setImage(final URL imageUrl) {
if (imageUrl == null) { if (imageUrl == null) {
setImage_(null); setImage_(null);
@ -193,7 +105,7 @@ class GtkMenuEntry implements MenuEntry, GCallback {
} }
@Override @Override
public public final
void setImage(final String cacheName, final InputStream imageStream) { void setImage(final String cacheName, final InputStream imageStream) {
if (imageStream == null) { if (imageStream == null) {
setImage_(null); setImage_(null);
@ -204,7 +116,7 @@ class GtkMenuEntry implements MenuEntry, GCallback {
} }
@Override @Override
public public final
void setImage(final InputStream imageStream) { void setImage(final InputStream imageStream) {
if (imageStream == null) { if (imageStream == null) {
setImage_(null); setImage_(null);
@ -214,56 +126,41 @@ class GtkMenuEntry implements MenuEntry, GCallback {
} }
} }
@Override
public
boolean hasImage() {
return hasLegitIcon;
}
@Override
public
void setCallback(final SystemTrayMenuAction callback) {
this.callback = callback;
}
/** /**
* This is ONLY called via systray.menuEntry.remove() !! * This is ONLY called via systray.menuEntry.remove() !!
*/ */
public public final
void remove() { void remove() {
Gtk.dispatch(new Runnable() { Gtk.dispatch(new Runnable() {
@Override @Override
public public
void run() { void run() {
Gtk.gtk_container_remove(systemTray.getMenu(), menuItem);
Gtk.gtk_menu_shell_deactivate(systemTray.getMenu(), menuItem);
Gtk.gtk_widget_destroy(menuItem);
removePrivate(); removePrivate();
// have to rebuild the menu now... // have to rebuild the menu now...
parent.deleteMenu(); systemTray.deleteMenu();
parent.createMenu(); systemTray.createMenu();
} }
}); });
} }
void removePrivate() { // called when this item is removed. Necessary to cleanup/remove itself
callback = null; abstract
Gtk.gtk_menu_shell_deactivate(parent.getMenu(), menuItem); void removePrivate();
if (image != null) {
Gtk.gtk_widget_destroy(image);
}
Gtk.gtk_widget_destroy(menuItem);
}
@Override @Override
public public final
int hashCode() { int hashCode() {
return id; return id;
} }
@Override @Override
public public final
boolean equals(Object obj) { boolean equals(Object obj) {
if (this == obj) { if (this == obj) {
return true; return true;

View File

@ -0,0 +1,160 @@
/*
* 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.linux;
import java.io.File;
import com.sun.jna.NativeLong;
import com.sun.jna.Pointer;
import dorkbox.systemTray.SystemTrayMenuAction;
import dorkbox.systemTray.linux.jna.GCallback;
import dorkbox.systemTray.linux.jna.Gobject;
import dorkbox.systemTray.linux.jna.Gtk;
import dorkbox.systemTray.util.ImageUtils;
class GtkMenuEntryItem extends GtkMenuEntry implements GCallback {
private static File transparentIcon = null;
@SuppressWarnings({"FieldCanBeLocal", "unused"})
private final NativeLong nativeLong;
// these have to be volatile, because they can be changed from any thread
private volatile SystemTrayMenuAction callback;
private volatile Pointer image;
// these are necessary BECAUSE GTK menus look funky as hell when there are some menu entries WITH icons and some WITHOUT
private volatile boolean hasLegitIcon = true;
/**
* called from inside dispatch thread. ONLY creates the menu item, but DOES NOT attach it!
* this is a FLOATING reference. See: https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#floating-ref
*/
GtkMenuEntryItem(final String label, final File image, final SystemTrayMenuAction callback, final GtkTypeSystemTray systemTray) {
super(Gtk.gtk_image_menu_item_new_with_label(""), systemTray);
this.callback = callback;
setText(label);
if (transparentIcon == null) {
transparentIcon = ImageUtils.getTransparentImage(ImageUtils.ENTRY_SIZE);
}
setImage_(image);
if (callback != null) {
nativeLong = Gobject.g_signal_connect_object(menuItem, "activate", this, null, 0);
}
else {
nativeLong = null;
}
}
@Override
public
void setCallback(final SystemTrayMenuAction callback) {
this.callback = callback;
}
// called by native code
@Override
public
int callback(final Pointer instance, final Pointer data) {
final SystemTrayMenuAction cb = this.callback;
if (cb != null) {
Gtk.proxyClick(cb, systemTray, GtkMenuEntryItem.this);
}
return Gtk.TRUE;
}
@Override
public
boolean hasImage() {
return hasLegitIcon;
}
/**
* the menu entry looks FUNKY when there are a mis-match of entries WITH and WITHOUT images
* <p>
* called on the DISPATCH thread
*/
void setSpacerImage(final boolean everyoneElseHasImages) {
if (hasLegitIcon) {
// we have a legit icon, so there is nothing else we can do.
return;
}
if (image != null) {
Gtk.gtk_widget_destroy(image);
image = null;
Gtk.gtk_widget_show_all(menuItem);
}
if (everyoneElseHasImages) {
image = Gtk.gtk_image_new_from_file(transparentIcon.getAbsolutePath());
Gtk.gtk_image_menu_item_set_image(menuItem, image);
// must always re-set always-show after setting the image
Gtk.gtk_image_menu_item_set_always_show_image(menuItem, Gtk.TRUE);
}
Gtk.gtk_widget_show_all(menuItem);
}
/**
* must always be called in the GTK thread
*/
void renderText(final String text) {
Gtk.gtk_menu_item_set_label(menuItem, text);
Gtk.gtk_widget_show_all(menuItem);
}
// NOTE: XFCE used to use appindicator3, which DOES NOT support images in the menu. This change was reverted.
// see: https://ask.fedoraproject.org/en/question/23116/how-to-fix-missing-icons-in-program-menus-and-context-menus/
// see: https://git.gnome.org/browse/gtk+/commit/?id=627a03683f5f41efbfc86cc0f10e1b7c11e9bb25
void setImage_(final File imageFile) {
hasLegitIcon = imageFile != null;
Gtk.dispatch(new Runnable() {
@Override
public
void run() {
if (image != null) {
Gtk.gtk_widget_destroy(image);
image = null;
Gtk.gtk_widget_show_all(menuItem);
}
if (imageFile != null) {
image = Gtk.gtk_image_new_from_file(imageFile.getAbsolutePath());
Gtk.gtk_image_menu_item_set_image(menuItem, image);
Gobject.g_object_ref_sink(image);
// must always re-set always-show after setting the image
Gtk.gtk_image_menu_item_set_always_show_image(menuItem, Gtk.TRUE);
}
Gtk.gtk_widget_show_all(menuItem);
}
});
}
void removePrivate() {
callback = null;
if (image != null) {
Gtk.gtk_widget_destroy(image);
}
}
}

View File

@ -0,0 +1,62 @@
/*
* 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.linux;
import java.io.File;
import dorkbox.systemTray.MenuSpacer;
import dorkbox.systemTray.SystemTrayMenuAction;
import dorkbox.systemTray.linux.jna.Gtk;
class GtkMenuEntrySpacer extends GtkMenuEntry implements MenuSpacer {
/**
* called from inside dispatch thread. ONLY creates the menu item, but DOES NOT attach it!
* this is a FLOATING reference. See: https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#floating-ref
*/
GtkMenuEntrySpacer(final GtkTypeSystemTray parent) {
super(Gtk.gtk_separator_menu_item_new(), parent);
}
@Override
void setSpacerImage(final boolean everyoneElseHasImages) {
}
// called in the GTK thread
@Override
void renderText(final String text) {
}
@Override
void setImage_(final File imageFile) {
}
@Override
void removePrivate() {
}
@Override
public
boolean hasImage() {
return false;
}
@Override
public
void setCallback(final SystemTrayMenuAction callback) {
}
}

View File

@ -0,0 +1,74 @@
/*
* 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.linux;
import java.io.File;
import com.sun.jna.Pointer;
import dorkbox.systemTray.MenuStatus;
import dorkbox.systemTray.SystemTrayMenuAction;
import dorkbox.systemTray.linux.jna.Gobject;
import dorkbox.systemTray.linux.jna.Gtk;
class GtkMenuEntryStatus extends GtkMenuEntry implements MenuStatus {
/**
* called from inside dispatch thread. ONLY creates the menu item, but DOES NOT attach it!
* this is a FLOATING reference. See: https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#floating-ref
*/
GtkMenuEntryStatus(final String label, final GtkTypeSystemTray parent) {
super(Gtk.gtk_menu_item_new_with_label(""), parent);
setText(label);
}
@Override
void setSpacerImage(final boolean everyoneElseHasImages) {
}
// called in the GTK thread
@Override
void renderText(final String text) {
// evil hacks abound...
Pointer label = Gtk.gtk_bin_get_child(menuItem);
Gtk.gtk_label_set_use_markup(label, Gtk.TRUE);
Pointer markup = Gobject.g_markup_printf_escaped("<b>%s</b>", text);
Gtk.gtk_label_set_markup(label, markup);
Gobject.g_free(markup);
Gtk.gtk_widget_set_sensitive(menuItem, Gtk.FALSE);
}
@Override
void setImage_(final File imageFile) {
}
@Override
void removePrivate() {
}
@Override
public
boolean hasImage() {
return false;
}
@Override
public
void setCallback(final SystemTrayMenuAction callback) {
}
}

View File

@ -19,9 +19,11 @@ package dorkbox.systemTray.linux;
import java.io.File; import java.io.File;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.util.concurrent.atomic.AtomicInteger;
import com.sun.jna.Pointer; import com.sun.jna.Pointer;
import dorkbox.systemTray.MenuEntry;
import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.SystemTrayMenuAction; import dorkbox.systemTray.SystemTrayMenuAction;
import dorkbox.systemTray.linux.jna.Gobject; import dorkbox.systemTray.linux.jna.Gobject;
@ -34,10 +36,9 @@ import dorkbox.systemTray.util.ImageUtils;
*/ */
public abstract public abstract
class GtkTypeSystemTray extends SystemTray { class GtkTypeSystemTray extends SystemTray {
private volatile Pointer menu; static final AtomicInteger MENU_ID_COUNTER = new AtomicInteger();
private volatile Pointer connectionStatusItem; private volatile Pointer menu;
private volatile String statusText = null;
@Override @Override
protected protected
@ -62,63 +63,77 @@ class GtkTypeSystemTray extends SystemTray {
@Override @Override
public public
String getStatus() { String getStatus() {
return statusText; synchronized (menuEntries) {
MenuEntry menuEntry = menuEntries.get(0);
if (menuEntry instanceof GtkMenuEntryStatus) {
return menuEntry.getText();
}
}
return null;
} }
@Override @Override
public public
void setStatus(final String statusText) { void setStatus(final String statusText) {
this.statusText = statusText;
Gtk.dispatch(new Runnable() { Gtk.dispatch(new Runnable() {
@Override @Override
public public
void run() { void run() {
// some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator. // some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator.
// To work around this issue, we destroy then recreate the menu every time something is changed. // To work around this issue, we destroy then recreate the menu every time something is changed.
if (connectionStatusItem == null && statusText != null && !statusText.isEmpty()) { synchronized (menuEntries) {
// status is ALWAYS at 0 index...
GtkMenuEntry menuEntry = null;
if (!menuEntries.isEmpty()) {
menuEntry = (GtkMenuEntry) menuEntries.get(0);
}
if (menuEntry instanceof GtkMenuEntryStatus) {
// always delete...
removeMenuEntry(menuEntry);
}
// some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator.
// To work around this issue, we destroy then recreate the menu every time something is changed.
deleteMenu(); deleteMenu();
connectionStatusItem = Gtk.gtk_menu_item_new_with_label(""); if (menuEntry == null) {
menuEntry = new GtkMenuEntryStatus(statusText, GtkTypeSystemTray.this);
// evil hacks abound... // status is ALWAYS at 0 index...
Pointer label = Gtk.gtk_bin_get_child(connectionStatusItem); menuEntries.add(0, menuEntry);
Gtk.gtk_label_set_use_markup(label, Gtk.TRUE); } else if (menuEntry instanceof GtkMenuEntryStatus) {
Pointer markup = Gobject.g_markup_printf_escaped("<b>%s</b>", statusText); // change the text?
Gtk.gtk_label_set_markup(label, markup); if (statusText != null) {
Gobject.g_free(markup); menuEntry = new GtkMenuEntryStatus(statusText, GtkTypeSystemTray.this);
menuEntries.add(0, menuEntry);
Gtk.gtk_widget_set_sensitive(connectionStatusItem, Gtk.FALSE); }
}
createMenu(); createMenu();
} }
else { }
if (statusText == null || statusText.isEmpty()) { });
// this means the status text already exists, and we are removing it }
Gtk.gtk_container_remove(menu, connectionStatusItem); @Override
connectionStatusItem = null; // because we manually delete it public
void addMenuSpacer() {
Gtk.dispatch(new Runnable() {
@Override
public
void run() {
// some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator.
// To work around this issue, we destroy then recreate the menu every time something is changed.
synchronized (menuEntries) {
// some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator.
// To work around this issue, we destroy then recreate the menu every time something is changed.
deleteMenu();
Gtk.gtk_widget_show_all(menu); GtkMenuEntry menuEntry = new GtkMenuEntrySpacer(GtkTypeSystemTray.this);
menuEntries.add(menuEntry);
deleteMenu(); createMenu();
createMenu();
}
else {
// here we set the text only. it already exists
// set bold instead
// libgtk.gtk_menu_item_set_label(this.connectionStatusItem, statusText);
// evil hacks abound...
Pointer label = Gtk.gtk_bin_get_child(connectionStatusItem);
Gtk.gtk_label_set_use_markup(label, Gtk.TRUE);
Pointer markup = Gobject.g_markup_printf_escaped("<b>%s</b>", statusText);
Gtk.gtk_label_set_markup(label, markup);
Gobject.g_free(markup);
Gtk.gtk_widget_show_all(menu);
}
} }
} }
}); });
@ -131,12 +146,6 @@ class GtkTypeSystemTray extends SystemTray {
*/ */
void deleteMenu() { void deleteMenu() {
if (menu != null) { if (menu != null) {
// have to remove status from menu (but not destroy the object)
if (connectionStatusItem != null) {
Gobject.g_object_force_floating(connectionStatusItem);
Gtk.gtk_container_remove(menu, connectionStatusItem);
}
// have to remove all other menu entries // have to remove all other menu entries
synchronized (menuEntries) { synchronized (menuEntries) {
for (int i = 0; i < menuEntries.size(); i++) { for (int i = 0; i < menuEntries.size(); i++) {
@ -157,19 +166,12 @@ class GtkTypeSystemTray extends SystemTray {
// some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator. // some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator.
// To work around this issue, we destroy then recreate the menu every time something is changed. // To work around this issue, we destroy then recreate the menu every time something is changed.
void createMenu() { void createMenu() {
// now add status
if (connectionStatusItem != null) {
Gtk.gtk_menu_shell_append(this.menu, this.connectionStatusItem);
Gobject.g_object_ref_sink(connectionStatusItem);
}
boolean hasImages = false; boolean hasImages = false;
// now add back other menu entries // now add back other menu entries
synchronized (menuEntries) { synchronized (menuEntries) {
for (int i = 0; i < menuEntries.size(); i++) { for (int i = 0; i < menuEntries.size(); i++) {
GtkMenuEntry menuEntry__ = (GtkMenuEntry) menuEntries.get(i); MenuEntry menuEntry__ = menuEntries.get(i);
hasImages |= menuEntry__.hasImage(); hasImages |= menuEntry__.hasImage();
} }
@ -189,23 +191,23 @@ class GtkTypeSystemTray extends SystemTray {
} }
} }
/**
* Called inside the gdk_threads block
*/
void onMenuAdded(final Pointer menu) {
// only needed for AppIndicator
}
/** /**
* Completely obliterates the menu, no possible way to reconstruct it. * Completely obliterates the menu, no possible way to reconstruct it.
*/ */
private private
void obliterateMenu() { void obliterateMenu() {
if (menu != null) { if (menu != null) {
// have to remove status from menu
if (connectionStatusItem != null) {
Gtk.gtk_widget_destroy(connectionStatusItem);
connectionStatusItem = null;
}
// have to remove all other menu entries // have to remove all other menu entries
synchronized (menuEntries) { synchronized (menuEntries) {
for (int i = 0; i < menuEntries.size(); i++) { for (int i = 0; i < menuEntries.size(); i++) {
GtkMenuEntry menuEntry__ = (GtkMenuEntry) menuEntries.get(i); GtkMenuEntry menuEntry__ = (GtkMenuEntry) menuEntries.get(i);
menuEntry__.removePrivate(); menuEntry__.removePrivate();
} }
menuEntries.clear(); menuEntries.clear();
@ -215,12 +217,6 @@ class GtkTypeSystemTray extends SystemTray {
} }
} }
/**
* Called inside the gdk_threads block
*/
protected
void onMenuAdded(final Pointer menu) {}
protected protected
Pointer getMenu() { Pointer getMenu() {
return menu; return menu;
@ -240,14 +236,13 @@ class GtkTypeSystemTray extends SystemTray {
public public
void run() { void run() {
synchronized (menuEntries) { synchronized (menuEntries) {
GtkMenuEntry menuEntry = (GtkMenuEntry) getMenuEntry(menuText); MenuEntry menuEntry = getMenuEntry(menuText);
if (menuEntry == null) { if (menuEntry == null) {
// some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator. // some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator.
// To work around this issue, we destroy then recreate the menu every time something is changed. // To work around this issue, we destroy then recreate the menu every time something is changed.
deleteMenu(); deleteMenu();
menuEntry = new GtkMenuEntry(menuText, imagePath, callback, GtkTypeSystemTray.this); menuEntry = new GtkMenuEntryItem(menuText, imagePath, callback, GtkTypeSystemTray.this);
menuEntries.add(menuEntry); menuEntries.add(menuEntry);
createMenu(); createMenu();
@ -291,7 +286,6 @@ class GtkTypeSystemTray extends SystemTray {
} }
@Override @Override
@Deprecated
public public
void addMenuEntry(final String menuText, final InputStream imageStream, final SystemTrayMenuAction callback) { void addMenuEntry(final String menuText, final InputStream imageStream, final SystemTrayMenuAction callback) {
if (imageStream == null) { if (imageStream == null) {

View File

@ -317,6 +317,8 @@ class Gtk {
*/ */
public static public static
void dispatch(final Runnable runnable) { void dispatch(final Runnable runnable) {
// FIXME: on mac, check -XstartOnFirstThread.. there are issues with javaFX (possibly SWT as well)
if (alreadyRunningGTK) { if (alreadyRunningGTK) {
// SWT/JavaFX // SWT/JavaFX
if (SystemTray.isJavaFxLoaded) { if (SystemTray.isJavaFxLoaded) {
@ -443,6 +445,8 @@ class Gtk {
public static native Pointer gtk_menu_item_new_with_label(String label); public static native Pointer gtk_menu_item_new_with_label(String label);
public static native Pointer gtk_separator_menu_item_new();
// to create a menu entry WITH an icon. // to create a menu entry WITH an icon.
public static native Pointer gtk_image_new_from_file(String iconPath); public static native Pointer gtk_image_new_from_file(String iconPath);

View File

@ -16,66 +16,43 @@
package dorkbox.systemTray.swing; package dorkbox.systemTray.swing;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File; import java.io.File;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
import javax.swing.ImageIcon; import javax.swing.JComponent;
import javax.swing.JMenuItem;
import dorkbox.systemTray.MenuEntry; import dorkbox.systemTray.MenuEntry;
import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.SystemTrayMenuAction;
import dorkbox.systemTray.util.ImageUtils; import dorkbox.systemTray.util.ImageUtils;
import dorkbox.util.SwingUtil; import dorkbox.util.SwingUtil;
abstract
class SwingMenuEntry implements MenuEntry { class SwingMenuEntry implements MenuEntry {
private final SwingSystemTrayMenuPopup parent; private final int id = SwingSystemTray.MENU_ID_COUNTER.getAndIncrement();
private final SystemTray systemTray;
private final JMenuItem menuItem;
private final ActionListener swingCallback;
private volatile boolean hasLegitIcon = false; final SwingSystemTray systemTray;
final JComponent menuItem;
// this have to be volatile, because they can be changed from any thread
private volatile String text; private volatile String text;
private volatile SystemTrayMenuAction callback;
// this is ALWAYS called on the EDT. // this is ALWAYS called on the EDT.
SwingMenuEntry(final SwingSystemTrayMenuPopup parentMenu, final String label, final File imagePath, final SystemTrayMenuAction callback, SwingMenuEntry(JComponent menuItem, final SwingSystemTray systemTray) {
final SystemTray systemTray) { this.menuItem = menuItem;
this.parent = parentMenu;
this.text = label;
this.callback = callback;
this.systemTray = systemTray; this.systemTray = systemTray;
swingCallback = new ActionListener() { systemTray.getMenu().add(menuItem);
@Override
public
void actionPerformed(ActionEvent e) {
// we want it to run on the EDT
handle();
}
};
menuItem = new JMenuItem(label);
menuItem.addActionListener(swingCallback);
if (imagePath != null) {
hasLegitIcon = true;
setImageIcon(imagePath);
}
parentMenu.add(menuItem);
} }
private /**
void handle() { * must always be called in the GTK thread
SystemTrayMenuAction cb = this.callback; */
if (cb != null) { abstract
cb.onClick(systemTray, this); void renderText(final String text);
}
} abstract
void setImage_(final File imageFile);
@Override @Override
public public
@ -92,38 +69,13 @@ class SwingMenuEntry implements MenuEntry {
@Override @Override
public public
void run() { void run() {
menuItem.setText(newText); renderText(newText);
} }
}); });
} }
private
void setImage_(final File imagePath) {
SwingUtil.invokeLater(new Runnable() {
@Override
public
void run() {
setImageIcon(imagePath);
}
});
}
// always called on the EDT
private
void setImageIcon(final File imagePath) {
if (imagePath != null) {
hasLegitIcon = true;
ImageIcon origIcon = new ImageIcon(imagePath.getAbsolutePath());
menuItem.setIcon(origIcon);
}
else {
hasLegitIcon = false;
menuItem.setIcon(null);
}
}
@Override @Override
public public final
void setImage(final String imagePath) { void setImage(final String imagePath) {
if (imagePath == null) { if (imagePath == null) {
setImage_(null); setImage_(null);
@ -134,7 +86,7 @@ class SwingMenuEntry implements MenuEntry {
} }
@Override @Override
public public final
void setImage(final URL imageUrl) { void setImage(final URL imageUrl) {
if (imageUrl == null) { if (imageUrl == null) {
setImage_(null); setImage_(null);
@ -145,7 +97,7 @@ class SwingMenuEntry implements MenuEntry {
} }
@Override @Override
public public final
void setImage(final String cacheName, final InputStream imageStream) { void setImage(final String cacheName, final InputStream imageStream) {
if (imageStream == null) { if (imageStream == null) {
setImage_(null); setImage_(null);
@ -156,7 +108,7 @@ class SwingMenuEntry implements MenuEntry {
} }
@Override @Override
public public final
void setImage(final InputStream imageStream) { void setImage(final InputStream imageStream) {
if (imageStream == null) { if (imageStream == null) {
setImage_(null); setImage_(null);
@ -167,27 +119,43 @@ class SwingMenuEntry implements MenuEntry {
} }
@Override @Override
public public final
boolean hasImage() {
return hasLegitIcon;
}
@Override
public
void setCallback(final SystemTrayMenuAction callback) {
this.callback = callback;
}
@Override
public
void remove() { void remove() {
SwingUtil.invokeAndWait(new Runnable() { SwingUtil.invokeAndWait(new Runnable() {
@Override @Override
public public
void run() { void run() {
menuItem.removeActionListener(swingCallback); removePrivate();
parent.remove(menuItem); systemTray.getMenu().remove(menuItem);
} }
}); });
} }
// called when this item is removed. Necessary to cleanup/remove itself
abstract
void removePrivate();
@Override
public final
int hashCode() {
return id;
}
@Override
public final
boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
SwingMenuEntry other = (SwingMenuEntry) obj;
return this.id == other.id;
}
} }

View File

@ -0,0 +1,105 @@
/*
* Copyright 2014 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.swing;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import javax.swing.ImageIcon;
import javax.swing.JMenuItem;
import dorkbox.systemTray.SystemTrayMenuAction;
import dorkbox.util.SwingUtil;
class SwingMenuEntryItem extends SwingMenuEntry {
private final ActionListener swingCallback;
private volatile boolean hasLegitIcon = false;
private volatile SystemTrayMenuAction callback;
// this is ALWAYS called on the EDT.
SwingMenuEntryItem(final String label, final File image, final SystemTrayMenuAction callback, final SwingSystemTray systemTray) {
super(new JMenuItem(label), systemTray);
setText(label);
this.callback = callback;
swingCallback = new ActionListener() {
@Override
public
void actionPerformed(ActionEvent e) {
// we want it to run on the EDT
handle();
}
};
((JMenuItem) menuItem).addActionListener(swingCallback);
setImage_(image);
}
@Override
public
void setCallback(final SystemTrayMenuAction callback) {
this.callback = callback;
}
private
void handle() {
SystemTrayMenuAction cb = this.callback;
if (cb != null) {
cb.onClick(systemTray, this);
}
}
@Override
public
boolean hasImage() {
return hasLegitIcon;
}
@Override
void removePrivate() {
((JMenuItem) menuItem).removeActionListener(swingCallback);
}
// always called in the EDT
@Override
void renderText(final String text) {
((JMenuItem) menuItem).setText(text);
}
@Override
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) menuItem).setIcon(origIcon);
}
else {
((JMenuItem) menuItem).setIcon(null);
}
}
});
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright 2014 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.swing;
import java.io.File;
import javax.swing.JSeparator;
import dorkbox.systemTray.MenuSpacer;
import dorkbox.systemTray.SystemTrayMenuAction;
class SwingMenuEntrySpacer extends SwingMenuEntry implements MenuSpacer {
// this is ALWAYS called on the EDT.
SwingMenuEntrySpacer(final SwingSystemTray parent) {
super(new JSeparator(JSeparator.HORIZONTAL), parent);
}
// called in the EDT thread
@Override
void renderText(final String text) {
}
@Override
void setImage_(final File imageFile) {
}
@Override
void removePrivate() {
}
@Override
public
boolean hasImage() {
return false;
}
@Override
public
void setCallback(final SystemTrayMenuAction callback) {
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright 2014 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.swing;
import java.awt.Font;
import java.io.File;
import javax.swing.JMenuItem;
import dorkbox.systemTray.MenuStatus;
import dorkbox.systemTray.SystemTrayMenuAction;
class SwingMenuEntryStatus extends SwingMenuEntry implements MenuStatus {
// this is ALWAYS called on the EDT.
SwingMenuEntryStatus(final String label, final SwingSystemTray parent) {
super(new JMenuItem(""), parent);
setText(label);
}
// called in the EDT thread
@Override
void renderText(final String text) {
((JMenuItem) menuItem).setText(text);
Font font = menuItem.getFont();
Font font1 = font.deriveFont(Font.BOLD);
menuItem.setFont(font1);
menuItem.setEnabled(false);
}
@Override
void setImage_(final File imageFile) {
}
@Override
void removePrivate() {
}
@Override
public
boolean hasImage() {
return false;
}
@Override
public
void setCallback(final SystemTrayMenuAction callback) {
}
}

View File

@ -17,7 +17,6 @@ package dorkbox.systemTray.swing;
import java.awt.AWTException; import java.awt.AWTException;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Font;
import java.awt.Image; import java.awt.Image;
import java.awt.Point; import java.awt.Point;
import java.awt.Rectangle; import java.awt.Rectangle;
@ -28,9 +27,9 @@ import java.awt.event.MouseEvent;
import java.io.File; import java.io.File;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
import javax.swing.JMenuItem;
import dorkbox.systemTray.MenuEntry; import dorkbox.systemTray.MenuEntry;
import dorkbox.systemTray.SystemTrayMenuAction; import dorkbox.systemTray.SystemTrayMenuAction;
@ -49,10 +48,9 @@ import dorkbox.util.SwingUtil;
@SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "WeakerAccess"}) @SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "WeakerAccess"})
public public
class SwingSystemTray extends dorkbox.systemTray.SystemTray { class SwingSystemTray extends dorkbox.systemTray.SystemTray {
volatile SwingSystemTrayMenuPopup menu; static final AtomicInteger MENU_ID_COUNTER = new AtomicInteger();
volatile JMenuItem connectionStatusItem; volatile SwingSystemTrayMenuPopup menu;
private volatile String statusText = null;
volatile SystemTray tray; volatile SystemTray tray;
volatile TrayIcon trayIcon; volatile TrayIcon trayIcon;
@ -96,17 +94,27 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
menuEntry.remove(); menuEntry.remove();
} }
tray.menuEntries.clear(); tray.menuEntries.clear();
tray.connectionStatusItem = null;
} }
} }
}); });
} }
protected SwingSystemTrayMenuPopup getMenu() {
return menu;
}
@Override @Override
public public
String getStatus() { String getStatus() {
return this.statusText; synchronized (menuEntries) {
MenuEntry menuEntry = menuEntries.get(0);
if (menuEntry instanceof SwingMenuEntryStatus) {
return menuEntry.getText();
}
}
return null;
} }
protected protected
@ -118,33 +126,65 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
@Override @Override
public public
void setStatus(final String statusText) { void setStatus(final String statusText) {
this.statusText = statusText;
dispatch(new Runnable() { dispatch(new Runnable() {
@Override @Override
public public
void run() { void run() {
SwingSystemTray tray = SwingSystemTray.this; SwingSystemTray tray = SwingSystemTray.this;
synchronized (tray) { synchronized (tray) {
if (tray.connectionStatusItem == null) { synchronized (menuEntries) {
final JMenuItem connectionStatusItem = new JMenuItem(statusText); // status is ALWAYS at 0 index...
Font font = connectionStatusItem.getFont(); SwingMenuEntry menuEntry = null;
Font font1 = font.deriveFont(Font.BOLD); if (!menuEntries.isEmpty()) {
connectionStatusItem.setFont(font1); menuEntry = (SwingMenuEntry) menuEntries.get(0);
}
connectionStatusItem.setEnabled(false); if (menuEntry instanceof SwingMenuEntryStatus) {
tray.menu.add(connectionStatusItem); // set the text or delete...
tray.connectionStatusItem = connectionStatusItem; if (statusText == null) {
} // delete
else { removeMenuEntry(menuEntry);
tray.connectionStatusItem.setText(statusText); }
else {
// set text
menuEntry.setText(statusText);
}
} else {
// create a new one
menuEntry = new SwingMenuEntryStatus(statusText, tray);
// status is ALWAYS at 0 index...
menuEntries.add(0, menuEntry);
}
} }
} }
} }
}); });
} }
@Override
public
void addMenuSpacer() {
dispatch(new Runnable() {
@Override
public
void run() {
SwingSystemTray tray = SwingSystemTray.this;
synchronized (tray) {
synchronized (menuEntries) {
synchronized (menuEntries) {
MenuEntry menuEntry = new SwingMenuEntrySpacer(tray);
menuEntries.add(menuEntry);
}
}
}
}
});
}
@Override @Override
protected protected
void setIcon_(final File iconFile) { void setIcon_(final File iconFile) {
@ -244,7 +284,7 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
throw new IllegalArgumentException("Menu entry already exists for given label '" + menuText + "'"); throw new IllegalArgumentException("Menu entry already exists for given label '" + menuText + "'");
} }
else { else {
menuEntry = new SwingMenuEntry(menu, menuText, imagePath, callback, tray); menuEntry = new SwingMenuEntryItem(menuText, imagePath, callback, tray);
menuEntries.add(menuEntry); menuEntries.add(menuEntry);
} }
} }
@ -287,7 +327,6 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
} }
@Override @Override
@Deprecated
public public
void addMenuEntry(final String menuText, final InputStream imageStream, final SystemTrayMenuAction callback) { void addMenuEntry(final String menuText, final InputStream imageStream, final SystemTrayMenuAction callback) {
if (imageStream == null) { if (imageStream == null) {

View File

@ -81,6 +81,7 @@ class TestTray {
}; };
this.systemTray.addMenuEntry("Green Mail", GREEN_MAIL, callbackGreen); this.systemTray.addMenuEntry("Green Mail", GREEN_MAIL, callbackGreen);
this.systemTray.addMenuSpacer();
systemTray.addMenuEntry("Quit", new SystemTrayMenuAction() { systemTray.addMenuEntry("Quit", new SystemTrayMenuAction() {
@Override @Override

View File

@ -115,6 +115,7 @@ class TestTrayJavaFX extends Application {
}; };
this.systemTray.addMenuEntry("Green Mail", GREEN_MAIL, callbackGreen); this.systemTray.addMenuEntry("Green Mail", GREEN_MAIL, callbackGreen);
this.systemTray.addMenuSpacer();
systemTray.addMenuEntry("Quit", new SystemTrayMenuAction() { systemTray.addMenuEntry("Quit", new SystemTrayMenuAction() {
@Override @Override

View File

@ -99,6 +99,7 @@ class TestTraySwt {
}; };
this.systemTray.addMenuEntry("Green Mail", GREEN_MAIL, callbackGreen); this.systemTray.addMenuEntry("Green Mail", GREEN_MAIL, callbackGreen);
this.systemTray.addMenuSpacer();
systemTray.addMenuEntry("Quit", new SystemTrayMenuAction() { systemTray.addMenuEntry("Quit", new SystemTrayMenuAction() {
@Override @Override