Refactored status entries, added spacers to menu
This commit is contained in:
parent
9fdee240f8
commit
c9b0cce1e3
25
src/dorkbox/systemTray/MenuSpacer.java
Normal file
25
src/dorkbox/systemTray/MenuSpacer.java
Normal 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 {
|
||||
|
||||
}
|
25
src/dorkbox/systemTray/MenuStatus.java
Normal file
25
src/dorkbox/systemTray/MenuStatus.java
Normal 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 {
|
||||
|
||||
}
|
|
@ -697,9 +697,16 @@ class SystemTray {
|
|||
* Must be wrapped in a synchronized block for object visibility
|
||||
*/
|
||||
protected
|
||||
MenuEntry getMenuEntry(String menuText) {
|
||||
MenuEntry getMenuEntry(final String menuText) {
|
||||
if (menuText == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (MenuEntry entry : menuEntries) {
|
||||
if (entry.getText().equals(menuText)) {
|
||||
String text = entry.getText();
|
||||
|
||||
// text can be null
|
||||
if (menuText.equals(text)) {
|
||||
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 callback callback that will be executed when this menu entry is clicked
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract
|
||||
void addMenuEntry(String menuText, InputStream imageStream, SystemTrayMenuAction callback);
|
||||
|
||||
|
@ -1169,8 +1175,6 @@ class SystemTray {
|
|||
throw new NullPointerException("No menu entry exists for menuEntry");
|
||||
}
|
||||
|
||||
final String label = menuEntry.getText();
|
||||
|
||||
// have to wait for the value
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
final AtomicBoolean hasValue = new AtomicBoolean(false);
|
||||
|
@ -1183,8 +1187,7 @@ class SystemTray {
|
|||
synchronized (menuEntries) {
|
||||
for (Iterator<MenuEntry> iterator = menuEntries.iterator(); iterator.hasNext(); ) {
|
||||
final MenuEntry entry = iterator.next();
|
||||
if (entry.getText()
|
||||
.equals(label)) {
|
||||
if (entry == menuEntry) {
|
||||
iterator.remove();
|
||||
|
||||
// this will also reset the menu
|
||||
|
@ -1193,6 +1196,19 @@ class SystemTray {
|
|||
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) {
|
||||
logger.error("Error removing menu entry from list.", e);
|
||||
|
@ -1209,11 +1225,11 @@ class SystemTray {
|
|||
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
logger.error("Error removing menu entry: {}", label);
|
||||
logger.error("Error removing menu entry: {}", menuEntry.getText());
|
||||
}
|
||||
|
||||
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 + "'");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
|
|
|
@ -18,108 +18,49 @@ package dorkbox.systemTray.linux;
|
|||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.sun.jna.NativeLong;
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
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.util.ImageUtils;
|
||||
|
||||
class GtkMenuEntry implements MenuEntry, GCallback {
|
||||
private static final AtomicInteger ID_COUNTER = new AtomicInteger();
|
||||
private final int id = ID_COUNTER.getAndIncrement();
|
||||
abstract
|
||||
class GtkMenuEntry implements MenuEntry {
|
||||
private final int id = GtkTypeSystemTray.MENU_ID_COUNTER.getAndIncrement();
|
||||
|
||||
final Pointer menuItem;
|
||||
final GtkTypeSystemTray parent;
|
||||
final GtkTypeSystemTray systemTray;
|
||||
|
||||
@SuppressWarnings({"FieldCanBeLocal", "unused"})
|
||||
private final NativeLong nativeLong;
|
||||
|
||||
// these have to be volatile, because they can be changed from any thread
|
||||
// this have to be volatile, because they can be changed from any thread
|
||||
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!
|
||||
* 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) {
|
||||
this.parent = parent;
|
||||
this.text = label;
|
||||
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);
|
||||
GtkMenuEntry(Pointer menuItem, final GtkTypeSystemTray systemTray) {
|
||||
this.systemTray = systemTray;
|
||||
this.menuItem = menuItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
if (hasLegitIcon) {
|
||||
// we have a legit icon, so there is nothing else we can do.
|
||||
return;
|
||||
}
|
||||
abstract
|
||||
void setSpacerImage(final boolean everyoneElseHasImages);
|
||||
|
||||
if (image != null) {
|
||||
Gtk.gtk_widget_destroy(image);
|
||||
image = null;
|
||||
Gtk.gtk_widget_show_all(menuItem);
|
||||
}
|
||||
/**
|
||||
* must always be called in the GTK thread
|
||||
*/
|
||||
abstract
|
||||
void renderText(final String text);
|
||||
|
||||
if (everyoneElseHasImages) {
|
||||
image = Gtk.gtk_image_new_from_file(transparentIcon.getAbsolutePath());
|
||||
abstract
|
||||
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
|
||||
public
|
||||
|
@ -128,50 +69,21 @@ class GtkMenuEntry implements MenuEntry, GCallback {
|
|||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
public final
|
||||
void setText(final String newText) {
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@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() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
if (image != null) {
|
||||
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);
|
||||
renderText(text);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
public final
|
||||
void setImage(final String imagePath) {
|
||||
if (imagePath == null) {
|
||||
setImage_(null);
|
||||
|
@ -182,7 +94,7 @@ class GtkMenuEntry implements MenuEntry, GCallback {
|
|||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
public final
|
||||
void setImage(final URL imageUrl) {
|
||||
if (imageUrl == null) {
|
||||
setImage_(null);
|
||||
|
@ -193,7 +105,7 @@ class GtkMenuEntry implements MenuEntry, GCallback {
|
|||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
public final
|
||||
void setImage(final String cacheName, final InputStream imageStream) {
|
||||
if (imageStream == null) {
|
||||
setImage_(null);
|
||||
|
@ -204,7 +116,7 @@ class GtkMenuEntry implements MenuEntry, GCallback {
|
|||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
public final
|
||||
void setImage(final InputStream imageStream) {
|
||||
if (imageStream == 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() !!
|
||||
*/
|
||||
public
|
||||
public final
|
||||
void remove() {
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
Gtk.gtk_container_remove(systemTray.getMenu(), menuItem);
|
||||
Gtk.gtk_menu_shell_deactivate(systemTray.getMenu(), menuItem);
|
||||
Gtk.gtk_widget_destroy(menuItem);
|
||||
|
||||
removePrivate();
|
||||
|
||||
// have to rebuild the menu now...
|
||||
parent.deleteMenu();
|
||||
parent.createMenu();
|
||||
systemTray.deleteMenu();
|
||||
systemTray.createMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void removePrivate() {
|
||||
callback = null;
|
||||
Gtk.gtk_menu_shell_deactivate(parent.getMenu(), menuItem);
|
||||
|
||||
if (image != null) {
|
||||
Gtk.gtk_widget_destroy(image);
|
||||
}
|
||||
|
||||
Gtk.gtk_widget_destroy(menuItem);
|
||||
}
|
||||
// called when this item is removed. Necessary to cleanup/remove itself
|
||||
abstract
|
||||
void removePrivate();
|
||||
|
||||
@Override
|
||||
public
|
||||
public final
|
||||
int hashCode() {
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public
|
||||
public final
|
||||
boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
|
|
160
src/dorkbox/systemTray/linux/GtkMenuEntryItem.java
Normal file
160
src/dorkbox/systemTray/linux/GtkMenuEntryItem.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
62
src/dorkbox/systemTray/linux/GtkMenuEntrySpacer.java
Normal file
62
src/dorkbox/systemTray/linux/GtkMenuEntrySpacer.java
Normal 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) {
|
||||
|
||||
}
|
||||
}
|
74
src/dorkbox/systemTray/linux/GtkMenuEntryStatus.java
Normal file
74
src/dorkbox/systemTray/linux/GtkMenuEntryStatus.java
Normal 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) {
|
||||
|
||||
}
|
||||
}
|
|
@ -19,9 +19,11 @@ package dorkbox.systemTray.linux;
|
|||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
import dorkbox.systemTray.MenuEntry;
|
||||
import dorkbox.systemTray.SystemTray;
|
||||
import dorkbox.systemTray.SystemTrayMenuAction;
|
||||
import dorkbox.systemTray.linux.jna.Gobject;
|
||||
|
@ -34,10 +36,9 @@ import dorkbox.systemTray.util.ImageUtils;
|
|||
*/
|
||||
public abstract
|
||||
class GtkTypeSystemTray extends SystemTray {
|
||||
private volatile Pointer menu;
|
||||
static final AtomicInteger MENU_ID_COUNTER = new AtomicInteger();
|
||||
|
||||
private volatile Pointer connectionStatusItem;
|
||||
private volatile String statusText = null;
|
||||
private volatile Pointer menu;
|
||||
|
||||
@Override
|
||||
protected
|
||||
|
@ -62,64 +63,78 @@ class GtkTypeSystemTray extends SystemTray {
|
|||
@Override
|
||||
public
|
||||
String getStatus() {
|
||||
return statusText;
|
||||
synchronized (menuEntries) {
|
||||
MenuEntry menuEntry = menuEntries.get(0);
|
||||
if (menuEntry instanceof GtkMenuEntryStatus) {
|
||||
return menuEntry.getText();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setStatus(final String statusText) {
|
||||
this.statusText = statusText;
|
||||
|
||||
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.
|
||||
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();
|
||||
|
||||
connectionStatusItem = Gtk.gtk_menu_item_new_with_label("");
|
||||
|
||||
// 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_set_sensitive(connectionStatusItem, Gtk.FALSE);
|
||||
if (menuEntry == null) {
|
||||
menuEntry = new GtkMenuEntryStatus(statusText, GtkTypeSystemTray.this);
|
||||
// status is ALWAYS at 0 index...
|
||||
menuEntries.add(0, menuEntry);
|
||||
} else if (menuEntry instanceof GtkMenuEntryStatus) {
|
||||
// change the text?
|
||||
if (statusText != null) {
|
||||
menuEntry = new GtkMenuEntryStatus(statusText, GtkTypeSystemTray.this);
|
||||
menuEntries.add(0, menuEntry);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
connectionStatusItem = null; // because we manually delete it
|
||||
|
||||
Gtk.gtk_widget_show_all(menu);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
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();
|
||||
|
||||
GtkMenuEntry menuEntry = new GtkMenuEntrySpacer(GtkTypeSystemTray.this);
|
||||
menuEntries.add(menuEntry);
|
||||
|
||||
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() {
|
||||
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
|
||||
synchronized (menuEntries) {
|
||||
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.
|
||||
// To work around this issue, we destroy then recreate the menu every time something is changed.
|
||||
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;
|
||||
|
||||
// now add back other menu entries
|
||||
synchronized (menuEntries) {
|
||||
for (int i = 0; i < menuEntries.size(); i++) {
|
||||
GtkMenuEntry menuEntry__ = (GtkMenuEntry) menuEntries.get(i);
|
||||
|
||||
MenuEntry menuEntry__ = menuEntries.get(i);
|
||||
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.
|
||||
*/
|
||||
private
|
||||
void obliterateMenu() {
|
||||
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
|
||||
synchronized (menuEntries) {
|
||||
for (int i = 0; i < menuEntries.size(); i++) {
|
||||
GtkMenuEntry menuEntry__ = (GtkMenuEntry) menuEntries.get(i);
|
||||
|
||||
menuEntry__.removePrivate();
|
||||
}
|
||||
menuEntries.clear();
|
||||
|
@ -215,12 +217,6 @@ class GtkTypeSystemTray extends SystemTray {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called inside the gdk_threads block
|
||||
*/
|
||||
protected
|
||||
void onMenuAdded(final Pointer menu) {}
|
||||
|
||||
protected
|
||||
Pointer getMenu() {
|
||||
return menu;
|
||||
|
@ -240,14 +236,13 @@ class GtkTypeSystemTray extends SystemTray {
|
|||
public
|
||||
void run() {
|
||||
synchronized (menuEntries) {
|
||||
GtkMenuEntry menuEntry = (GtkMenuEntry) getMenuEntry(menuText);
|
||||
|
||||
MenuEntry menuEntry = getMenuEntry(menuText);
|
||||
if (menuEntry == null) {
|
||||
// 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();
|
||||
|
||||
menuEntry = new GtkMenuEntry(menuText, imagePath, callback, GtkTypeSystemTray.this);
|
||||
menuEntry = new GtkMenuEntryItem(menuText, imagePath, callback, GtkTypeSystemTray.this);
|
||||
menuEntries.add(menuEntry);
|
||||
|
||||
createMenu();
|
||||
|
@ -291,7 +286,6 @@ class GtkTypeSystemTray extends SystemTray {
|
|||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public
|
||||
void addMenuEntry(final String menuText, final InputStream imageStream, final SystemTrayMenuAction callback) {
|
||||
if (imageStream == null) {
|
||||
|
|
|
@ -317,6 +317,8 @@ class Gtk {
|
|||
*/
|
||||
public static
|
||||
void dispatch(final Runnable runnable) {
|
||||
// FIXME: on mac, check -XstartOnFirstThread.. there are issues with javaFX (possibly SWT as well)
|
||||
|
||||
if (alreadyRunningGTK) {
|
||||
// SWT/JavaFX
|
||||
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_separator_menu_item_new();
|
||||
|
||||
// to create a menu entry WITH an icon.
|
||||
public static native Pointer gtk_image_new_from_file(String iconPath);
|
||||
|
||||
|
|
|
@ -16,66 +16,43 @@
|
|||
|
||||
package dorkbox.systemTray.swing;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JComponent;
|
||||
|
||||
import dorkbox.systemTray.MenuEntry;
|
||||
import dorkbox.systemTray.SystemTray;
|
||||
import dorkbox.systemTray.SystemTrayMenuAction;
|
||||
import dorkbox.systemTray.util.ImageUtils;
|
||||
import dorkbox.util.SwingUtil;
|
||||
|
||||
abstract
|
||||
class SwingMenuEntry implements MenuEntry {
|
||||
private final SwingSystemTrayMenuPopup parent;
|
||||
private final SystemTray systemTray;
|
||||
private final JMenuItem menuItem;
|
||||
private final ActionListener swingCallback;
|
||||
private final int id = SwingSystemTray.MENU_ID_COUNTER.getAndIncrement();
|
||||
|
||||
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 SystemTrayMenuAction callback;
|
||||
|
||||
// this is ALWAYS called on the EDT.
|
||||
SwingMenuEntry(final SwingSystemTrayMenuPopup parentMenu, final String label, final File imagePath, final SystemTrayMenuAction callback,
|
||||
final SystemTray systemTray) {
|
||||
this.parent = parentMenu;
|
||||
this.text = label;
|
||||
this.callback = callback;
|
||||
SwingMenuEntry(JComponent menuItem, final SwingSystemTray systemTray) {
|
||||
this.menuItem = menuItem;
|
||||
this.systemTray = systemTray;
|
||||
|
||||
swingCallback = new ActionListener() {
|
||||
@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);
|
||||
systemTray.getMenu().add(menuItem);
|
||||
}
|
||||
|
||||
parentMenu.add(menuItem);
|
||||
}
|
||||
/**
|
||||
* must always be called in the GTK thread
|
||||
*/
|
||||
abstract
|
||||
void renderText(final String text);
|
||||
|
||||
abstract
|
||||
void setImage_(final File imageFile);
|
||||
|
||||
private
|
||||
void handle() {
|
||||
SystemTrayMenuAction cb = this.callback;
|
||||
if (cb != null) {
|
||||
cb.onClick(systemTray, this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
|
@ -92,38 +69,13 @@ class SwingMenuEntry implements MenuEntry {
|
|||
@Override
|
||||
public
|
||||
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
|
||||
public
|
||||
public final
|
||||
void setImage(final String imagePath) {
|
||||
if (imagePath == null) {
|
||||
setImage_(null);
|
||||
|
@ -134,7 +86,7 @@ class SwingMenuEntry implements MenuEntry {
|
|||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
public final
|
||||
void setImage(final URL imageUrl) {
|
||||
if (imageUrl == null) {
|
||||
setImage_(null);
|
||||
|
@ -145,7 +97,7 @@ class SwingMenuEntry implements MenuEntry {
|
|||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
public final
|
||||
void setImage(final String cacheName, final InputStream imageStream) {
|
||||
if (imageStream == null) {
|
||||
setImage_(null);
|
||||
|
@ -156,7 +108,7 @@ class SwingMenuEntry implements MenuEntry {
|
|||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
public final
|
||||
void setImage(final InputStream imageStream) {
|
||||
if (imageStream == null) {
|
||||
setImage_(null);
|
||||
|
@ -167,27 +119,43 @@ class SwingMenuEntry implements MenuEntry {
|
|||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean hasImage() {
|
||||
return hasLegitIcon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setCallback(final SystemTrayMenuAction callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
public final
|
||||
void remove() {
|
||||
SwingUtil.invokeAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
menuItem.removeActionListener(swingCallback);
|
||||
parent.remove(menuItem);
|
||||
removePrivate();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
105
src/dorkbox/systemTray/swing/SwingMenuEntryItem.java
Normal file
105
src/dorkbox/systemTray/swing/SwingMenuEntryItem.java
Normal 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
56
src/dorkbox/systemTray/swing/SwingMenuEntrySpacer.java
Normal file
56
src/dorkbox/systemTray/swing/SwingMenuEntrySpacer.java
Normal 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) {
|
||||
|
||||
}
|
||||
}
|
64
src/dorkbox/systemTray/swing/SwingMenuEntryStatus.java
Normal file
64
src/dorkbox/systemTray/swing/SwingMenuEntryStatus.java
Normal 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) {
|
||||
|
||||
}
|
||||
}
|
|
@ -17,7 +17,6 @@ package dorkbox.systemTray.swing;
|
|||
|
||||
import java.awt.AWTException;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.Image;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
|
@ -28,9 +27,9 @@ import java.awt.event.MouseEvent;
|
|||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JMenuItem;
|
||||
|
||||
import dorkbox.systemTray.MenuEntry;
|
||||
import dorkbox.systemTray.SystemTrayMenuAction;
|
||||
|
@ -49,10 +48,9 @@ import dorkbox.util.SwingUtil;
|
|||
@SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "WeakerAccess"})
|
||||
public
|
||||
class SwingSystemTray extends dorkbox.systemTray.SystemTray {
|
||||
volatile SwingSystemTrayMenuPopup menu;
|
||||
static final AtomicInteger MENU_ID_COUNTER = new AtomicInteger();
|
||||
|
||||
volatile JMenuItem connectionStatusItem;
|
||||
private volatile String statusText = null;
|
||||
volatile SwingSystemTrayMenuPopup menu;
|
||||
|
||||
volatile SystemTray tray;
|
||||
volatile TrayIcon trayIcon;
|
||||
|
@ -96,17 +94,27 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
|
|||
menuEntry.remove();
|
||||
}
|
||||
tray.menuEntries.clear();
|
||||
|
||||
tray.connectionStatusItem = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected SwingSystemTrayMenuPopup getMenu() {
|
||||
return menu;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public
|
||||
String getStatus() {
|
||||
return this.statusText;
|
||||
synchronized (menuEntries) {
|
||||
MenuEntry menuEntry = menuEntries.get(0);
|
||||
if (menuEntry instanceof SwingMenuEntryStatus) {
|
||||
return menuEntry.getText();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected
|
||||
|
@ -118,33 +126,65 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
|
|||
@Override
|
||||
public
|
||||
void setStatus(final String statusText) {
|
||||
this.statusText = statusText;
|
||||
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
SwingSystemTray tray = SwingSystemTray.this;
|
||||
synchronized (tray) {
|
||||
if (tray.connectionStatusItem == null) {
|
||||
final JMenuItem connectionStatusItem = new JMenuItem(statusText);
|
||||
Font font = connectionStatusItem.getFont();
|
||||
Font font1 = font.deriveFont(Font.BOLD);
|
||||
connectionStatusItem.setFont(font1);
|
||||
synchronized (menuEntries) {
|
||||
// status is ALWAYS at 0 index...
|
||||
SwingMenuEntry menuEntry = null;
|
||||
if (!menuEntries.isEmpty()) {
|
||||
menuEntry = (SwingMenuEntry) menuEntries.get(0);
|
||||
}
|
||||
|
||||
connectionStatusItem.setEnabled(false);
|
||||
tray.menu.add(connectionStatusItem);
|
||||
if (menuEntry instanceof SwingMenuEntryStatus) {
|
||||
// set the text or delete...
|
||||
|
||||
tray.connectionStatusItem = connectionStatusItem;
|
||||
if (statusText == null) {
|
||||
// delete
|
||||
removeMenuEntry(menuEntry);
|
||||
}
|
||||
else {
|
||||
tray.connectionStatusItem.setText(statusText);
|
||||
// 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
|
||||
protected
|
||||
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 + "'");
|
||||
}
|
||||
else {
|
||||
menuEntry = new SwingMenuEntry(menu, menuText, imagePath, callback, tray);
|
||||
menuEntry = new SwingMenuEntryItem(menuText, imagePath, callback, tray);
|
||||
menuEntries.add(menuEntry);
|
||||
}
|
||||
}
|
||||
|
@ -287,7 +327,6 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
|
|||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public
|
||||
void addMenuEntry(final String menuText, final InputStream imageStream, final SystemTrayMenuAction callback) {
|
||||
if (imageStream == null) {
|
||||
|
|
|
@ -81,6 +81,7 @@ class TestTray {
|
|||
};
|
||||
|
||||
this.systemTray.addMenuEntry("Green Mail", GREEN_MAIL, callbackGreen);
|
||||
this.systemTray.addMenuSpacer();
|
||||
|
||||
systemTray.addMenuEntry("Quit", new SystemTrayMenuAction() {
|
||||
@Override
|
||||
|
|
|
@ -115,6 +115,7 @@ class TestTrayJavaFX extends Application {
|
|||
};
|
||||
|
||||
this.systemTray.addMenuEntry("Green Mail", GREEN_MAIL, callbackGreen);
|
||||
this.systemTray.addMenuSpacer();
|
||||
|
||||
systemTray.addMenuEntry("Quit", new SystemTrayMenuAction() {
|
||||
@Override
|
||||
|
|
|
@ -99,6 +99,7 @@ class TestTraySwt {
|
|||
};
|
||||
|
||||
this.systemTray.addMenuEntry("Green Mail", GREEN_MAIL, callbackGreen);
|
||||
this.systemTray.addMenuSpacer();
|
||||
|
||||
systemTray.addMenuEntry("Quit", new SystemTrayMenuAction() {
|
||||
@Override
|
||||
|
|
Loading…
Reference in New Issue
Block a user