Fixed issues with spacer images, fixed issues with adding images to a

menu entry after adding it to a menu. Removed GTK event dispatch queue,
properly fixed out-of-order menu creation. During entry removal, the
native peer takes care of cleaning up the native bits.
This commit is contained in:
nathan 2017-11-15 17:30:47 +01:00
parent 740348b6c4
commit e1cca820c3
11 changed files with 137 additions and 130 deletions

View File

@ -81,7 +81,7 @@ class Entry {
}
/**
* Removes this menu entry from the menu and releases all system resources associated with this menu entry
* Removes this menu entry from the menu and releases all system resources associated with this menu entry.
*/
public
void remove() {

View File

@ -408,33 +408,15 @@ class Menu extends MenuItem {
}
}
/**
* This removes all menu entries from this menu
*/
public
void clear() {
List<Entry> copy;
synchronized (menuEntries) {
// access on this object must be synchronized for object visibility
// a copy is made to prevent deadlocks from occurring when operating in different threads
// have to make copy because we are deleting all of them, and sub-menus remove themselves from parents
copy = new ArrayList<Entry>(menuEntries);
menuEntries.clear();
}
for (Entry entry : copy) {
entry.remove();
}
}
/**
* This removes all menu entries from this menu AND this menu from it's parent
*/
@Override
public
void remove() {
clear();
synchronized (menuEntries) {
menuEntries.clear();
}
super.remove();
}

View File

@ -387,16 +387,4 @@ class MenuItem extends Entry {
String getTooltip() {
return this.tooltip;
}
@Override
public
void remove() {
if (peer != null) {
setImage_(null);
setText(null);
setCallback(null);
}
super.remove();
}
}

View File

@ -44,6 +44,7 @@ import dorkbox.systemTray.ui.gtk._AppIndicatorNativeTray;
import dorkbox.systemTray.ui.gtk._GtkStatusIconNativeTray;
import dorkbox.systemTray.ui.swing.SwingUIFactory;
import dorkbox.systemTray.ui.swing._SwingTray;
import dorkbox.systemTray.util.EventDispatch;
import dorkbox.systemTray.util.ImageResizeUtil;
import dorkbox.systemTray.util.LinuxSwingUI;
import dorkbox.systemTray.util.SizeAndScalingUtil;
@ -226,7 +227,7 @@ class SystemTray {
OSUtil.DesktopEnv.Env de = OSUtil.DesktopEnv.get();
if (DEBUG) {
logger.debug("Currently using the '{}' desktop environment", de);
logger.debug("Currently using the '{}' desktop environment" + OS.LINE_SEPARATOR + OSUtil.Linux.getInfo(), de);
}
switch (de) {
@ -310,6 +311,19 @@ class SystemTray {
// kde 5.8+ is "high DPI", so we need to adjust the scale. Image resize will do that
}
case Unity: {
try {
String ubuntuVersion = OSUtil.Linux.getUbuntuVersion();
String[] split = ubuntuVersion.split(".");
int major = Integer.parseInt(split[0]);
int minor = Integer.parseInt(split[1]);
// <=16.04 it is better to use GtkStatusIcons.
if (major < 16 || (major == 16 && minor <= 4)) {
return selectTypeQuietly(TrayType.GtkStatusIcon);
}
} catch (Exception ignored) {
}
// Ubuntu Unity is a weird combination. It's "Gnome", but it's not "Gnome Shell".
return selectTypeQuietly(TrayType.AppIndicator);
}
@ -393,6 +407,19 @@ class SystemTray {
logger.error("Error detecting gnome version", e);
}
}
if (OS.isLinux()) {
// now just blanket query what we are to guess...
if (OSUtil.Linux.isUbuntu()) {
return selectTypeQuietly(TrayType.AppIndicator);
}
else if (OSUtil.Linux.isFedora()) {
return selectTypeQuietly(TrayType.AppIndicator);
} else {
// AppIndicators are now the "default" for most linux distro's.
return selectTypeQuietly(TrayType.AppIndicator);
}
}
}
return null;
@ -1025,13 +1052,14 @@ class SystemTray {
*/
public
void shutdown() {
// this will call "dispatchAndWait()" behind the scenes, so it is thread-safe
// this is thread-safe
final Menu menu = systemTrayMenu;
if (menu != null) {
menu.remove();
}
systemTrayMenu = null;
EventDispatch.shutdown();
}
/**

View File

@ -30,7 +30,7 @@ abstract
class GtkBaseMenuItem implements EntryPeer {
// these are necessary BECAUSE GTK menus look funky as hell when there are some menu entries WITH icons and some WITHOUT
private static final File transparentIcon = ImageResizeUtil.getTransparentImage();
private volatile boolean hasLegitImage = true;
private volatile boolean hasLegitImage = false; // default is to not have an image assigned
// these have to be volatile, because they can be changed from any thread
private volatile Pointer spacerImage;
@ -51,6 +51,39 @@ class GtkBaseMenuItem implements EntryPeer {
hasLegitImage = isLegit;
}
/**
* always remove a spacer image.
* <p>
* called on the DISPATCH thread
*/
protected
void removeSpacerImage() {
if (spacerImage != null) {
Gtk2.gtk_container_remove(_native, spacerImage); // will automatically get destroyed if no other references to it
spacerImage = null;
Gtk2.gtk_widget_show_all(_native);
}
}
/**
* always add a spacer image.
* <p>
* called on the DISPATCH thread
*/
protected
void addSpacerImage() {
if (spacerImage == null) {
spacerImage = Gtk2.gtk_image_new_from_file(transparentIcon.getAbsolutePath());
Gtk2.gtk_image_menu_item_set_image(_native, spacerImage);
// must always re-set always-show after setting the image
Gtk2.gtk_image_menu_item_set_always_show_image(_native, true);
}
}
/**
* the menu entry looks FUNKY when there are a mis-match of entries WITH and WITHOUT images.
* This is primarily only with AppIndicators, although not always.
@ -64,18 +97,10 @@ class GtkBaseMenuItem implements EntryPeer {
return;
}
if (spacerImage != null) {
Gtk2.gtk_container_remove(_native, spacerImage); // will automatically get destroyed if no other references to it
spacerImage = null;
Gtk2.gtk_widget_show_all(_native);
}
removeSpacerImage();
if (everyoneElseHasImages) {
spacerImage = Gtk2.gtk_image_new_from_file(transparentIcon.getAbsolutePath());
Gtk2.gtk_image_menu_item_set_image(_native, spacerImage);
// must always re-set always-show after setting the image
Gtk2.gtk_image_menu_item_set_always_show_image(_native, true);
addSpacerImage();
}
Gtk2.gtk_widget_show_all(_native);

View File

@ -197,7 +197,7 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer {
@Override
public
void add(final Menu parentMenu, final Entry entry, final int index) {
// must always be called on the GTK dispatch. This must be dispatchAndWait
// must always be called on the GTK dispatch. This must be dispatchAndWait() so it will properly executed immediately
GtkEventDispatch.dispatchAndWait(new Runnable() {
@Override
public
@ -206,35 +206,50 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer {
// To work around this issue, we destroy then recreate the menu every time something is changed.
deleteMenu();
GtkBaseMenuItem item = null;
if (entry instanceof Menu) {
// some implementations of appindicator, do NOT like having a menu added, which has no menu items yet.
// see: https://bugs.launchpad.net/glipper/+bug/1203888
GtkMenu item = new GtkMenu(GtkMenu.this);
item = new GtkMenu(GtkMenu.this);
menuEntries.add(index, item);
((Menu) entry).bind(item, parentMenu, parentMenu.getSystemTray());
}
else if (entry instanceof Separator) {
GtkMenuItemSeparator item = new GtkMenuItemSeparator(GtkMenu.this);
item = new GtkMenuItemSeparator(GtkMenu.this);
menuEntries.add(index, item);
entry.bind(item, parentMenu, parentMenu.getSystemTray());
}
else if (entry instanceof Checkbox) {
GtkMenuItemCheckbox item = new GtkMenuItemCheckbox(GtkMenu.this);
item = new GtkMenuItemCheckbox(GtkMenu.this);
menuEntries.add(index, item);
((Checkbox) entry).bind(item, parentMenu, parentMenu.getSystemTray());
}
else if (entry instanceof Status) {
GtkMenuItemStatus item = new GtkMenuItemStatus(GtkMenu.this);
item = new GtkMenuItemStatus(GtkMenu.this);
menuEntries.add(index, item);
((Status) entry).bind(item, parentMenu, parentMenu.getSystemTray());
}
else if (entry instanceof MenuItem) {
GtkMenuItem item = new GtkMenuItem(GtkMenu.this);
item = new GtkMenuItem(GtkMenu.this);
menuEntries.add(index, item);
((MenuItem) entry).bind(item, parentMenu, parentMenu.getSystemTray());
}
createMenu();
// we must create the menu BEFORE binding the menu, otherwise the menus' children's GTK element can be added before
// their parent GTK elements are added (and the menu won't show up)
if (entry instanceof Menu) {
((Menu) entry).bind((GtkMenu) item, parentMenu, parentMenu.getSystemTray());
}
else if (entry instanceof Separator) {
((Separator)entry).bind((GtkMenuItemSeparator) item, parentMenu, parentMenu.getSystemTray());
}
else if (entry instanceof Checkbox) {
((Checkbox) entry).bind((GtkMenuItemCheckbox) item, parentMenu, parentMenu.getSystemTray());
}
else if (entry instanceof Status) {
((Status) entry).bind((GtkMenuItemStatus) item, parentMenu, parentMenu.getSystemTray());
}
else if (entry instanceof MenuItem) {
((MenuItem) entry).bind((GtkMenuItem) item, parentMenu, parentMenu.getSystemTray());
}
}
});
}
@ -252,7 +267,7 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer {
// is overridden by system tray
setLegitImage(menuItem.getImage() != null);
Runnable runnable = new Runnable() {
GtkEventDispatch.dispatch(new Runnable() {
@Override
public
void run() {
@ -263,8 +278,7 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer {
}
if (menuItem.getImage() != null) {
image = Gtk2.gtk_image_new_from_file(menuItem.getImage()
.getAbsolutePath());
image = Gtk2.gtk_image_new_from_file(menuItem.getImage().getAbsolutePath());
Gtk2.gtk_image_menu_item_set_image(_native, image);
// must always re-set always-show after setting the image
@ -273,14 +287,7 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer {
Gtk2.gtk_widget_show_all(_native);
}
};
if (GtkEventDispatch.isDispatch.get()) {
runnable.run();
}
else {
GtkEventDispatch.dispatch(runnable);
}
});
}
// is overridden in tray impl
@ -385,7 +392,7 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer {
@Override
public
void remove() {
Runnable runnable = new Runnable() {
GtkEventDispatch.dispatch(new Runnable() {
@Override
public
void run() {
@ -408,13 +415,6 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer {
parent.createMenu(); // must be on EDT
}
}
};
if (GtkEventDispatch.isDispatch.get()) {
runnable.run();
}
else {
GtkEventDispatch.dispatch(runnable);
}
});
}
}

View File

@ -73,6 +73,7 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback {
@Override
public
void setImage(final MenuItem menuItem) {
final boolean hadImage = hasImage();
setLegitImage(menuItem.getImage() != null);
GtkEventDispatch.dispatch(new Runnable() {
@ -86,6 +87,9 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback {
}
if (menuItem.getImage() != null) {
// always remove the spacer image in case it's there. The spacer image will correctly added when the menu is created.
removeSpacerImage();
image = Gtk2.gtk_image_new_from_file(menuItem.getImage()
.getAbsolutePath());
Gtk2.gtk_image_menu_item_set_image(_native, image);
@ -93,6 +97,11 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback {
// must always re-set always-show after setting the image
Gtk2.gtk_image_menu_item_set_always_show_image(_native, true);
}
else if (hadImage) {
// if at one point, we had an image, we should set the spacer image back, so that menu spacing looks correct.
// since we USED to have an image, it is safe to assume that we should have a spacer image.
addSpacerImage();
}
Gtk2.gtk_widget_show_all(_native);
}
@ -205,14 +214,16 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback {
@Override
public
void remove() {
Runnable runnable = new Runnable() {
GtkEventDispatch.dispatch(new Runnable() {
@Override
public
void run() {
Gtk2.gtk_container_remove(parent._nativeMenu, _native); // will automatically get destroyed if no other references to it
GtkMenuItem.super.remove();
callback = null;
Gtk2.gtk_container_remove(parent._nativeMenu, _native); // will automatically get destroyed if no other references to it
if (image != null) {
Gtk2.gtk_container_remove(_native, image); // will automatically get destroyed if no other references to it
image = null;
@ -220,13 +231,6 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback {
parent.remove(GtkMenuItem.this);
}
};
if (GtkEventDispatch.isDispatch.get()) {
runnable.run();
}
else {
GtkEventDispatch.dispatch(runnable);
}
});
}
}

View File

@ -297,14 +297,16 @@ class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCall
@Override
public
void remove() {
Runnable runnable = new Runnable() {
GtkEventDispatch.dispatch(new Runnable() {
@Override
public
void run() {
Gtk2.gtk_container_remove(parent._nativeMenu, _native); // will automatically get destroyed if no other references to it
GtkMenuItemCheckbox.super.remove();
callback = null;
Gtk2.gtk_container_remove(parent._nativeMenu, _native); // will automatically get destroyed if no other references to it
if (image != null) {
Gtk2.gtk_container_remove(_native, image); // will automatically get destroyed if no other references to it
image = null;
@ -312,13 +314,6 @@ class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCall
parent.remove(GtkMenuItemCheckbox.this);
}
};
if (GtkEventDispatch.isDispatch.get()) {
runnable.run();
}
else {
GtkEventDispatch.dispatch(runnable);
}
});
}
}

View File

@ -17,10 +17,10 @@ package dorkbox.systemTray.ui.gtk;
import static dorkbox.util.jna.linux.Gtk.Gtk2;
import dorkbox.systemTray.peer.EntryPeer;
import dorkbox.systemTray.peer.SeparatorPeer;
import dorkbox.util.jna.linux.GtkEventDispatch;
class GtkMenuItemSeparator extends GtkBaseMenuItem implements EntryPeer {
class GtkMenuItemSeparator extends GtkBaseMenuItem implements SeparatorPeer {
private final GtkMenu parent;
@ -37,7 +37,7 @@ class GtkMenuItemSeparator extends GtkBaseMenuItem implements EntryPeer {
@Override
public
void remove() {
Runnable runnable = new Runnable() {
GtkEventDispatch.dispatch(new Runnable() {
@Override
public
void run() {
@ -45,14 +45,7 @@ class GtkMenuItemSeparator extends GtkBaseMenuItem implements EntryPeer {
parent.remove(GtkMenuItemSeparator.this);
}
};
if (GtkEventDispatch.isDispatch.get()) {
runnable.run();
}
else {
GtkEventDispatch.dispatch(runnable);
}
});
}
@Override

View File

@ -61,23 +61,16 @@ class GtkMenuItemStatus extends GtkBaseMenuItem implements StatusPeer {
@Override
public
void remove() {
Runnable runnable = new Runnable() {
GtkEventDispatch.dispatch(new Runnable() {
@Override
public
void run() {
Gtk2.gtk_container_remove(parent._nativeMenu, _native); // will automatically get destroyed if no other references to it
GtkMenuItemStatus.super.remove();
Gtk2.gtk_container_remove(parent._nativeMenu, _native); // will automatically get destroyed if no other references to it
parent.remove(GtkMenuItemStatus.this);
}
};
if (GtkEventDispatch.isDispatch.get()) {
runnable.run();
}
else {
GtkEventDispatch.dispatch(runnable);
}
});
}
}

View File

@ -197,25 +197,24 @@ class _AppIndicatorNativeTray extends Tray {
void remove() {
// This is required if we have JavaFX or SWT shutdown hooks (to prevent us from shutting down twice...)
if (!shuttingDown.getAndSet(true)) {
// must happen asap, so our hook properly notices we are in shutdown mode
final AppIndicatorInstanceStruct savedAppIndicator = appIndicator;
appIndicator = null;
super.remove();
GtkEventDispatch.dispatch(new Runnable() {
@Override
public
void run() {
// must happen asap, so our hook properly notices we are in shutdown mode
final AppIndicatorInstanceStruct savedAppIndicator = appIndicator;
appIndicator = null;
// STATUS_PASSIVE hides the indicator
AppIndicator.app_indicator_set_status(savedAppIndicator, AppIndicator.STATUS_PASSIVE);
Pointer p = savedAppIndicator.getPointer();
Gobject.g_object_unref(p);
GtkEventDispatch.shutdownGui();
}
});
super.remove();
// does not need to be called on the dispatch (it does that)
GtkEventDispatch.shutdownGui();
}
}
};