From a07c5e8ab8d0616d7a3187fe8c7369d1224d1760 Mon Sep 17 00:00:00 2001 From: nathan Date: Mon, 17 Oct 2016 11:47:25 +0200 Subject: [PATCH] Changed action callback to ActionListener. Added checkbox. --- .../systemTray/{Action.java => Checkbox.java} | 16 +- src/dorkbox/systemTray/Entry.java | 3 +- src/dorkbox/systemTray/Menu.java | 20 +- src/dorkbox/systemTray/SystemTray.java | 24 +- src/dorkbox/systemTray/jna/Windows/Gdi32.java | 1 - src/dorkbox/systemTray/jna/linux/Gtk.java | 11 +- .../systemTray/nativeUI/AwtEntryCheckbox.java | 104 +++++++++ .../systemTray/nativeUI/AwtEntryItem.java | 17 +- .../nativeUI/AwtEntrySeparator.java | 5 +- .../systemTray/nativeUI/AwtEntryStatus.java | 4 +- src/dorkbox/systemTray/nativeUI/AwtMenu.java | 79 ++++--- .../systemTray/nativeUI/GtkEntryCheckbox.java | 172 ++++++++++++++ .../systemTray/nativeUI/GtkEntryItem.java | 12 +- .../nativeUI/GtkEntrySeparator.java | 4 +- .../systemTray/nativeUI/GtkEntryStatus.java | 5 +- src/dorkbox/systemTray/nativeUI/GtkMenu.java | 220 ++++++++++-------- .../swingUI/SwingEntryCheckbox.java | 128 ++++++++++ .../systemTray/swingUI/SwingEntryItem.java | 17 +- .../swingUI/SwingEntrySeparator.java | 5 +- .../systemTray/swingUI/SwingEntryStatus.java | 4 +- .../systemTray/swingUI/SwingEntryWidget.java | 5 +- src/dorkbox/systemTray/swingUI/SwingMenu.java | 78 ++++--- src/dorkbox/systemTray/util/ImageUtils.java | 7 +- src/dorkbox/systemTray/util/MenuBase.java | 78 ++----- src/dorkbox/systemTray/util/checked_32.png | Bin 0 -> 298 bytes test/dorkbox/TestTray.java | 47 ++-- test/dorkbox/TestTrayJavaFX.java | 36 +-- test/dorkbox/TestTraySwt.java | 36 +-- 28 files changed, 813 insertions(+), 325 deletions(-) rename src/dorkbox/systemTray/{Action.java => Checkbox.java} (58%) create mode 100644 src/dorkbox/systemTray/nativeUI/AwtEntryCheckbox.java create mode 100644 src/dorkbox/systemTray/nativeUI/GtkEntryCheckbox.java create mode 100644 src/dorkbox/systemTray/swingUI/SwingEntryCheckbox.java create mode 100644 src/dorkbox/systemTray/util/checked_32.png diff --git a/src/dorkbox/systemTray/Action.java b/src/dorkbox/systemTray/Checkbox.java similarity index 58% rename from src/dorkbox/systemTray/Action.java rename to src/dorkbox/systemTray/Checkbox.java index 28bd48c5..9a27836e 100644 --- a/src/dorkbox/systemTray/Action.java +++ b/src/dorkbox/systemTray/Checkbox.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 dorkbox, llc + * 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. @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package dorkbox.systemTray; +/** + * This represents a common menu-checkbox entry, that is cross platform in nature + */ public -interface Action { +interface Checkbox extends Entry { /** - * This method will ALWAYS be called in the swing EDT. If there is work conducted in this method, it will slow-down the GUI. - * - * @param systemTray this is the parent, system tray object - * @param parent this is the parent menu of this menu entry - * @param entry this is the menu entry that was clicked + * @return true if this checkbox is selected, false if not */ - void onClick(SystemTray systemTray, Menu parent, final Entry entry); + boolean getState(); } diff --git a/src/dorkbox/systemTray/Entry.java b/src/dorkbox/systemTray/Entry.java index 389bd553..b15c6454 100644 --- a/src/dorkbox/systemTray/Entry.java +++ b/src/dorkbox/systemTray/Entry.java @@ -16,6 +16,7 @@ package dorkbox.systemTray; +import java.awt.event.ActionListener; import java.io.File; import java.io.InputStream; import java.net.URL; @@ -97,7 +98,7 @@ interface Entry { * * @param callback the callback to set. If null, the callback is safely removed. */ - void setCallback(Action callback); + void setCallback(ActionListener callback); /** * Sets a menu entry shortcut key (Mnemonic) so that menu entry can be "selected" via the keyboard while the menu is displayed. diff --git a/src/dorkbox/systemTray/Menu.java b/src/dorkbox/systemTray/Menu.java index 9bfba2c3..185b5142 100644 --- a/src/dorkbox/systemTray/Menu.java +++ b/src/dorkbox/systemTray/Menu.java @@ -15,6 +15,7 @@ */ package dorkbox.systemTray; +import java.awt.event.ActionListener; import java.io.InputStream; import java.net.URL; @@ -86,7 +87,7 @@ interface Menu extends Entry { * @param menuText string of the text you want to appear * @param callback callback that will be executed when this menu entry is clicked */ - Entry addEntry(String menuText, Action callback); + Entry addEntry(String menuText, ActionListener callback); /** * Adds a menu entry with text + image @@ -95,7 +96,7 @@ interface Menu extends Entry { * @param imagePath the image (full path required) to use. If null, no image will be used * @param callback callback that will be executed when this menu entry is clicked */ - Entry addEntry(String menuText, String imagePath, Action callback); + Entry addEntry(String menuText, String imagePath, ActionListener callback); /** * Adds a menu entry with text + image @@ -104,7 +105,7 @@ interface Menu extends Entry { * @param imageUrl the URL 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 */ - Entry addEntry(String menuText, URL imageUrl, Action callback); + Entry addEntry(String menuText, URL imageUrl, ActionListener callback); /** * Adds a menu entry with text + image @@ -114,7 +115,7 @@ interface Menu extends Entry { * @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 */ - Entry addEntry(String menuText, String cacheName, InputStream imageStream, Action callback); + Entry addEntry(String menuText, String cacheName, InputStream imageStream, ActionListener callback); /** * Adds a menu entry with text + image @@ -123,10 +124,19 @@ interface Menu extends Entry { * @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 */ - Entry addEntry(String menuText, InputStream imageStream, Action callback); + Entry addEntry(String menuText, InputStream imageStream, ActionListener callback); + /** + * Adds a check-box menu entry with text + * + * @param menuText string of the text you want to appear + * @param callback callback that will be executed when this menu entry is clicked + */ + Checkbox addCheckbox(String menuText, ActionListener callback); + + /** * Adds a sub-menu entry with text (no image) diff --git a/src/dorkbox/systemTray/SystemTray.java b/src/dorkbox/systemTray/SystemTray.java index bb959d92..dbc1a2ff 100644 --- a/src/dorkbox/systemTray/SystemTray.java +++ b/src/dorkbox/systemTray/SystemTray.java @@ -17,6 +17,7 @@ package dorkbox.systemTray; import java.awt.GraphicsEnvironment; import java.awt.HeadlessException; +import java.awt.event.ActionListener; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; @@ -924,7 +925,7 @@ class SystemTray implements Menu { */ @Override public - void setCallback(final Action callback) { + void setCallback(final ActionListener callback) { // NO OP. } @@ -992,7 +993,7 @@ class SystemTray implements Menu { * @param callback callback that will be executed when this menu entry is clicked */ public final - Entry addEntry(String menuText, Action callback) { + Entry addEntry(String menuText, ActionListener callback) { return addEntry(menuText, (String) null, callback); } @@ -1004,7 +1005,7 @@ class SystemTray implements Menu { * @param callback callback that will be executed when this menu entry is clicked */ public final - Entry addEntry(String menuText, String imagePath, Action callback) { + Entry addEntry(String menuText, String imagePath, ActionListener callback) { return systemTrayMenu.addEntry(menuText, imagePath, callback); } @@ -1016,7 +1017,7 @@ class SystemTray implements Menu { * @param callback callback that will be executed when this menu entry is clicked */ public final - Entry addEntry(String menuText, URL imageUrl, Action callback) { + Entry addEntry(String menuText, URL imageUrl, ActionListener callback) { return systemTrayMenu.addEntry(menuText, imageUrl, callback); } @@ -1029,7 +1030,7 @@ class SystemTray implements Menu { * @param callback callback that will be executed when this menu entry is clicked */ public - Entry addEntry(String menuText, String cacheName, InputStream imageStream, Action callback) { + Entry addEntry(String menuText, String cacheName, InputStream imageStream, ActionListener callback) { return systemTrayMenu.addEntry(menuText, cacheName, imageStream, callback); } @@ -1041,11 +1042,22 @@ class SystemTray implements Menu { * @param callback callback that will be executed when this menu entry is clicked */ public final - Entry addEntry(String menuText, InputStream imageStream, Action callback) { + Entry addEntry(String menuText, InputStream imageStream, ActionListener callback) { return systemTrayMenu.addEntry(menuText, imageStream, callback); } + /** + * Adds a check-box menu entry to the tray icon with text + * + * @param menuText string of the text you want to appear + * @param callback callback that will be executed when this menu entry is clicked + */ + @Override + public + Checkbox addCheckbox(final String menuText, final ActionListener callback) { + return systemTrayMenu.addCheckbox(menuText, callback); + } diff --git a/src/dorkbox/systemTray/jna/Windows/Gdi32.java b/src/dorkbox/systemTray/jna/Windows/Gdi32.java index c36da647..89726fd5 100644 --- a/src/dorkbox/systemTray/jna/Windows/Gdi32.java +++ b/src/dorkbox/systemTray/jna/Windows/Gdi32.java @@ -32,7 +32,6 @@ class Gdi32 { } public static final int LOGPIXELSX = 88; - public static final int LOGPIXELSY = 90; /** diff --git a/src/dorkbox/systemTray/jna/linux/Gtk.java b/src/dorkbox/systemTray/jna/linux/Gtk.java index d4a92c87..5a45a8f1 100644 --- a/src/dorkbox/systemTray/jna/linux/Gtk.java +++ b/src/dorkbox/systemTray/jna/linux/Gtk.java @@ -17,6 +17,8 @@ package dorkbox.systemTray.jna.linux; import static dorkbox.systemTray.SystemTray.logger; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.util.LinkedList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -24,9 +26,7 @@ import java.util.concurrent.TimeUnit; import com.sun.jna.Function; import com.sun.jna.Pointer; -import dorkbox.systemTray.Action; import dorkbox.systemTray.Entry; -import dorkbox.systemTray.Menu; import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.jna.JnaHelper; import dorkbox.systemTray.util.JavaFX; @@ -422,11 +422,11 @@ class Gtk { * @param callback will never be null. */ public static - void proxyClick(final Menu parent, final Entry menuEntry, final Action callback) { + void proxyClick(final Entry menuEntry, final ActionListener callback) { Gtk.isDispatch = true; try { - callback.onClick(parent.getSystemTray(), parent, menuEntry); + callback.actionPerformed(new ActionEvent(menuEntry, ActionEvent.ACTION_PERFORMED, "")); } catch (Throwable throwable) { SystemTray.logger.error("Error calling menu entry {} click event.", menuEntry.getText(), throwable); } @@ -470,6 +470,9 @@ class Gtk { // uses '_' to define which key is the mnemonic public static native Pointer gtk_image_menu_item_new_with_mnemonic(String label); + public static native Pointer gtk_check_menu_item_new_with_mnemonic (String label); + + public static native boolean gtk_check_menu_item_get_active (Pointer check_menu_item); public static native void gtk_image_menu_item_set_image(Pointer image_menu_item, Pointer image); diff --git a/src/dorkbox/systemTray/nativeUI/AwtEntryCheckbox.java b/src/dorkbox/systemTray/nativeUI/AwtEntryCheckbox.java new file mode 100644 index 00000000..3697950d --- /dev/null +++ b/src/dorkbox/systemTray/nativeUI/AwtEntryCheckbox.java @@ -0,0 +1,104 @@ +/* + * 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.nativeUI; + +import java.awt.CheckboxMenuItem; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; + +import dorkbox.systemTray.Checkbox; +import dorkbox.systemTray.SystemTray; + +class AwtEntryCheckbox extends AwtEntry implements Checkbox { + + private final ActionListener swingCallback; + + private volatile ActionListener callback; + + // this is ALWAYS called on the EDT. + AwtEntryCheckbox(final AwtMenu parent, final ActionListener callback) { + super(parent, new java.awt.CheckboxMenuItem()); + this.callback = callback; + + if (callback != null) { + _native.setEnabled(true); + swingCallback = new ActionListener() { + @Override + public + void actionPerformed(ActionEvent e) { + // we want it to run on the EDT + handle(); + } + }; + + _native.addActionListener(swingCallback); + } else { + _native.setEnabled(false); + swingCallback = null; + } + } + + /** + * @return true if this checkbox is selected, false if not + */ + public + boolean getState() { + return ((CheckboxMenuItem) _native).getState(); + } + + @Override + public + void setCallback(final ActionListener callback) { + this.callback = callback; + } + + private + void handle() { + ActionListener cb = this.callback; + if (cb != null) { + try { + cb.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "")); + } catch (Throwable throwable) { + SystemTray.logger.error("Error calling menu entry {} click event.", getText(), throwable); + } + } + } + + // always called in the EDT + @Override + void renderText(final String text) { + _native.setLabel(text); + } + + + // not supported! + @Override + public + boolean hasImage() { + return false; + } + + // not supported! + @Override + void setImage_(final File imageFile) { + } + + @Override + void removePrivate() { + _native.removeActionListener(swingCallback); + } +} diff --git a/src/dorkbox/systemTray/nativeUI/AwtEntryItem.java b/src/dorkbox/systemTray/nativeUI/AwtEntryItem.java index 15fad7f5..70d71830 100644 --- a/src/dorkbox/systemTray/nativeUI/AwtEntryItem.java +++ b/src/dorkbox/systemTray/nativeUI/AwtEntryItem.java @@ -19,16 +19,16 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; -import dorkbox.systemTray.Action; +import dorkbox.systemTray.SystemTray; class AwtEntryItem extends AwtEntry { private final ActionListener swingCallback; - private volatile Action callback; + private volatile ActionListener callback; // this is ALWAYS called on the EDT. - AwtEntryItem(final AwtMenu parent, final Action callback) { + AwtEntryItem(final AwtMenu parent, final ActionListener callback) { super(parent, new java.awt.MenuItem()); this.callback = callback; @@ -53,14 +53,19 @@ class AwtEntryItem extends AwtEntry { @Override public - void setCallback(final Action callback) { + void setCallback(final ActionListener callback) { this.callback = callback; } private void handle() { - if (callback != null) { - callback.onClick(getParent().getSystemTray(), getParent(), this); + ActionListener cb = this.callback; + if (cb != null) { + try { + cb.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "")); + } catch (Throwable throwable) { + SystemTray.logger.error("Error calling menu entry {} click event.", getText(), throwable); + } } } diff --git a/src/dorkbox/systemTray/nativeUI/AwtEntrySeparator.java b/src/dorkbox/systemTray/nativeUI/AwtEntrySeparator.java index b6e7f880..c6addf3e 100644 --- a/src/dorkbox/systemTray/nativeUI/AwtEntrySeparator.java +++ b/src/dorkbox/systemTray/nativeUI/AwtEntrySeparator.java @@ -16,10 +16,9 @@ package dorkbox.systemTray.nativeUI; import java.awt.MenuItem; +import java.awt.event.ActionListener; import java.io.File; -import dorkbox.systemTray.Action; - class AwtEntrySeparator extends AwtEntry implements dorkbox.systemTray.Separator { // this is ALWAYS called on the EDT. @@ -53,6 +52,6 @@ class AwtEntrySeparator extends AwtEntry implements dorkbox.systemTray.Separator @Override public - void setCallback(final Action callback) { + void setCallback(final ActionListener callback) { } } diff --git a/src/dorkbox/systemTray/nativeUI/AwtEntryStatus.java b/src/dorkbox/systemTray/nativeUI/AwtEntryStatus.java index 5df5fa76..764cafcf 100644 --- a/src/dorkbox/systemTray/nativeUI/AwtEntryStatus.java +++ b/src/dorkbox/systemTray/nativeUI/AwtEntryStatus.java @@ -19,9 +19,9 @@ import static java.awt.Font.DIALOG; import java.awt.Font; import java.awt.MenuItem; +import java.awt.event.ActionListener; import java.io.File; -import dorkbox.systemTray.Action; import dorkbox.systemTray.Status; class AwtEntryStatus extends AwtEntry implements Status { @@ -70,6 +70,6 @@ class AwtEntryStatus extends AwtEntry implements Status { @Override public - void setCallback(final Action callback) { + void setCallback(final ActionListener callback) { } } diff --git a/src/dorkbox/systemTray/nativeUI/AwtMenu.java b/src/dorkbox/systemTray/nativeUI/AwtMenu.java index 27f7874c..a1505677 100644 --- a/src/dorkbox/systemTray/nativeUI/AwtMenu.java +++ b/src/dorkbox/systemTray/nativeUI/AwtMenu.java @@ -18,10 +18,11 @@ package dorkbox.systemTray.nativeUI; import java.awt.MenuShortcut; import java.awt.PopupMenu; +import java.awt.event.ActionListener; import java.io.File; import java.util.concurrent.atomic.AtomicReference; -import dorkbox.systemTray.Action; +import dorkbox.systemTray.Checkbox; import dorkbox.systemTray.Entry; import dorkbox.systemTray.Menu; import dorkbox.systemTray.Status; @@ -97,36 +98,28 @@ class AwtMenu extends MenuBase implements NativeUI { } /** - * Will add a new menu entry, or update one if it already exists + * Will add a new menu entry * NOT ALWAYS CALLED ON EDT */ protected final - Entry addEntry_(final String menuText, final File imagePath, final Action callback) { + Entry addEntry_(final String menuText, final File imagePath, final ActionListener callback) { if (menuText == null) { throw new NullPointerException("Menu text cannot be null"); } final AtomicReference value = new AtomicReference(); + // must always be called on the EDT dispatchAndWait(new Runnable() { @Override public void run() { synchronized (menuEntries) { - Entry entry = get(menuText); - - if (entry == null) { - // must always be called on the EDT - entry = new AwtEntryItem(AwtMenu.this, callback); - entry.setText(menuText); - entry.setImage(imagePath); - - menuEntries.add(entry); - } else if (entry instanceof AwtEntryItem) { - entry.setText(menuText); - entry.setImage(imagePath); - } + Entry entry = entry = new AwtEntryItem(AwtMenu.this, callback); + entry.setText(menuText); + entry.setImage(imagePath); + menuEntries.add(entry); value.set(entry); } } @@ -136,7 +129,39 @@ class AwtMenu extends MenuBase implements NativeUI { } /** - * Will add a new sub-menu entry, or update one if it already exists + * Will add a new checkbox menu entry + * NOT ALWAYS CALLED ON DISPATCH + */ + @Override + protected + Checkbox addCheckbox_(final String menuText, final ActionListener callback) { + if (menuText == null) { + throw new NullPointerException("Menu text cannot be null"); + } + + final AtomicReference value = new AtomicReference(); + + // must always be called on the EDT + dispatchAndWait(new Runnable() { + @Override + public + void run() { + synchronized (menuEntries) { + Entry entry = new AwtEntryCheckbox(AwtMenu.this, callback); + entry.setText(menuText); + + menuEntries.add(entry); + value.set((Checkbox) entry); + } + } + }); + + return value.get(); + } + + + /** + * Will add a new sub-menu entry * NOT ALWAYS CALLED ON EDT */ protected final @@ -147,28 +172,20 @@ class AwtMenu extends MenuBase implements NativeUI { final AtomicReference value = new AtomicReference(); + // must always be called on the EDT dispatchAndWait(new Runnable() { @Override public void run() { synchronized (menuEntries) { - Entry entry = get(menuText); + Entry entry = new AwtMenu(getSystemTray(), AwtMenu.this, new java.awt.Menu()); + _native.add(((AwtMenu) entry)._native); // have to add it to our native item separately - if (entry == null) { - // must always be called on the EDT - entry = new AwtMenu(getSystemTray(), AwtMenu.this, new java.awt.Menu()); - _native.add(((AwtMenu) entry)._native); // have to add it separately - - entry.setText(menuText); - entry.setImage(imagePath); - value.set((Menu) entry); - - } else if (entry instanceof AwtMenu) { - entry.setText(menuText); - entry.setImage(imagePath); - } + entry.setText(menuText); + entry.setImage(imagePath); menuEntries.add(entry); + value.set((Menu) entry); } } }); diff --git a/src/dorkbox/systemTray/nativeUI/GtkEntryCheckbox.java b/src/dorkbox/systemTray/nativeUI/GtkEntryCheckbox.java new file mode 100644 index 00000000..bed76d9a --- /dev/null +++ b/src/dorkbox/systemTray/nativeUI/GtkEntryCheckbox.java @@ -0,0 +1,172 @@ +/* + * 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.nativeUI; + +import java.awt.event.ActionListener; +import java.io.File; + +import com.sun.jna.NativeLong; +import com.sun.jna.Pointer; + +import dorkbox.systemTray.Checkbox; +import dorkbox.systemTray.jna.linux.GCallback; +import dorkbox.systemTray.jna.linux.Gobject; +import dorkbox.systemTray.jna.linux.Gtk; +import dorkbox.systemTray.util.ImageUtils; + +class GtkEntryCheckbox extends GtkEntry implements GCallback, Checkbox { + 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 ActionListener callback; + private volatile Pointer image; + + // The mnemonic will ONLY show-up once a menu entry is selected. IT WILL NOT show up before then! + // AppIndicators will only show if you use the keyboard to navigate + // GtkStatusIconTray will show on mouse+keyboard movement + private volatile char mnemonicKey = 0; + + /** + * 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 + */ + GtkEntryCheckbox(final GtkMenu parent, final ActionListener callback) { + super(parent, Gtk.gtk_check_menu_item_new_with_mnemonic("")); + this.callback = callback; + + // cannot be done in a static initializer, because the tray icon size might not yet have been determined + if (transparentIcon == null) { + transparentIcon = ImageUtils.getTransparentImage(ImageUtils.ENTRY_SIZE); + } + + if (callback != null) { + Gtk.gtk_widget_set_sensitive(_native, Gtk.TRUE); + nativeLong = Gobject.g_signal_connect_object(_native, "activate", this, null, 0); + } + else { + Gtk.gtk_widget_set_sensitive(_native, Gtk.FALSE); + nativeLong = null; + } + } + + @Override + public + void setShortcut(final char key) { + this.mnemonicKey = Character.toLowerCase(key); + + Gtk.dispatch(new Runnable() { + @Override + public + void run() { + renderText(getText()); + } + }); + } + + /** + * @return true if this checkbox is selected, false if not + */ + public + boolean getState() { + return Gtk.gtk_check_menu_item_get_active(_native); + } + + @Override + public + void setCallback(final ActionListener callback) { + this.callback = callback; + } + + // called by native code + @Override + public + int callback(final Pointer instance, final Pointer data) { + final ActionListener cb = this.callback; + if (cb != null) { + Gtk.proxyClick(GtkEntryCheckbox.this, cb); + } + + return Gtk.TRUE; + } + + @Override + public + boolean hasImage() { + return 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. + *

+ * called on the DISPATCH thread + */ + void setSpacerImage(final boolean everyoneElseHasImages) { +// if (true) { +// // 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(_native); +// } +// +// if (everyoneElseHasImages) { +// image = Gtk.gtk_image_new_from_file(transparentIcon.getAbsolutePath()); +// Gtk.gtk_image_menu_item_set_image(_native, image); +// +// // must always re-set always-show after setting the image +// Gtk.gtk_image_menu_item_set_always_show_image(_native, Gtk.TRUE); +// } +// +// Gtk.gtk_widget_show_all(_native); + } + + /** + * must always be called in the GTK thread + */ + void renderText(String text) { + if (this.mnemonicKey != 0) { + // they are CASE INSENSITIVE! + int i = text.toLowerCase() + .indexOf(this.mnemonicKey); + + if (i >= 0) { + text = text.substring(0, i) + "_" + text.substring(i); + } + } + + Gtk.gtk_menu_item_set_label(_native, text); + Gtk.gtk_widget_show_all(_native); + } + + void setImage_(final File imageFile) { + } + + void removePrivate() { + callback = null; + + if (image != null) { + Gtk.gtk_widget_destroy(image); + image = null; + } + } +} diff --git a/src/dorkbox/systemTray/nativeUI/GtkEntryItem.java b/src/dorkbox/systemTray/nativeUI/GtkEntryItem.java index 74dac6aa..735ab16d 100644 --- a/src/dorkbox/systemTray/nativeUI/GtkEntryItem.java +++ b/src/dorkbox/systemTray/nativeUI/GtkEntryItem.java @@ -15,12 +15,12 @@ */ package dorkbox.systemTray.nativeUI; +import java.awt.event.ActionListener; import java.io.File; import com.sun.jna.NativeLong; import com.sun.jna.Pointer; -import dorkbox.systemTray.Action; import dorkbox.systemTray.jna.linux.GCallback; import dorkbox.systemTray.jna.linux.Gobject; import dorkbox.systemTray.jna.linux.Gtk; @@ -33,7 +33,7 @@ class GtkEntryItem extends GtkEntry implements GCallback { private final NativeLong nativeLong; // these have to be volatile, because they can be changed from any thread - private volatile Action callback; + private volatile ActionListener 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 @@ -48,7 +48,7 @@ class GtkEntryItem extends GtkEntry implements GCallback { * 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 */ - GtkEntryItem(final GtkMenu parent, final Action callback) { + GtkEntryItem(final GtkMenu parent, final ActionListener callback) { super(parent, Gtk.gtk_image_menu_item_new_with_mnemonic("")); this.callback = callback; @@ -83,7 +83,7 @@ class GtkEntryItem extends GtkEntry implements GCallback { @Override public - void setCallback(final Action callback) { + void setCallback(final ActionListener callback) { this.callback = callback; } @@ -91,9 +91,9 @@ class GtkEntryItem extends GtkEntry implements GCallback { @Override public int callback(final Pointer instance, final Pointer data) { - final Action cb = this.callback; + final ActionListener cb = this.callback; if (cb != null) { - Gtk.proxyClick(getParent(), GtkEntryItem.this, cb); + Gtk.proxyClick(GtkEntryItem.this, cb); } return Gtk.TRUE; diff --git a/src/dorkbox/systemTray/nativeUI/GtkEntrySeparator.java b/src/dorkbox/systemTray/nativeUI/GtkEntrySeparator.java index 96b3dae2..19cf8fb8 100644 --- a/src/dorkbox/systemTray/nativeUI/GtkEntrySeparator.java +++ b/src/dorkbox/systemTray/nativeUI/GtkEntrySeparator.java @@ -15,9 +15,9 @@ */ package dorkbox.systemTray.nativeUI; +import java.awt.event.ActionListener; import java.io.File; -import dorkbox.systemTray.Action; import dorkbox.systemTray.Separator; import dorkbox.systemTray.jna.linux.Gtk; @@ -56,7 +56,7 @@ class GtkEntrySeparator extends GtkEntry implements Separator { @Override public - void setCallback(final Action callback) { + void setCallback(final ActionListener callback) { } @Override diff --git a/src/dorkbox/systemTray/nativeUI/GtkEntryStatus.java b/src/dorkbox/systemTray/nativeUI/GtkEntryStatus.java index a3ea7137..e6a25dc3 100644 --- a/src/dorkbox/systemTray/nativeUI/GtkEntryStatus.java +++ b/src/dorkbox/systemTray/nativeUI/GtkEntryStatus.java @@ -15,7 +15,8 @@ */ package dorkbox.systemTray.nativeUI; -import dorkbox.systemTray.Action; +import java.awt.event.ActionListener; + import dorkbox.systemTray.jna.linux.Gtk; // you might wonder WHY this extends MenuEntryItem -- the reason is that an AppIndicator "status" will be offset from everyone else, @@ -47,7 +48,7 @@ class GtkEntryStatus extends GtkEntryItem { @Override public - void setCallback(final Action callback) { + void setCallback(final ActionListener callback) { } @Override diff --git a/src/dorkbox/systemTray/nativeUI/GtkMenu.java b/src/dorkbox/systemTray/nativeUI/GtkMenu.java index 74702a2e..8b979fc5 100644 --- a/src/dorkbox/systemTray/nativeUI/GtkMenu.java +++ b/src/dorkbox/systemTray/nativeUI/GtkMenu.java @@ -16,6 +16,7 @@ package dorkbox.systemTray.nativeUI; +import java.awt.event.ActionListener; import java.io.File; import java.util.ArrayList; import java.util.Iterator; @@ -23,7 +24,7 @@ import java.util.concurrent.atomic.AtomicReference; import com.sun.jna.Pointer; -import dorkbox.systemTray.Action; +import dorkbox.systemTray.Checkbox; import dorkbox.systemTray.Entry; import dorkbox.systemTray.Menu; import dorkbox.systemTray.SystemTray; @@ -62,6 +63,127 @@ class GtkMenu extends MenuBase implements NativeUI { // only needed for AppIndicator } + /** + * Will add a new menu entry + * NOT ALWAYS CALLED ON DISPATCH + */ + protected + Entry addEntry_(final String menuText, final File imagePath, final ActionListener callback) { + // 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 + + if (menuText == null) { + throw new NullPointerException("Menu text cannot be null"); + } + + // have to wait for the value + final AtomicReference value = new AtomicReference(); + + // must always be called on DISPATCH + dispatchAndWait(new Runnable() { + @Override + public + void run() { + 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(); + + Entry menuEntry = new GtkEntryItem(GtkMenu.this, callback); + menuEntry.setText(menuText); + menuEntry.setImage(imagePath); + + menuEntries.add(menuEntry); + value.set(menuEntry); + + createMenu(); + } + } + }); + + return value.get(); + } + + /** + * Will add a new checkbox menu entry + * NOT ALWAYS CALLED ON DISPATCH + */ + @Override + protected + Checkbox addCheckbox_(final String menuText, final ActionListener callback) { + if (menuText == null) { + throw new NullPointerException("Menu text cannot be null"); + } + + final AtomicReference value = new AtomicReference(); + + // must always be called on DISPATCH + dispatchAndWait(new Runnable() { + @Override + public + void run() { + 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(); + + Entry entry = new GtkEntryCheckbox(GtkMenu.this, callback); + entry.setText(menuText); + + menuEntries.add(entry); + value.set((Checkbox) entry); + + createMenu(); + } + } + }); + + return value.get(); + } + + + /** + * Will add a new menu entry + * NOT ALWAYS CALLED ON DISPATCH + */ + protected + Menu addMenu_(final String menuText, final File imagePath) { + // 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 + + if (menuText == null) { + throw new NullPointerException("Menu text cannot be null"); + } + + final AtomicReference

value = new AtomicReference(); + + // must always be called on DISPATCH + dispatchAndWait(new Runnable() { + @Override + public + void run() { + 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(); + + GtkMenu subMenu = new GtkMenu(getSystemTray(), GtkMenu.this); + subMenu.setText(menuText); + subMenu.setImage(imagePath); + + menuEntries.add(subMenu); + value.set(subMenu); + + createMenu(); + } + } + }); + + return value.get(); + } + + + /** * Necessary to guarantee all updates occur on the dispatch thread */ @@ -136,10 +258,6 @@ class GtkMenu extends MenuBase implements NativeUI { }); } - - - - // public here so that Swing/Gtk/AppIndicator can override this @Override public @@ -336,98 +454,6 @@ class GtkMenu extends MenuBase implements NativeUI { } - /** - * Will add a new menu entry, or update one if it already exists - */ - protected - Entry addEntry_(final String menuText, final File imagePath, final Action callback) { - // 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 - - if (menuText == null) { - throw new NullPointerException("Menu text cannot be null"); - } - - // have to wait for the value - final AtomicReference value = new AtomicReference(); - - dispatchAndWait(new Runnable() { - @Override - public - void run() { - synchronized (menuEntries) { - Entry menuEntry = get(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 GtkEntryItem(GtkMenu.this, callback); - menuEntry.setText(menuText); - menuEntry.setImage(imagePath); - menuEntries.add(menuEntry); - - createMenu(); - } else if (menuEntry instanceof GtkEntryItem) { - menuEntry.setText(menuText); - menuEntry.setImage(imagePath); - } - - value.set(menuEntry); - } - } - }); - - return value.get(); - } - - /** - * Will add a new menu entry, or update one if it already exists - */ - protected - Menu addMenu_(final String menuText, final File imagePath) { - // 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 - - if (menuText == null) { - throw new NullPointerException("Menu text cannot be null"); - } - - final AtomicReference value = new AtomicReference(); - - dispatchAndWait(new Runnable() { - @Override - public - void run() { - synchronized (menuEntries) { - Entry menuEntry = get(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(); - - GtkMenu subMenu = new GtkMenu(getSystemTray(), GtkMenu.this); - subMenu.setText(menuText); - subMenu.setImage(imagePath); - - menuEntries.add(subMenu); - - value.set(subMenu); - - createMenu(); - } else if (menuEntry instanceof GtkMenu) { - menuEntry.setText(menuText); - menuEntry.setImage(imagePath); - - value.set(((GtkMenu) menuEntry)); - } - } - } - }); - - return value.get(); - } - // a child will always remove itself from the parent. @Override public diff --git a/src/dorkbox/systemTray/swingUI/SwingEntryCheckbox.java b/src/dorkbox/systemTray/swingUI/SwingEntryCheckbox.java new file mode 100644 index 00000000..7df49d11 --- /dev/null +++ b/src/dorkbox/systemTray/swingUI/SwingEntryCheckbox.java @@ -0,0 +1,128 @@ +/* + * 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.swingUI; + +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.Checkbox; +import dorkbox.systemTray.SystemTray; +import dorkbox.systemTray.util.ImageUtils; + +class SwingEntryCheckbox extends SwingEntry implements Checkbox { + + private final ActionListener swingCallback; + + private volatile ActionListener callback; + + private static ImageIcon checkedIcon; + private static ImageIcon uncheckedIcon; + private volatile boolean isChecked = false; + + + // this is ALWAYS called on the EDT. + SwingEntryCheckbox(final SwingMenu parent, final ActionListener callback) { + super(parent, new AdjustedJMenuItem()); + this.callback = callback; + + if (checkedIcon == null) { + // from Brankic1979, public domain + File checkedFile = ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, ImageUtils.class.getResource("checked_32.png")); + checkedIcon = new ImageIcon(checkedFile.getAbsolutePath()); + + File uncheckedFile = ImageUtils.getTransparentImage(ImageUtils.ENTRY_SIZE); + uncheckedIcon = new ImageIcon(uncheckedFile.getAbsolutePath()); + } + + ((JMenuItem) _native).setIcon(uncheckedIcon); + + if (callback != null) { + _native.setEnabled(true); + swingCallback = new ActionListener() { + @Override + public + void actionPerformed(ActionEvent e) { + // we want it to run on the EDT + if (isChecked) { + ((JMenuItem) _native).setIcon(uncheckedIcon); + } else { + ((JMenuItem) _native).setIcon(checkedIcon); + } + isChecked = !isChecked; + + handle(); + } + }; + + ((JMenuItem) _native).addActionListener(swingCallback); + } else { + _native.setEnabled(false); + swingCallback = null; + } + } + + /** + * @return true if this checkbox is selected, false if not + */ + public + boolean getState() { + return isChecked; + } + + @Override + public + void setCallback(final ActionListener callback) { + this.callback = callback; + } + + private + void handle() { + ActionListener cb = this.callback; + if (cb != null) { + try { + cb.actionPerformed(new ActionEvent((Checkbox)this, ActionEvent.ACTION_PERFORMED, "")); + } catch (Throwable throwable) { + SystemTray.logger.error("Error calling menu entry {} click event.", getText(), throwable); + } + } + } + + // checkbox image is always present + @Override + public + boolean hasImage() { + return true; + } + + @Override + void removePrivate() { + ((JMenuItem) _native).removeActionListener(swingCallback); + } + + // always called in the EDT + @Override + void renderText(final String text) { + ((JMenuItem) _native).setText(text); + } + + @Override + void setImage_(final File imageFile) { + } +} diff --git a/src/dorkbox/systemTray/swingUI/SwingEntryItem.java b/src/dorkbox/systemTray/swingUI/SwingEntryItem.java index 85ba7478..0af6d263 100644 --- a/src/dorkbox/systemTray/swingUI/SwingEntryItem.java +++ b/src/dorkbox/systemTray/swingUI/SwingEntryItem.java @@ -22,7 +22,7 @@ import java.io.File; import javax.swing.ImageIcon; import javax.swing.JMenuItem; -import dorkbox.systemTray.Action; +import dorkbox.systemTray.SystemTray; import dorkbox.util.SwingUtil; class SwingEntryItem extends SwingEntry { @@ -30,10 +30,10 @@ class SwingEntryItem extends SwingEntry { private final ActionListener swingCallback; private volatile boolean hasLegitIcon = false; - private volatile Action callback; + private volatile ActionListener callback; // this is ALWAYS called on the EDT. - SwingEntryItem(final SwingMenu parent, final Action callback) { + SwingEntryItem(final SwingMenu parent, final ActionListener callback) { super(parent, new AdjustedJMenuItem()); this.callback = callback; @@ -58,14 +58,19 @@ class SwingEntryItem extends SwingEntry { @Override public - void setCallback(final Action callback) { + void setCallback(final ActionListener callback) { this.callback = callback; } private void handle() { - if (callback != null) { - callback.onClick(getParent().getSystemTray(), getParent(), this); + ActionListener cb = this.callback; + if (cb != null) { + try { + cb.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "")); + } catch (Throwable throwable) { + SystemTray.logger.error("Error calling menu entry {} click event.", getText(), throwable); + } } } diff --git a/src/dorkbox/systemTray/swingUI/SwingEntrySeparator.java b/src/dorkbox/systemTray/swingUI/SwingEntrySeparator.java index b1d74b2d..d5a23eef 100644 --- a/src/dorkbox/systemTray/swingUI/SwingEntrySeparator.java +++ b/src/dorkbox/systemTray/swingUI/SwingEntrySeparator.java @@ -15,12 +15,11 @@ */ package dorkbox.systemTray.swingUI; +import java.awt.event.ActionListener; import java.io.File; import javax.swing.JSeparator; -import dorkbox.systemTray.Action; - class SwingEntrySeparator extends SwingEntry implements dorkbox.systemTray.Separator { // this is ALWAYS called on the EDT. @@ -54,6 +53,6 @@ class SwingEntrySeparator extends SwingEntry implements dorkbox.systemTray.Separ @Override public - void setCallback(final Action callback) { + void setCallback(final ActionListener callback) { } } diff --git a/src/dorkbox/systemTray/swingUI/SwingEntryStatus.java b/src/dorkbox/systemTray/swingUI/SwingEntryStatus.java index 7eab9f20..6a5e24e5 100644 --- a/src/dorkbox/systemTray/swingUI/SwingEntryStatus.java +++ b/src/dorkbox/systemTray/swingUI/SwingEntryStatus.java @@ -16,11 +16,11 @@ package dorkbox.systemTray.swingUI; import java.awt.Font; +import java.awt.event.ActionListener; import java.io.File; import javax.swing.JMenuItem; -import dorkbox.systemTray.Action; import dorkbox.systemTray.Status; class SwingEntryStatus extends SwingEntry implements Status { @@ -65,7 +65,7 @@ class SwingEntryStatus extends SwingEntry implements Status { @Override public - void setCallback(final Action callback) { + void setCallback(final ActionListener callback) { } } diff --git a/src/dorkbox/systemTray/swingUI/SwingEntryWidget.java b/src/dorkbox/systemTray/swingUI/SwingEntryWidget.java index 60938631..9e58078e 100644 --- a/src/dorkbox/systemTray/swingUI/SwingEntryWidget.java +++ b/src/dorkbox/systemTray/swingUI/SwingEntryWidget.java @@ -15,12 +15,11 @@ */ package dorkbox.systemTray.swingUI; +import java.awt.event.ActionListener; import java.io.File; import javax.swing.JComponent; -import dorkbox.systemTray.Action; - // TODO: buggy. The menu will **sometimes** stop responding to the "enter" key after this. Mnemonics still work however. class SwingEntryWidget extends SwingEntry implements dorkbox.systemTray.Separator { @@ -57,6 +56,6 @@ class SwingEntryWidget extends SwingEntry implements dorkbox.systemTray.Separato @Override public - void setCallback(final Action callback) { + void setCallback(final ActionListener callback) { } } diff --git a/src/dorkbox/systemTray/swingUI/SwingMenu.java b/src/dorkbox/systemTray/swingUI/SwingMenu.java index 7cee2ecc..be281be4 100644 --- a/src/dorkbox/systemTray/swingUI/SwingMenu.java +++ b/src/dorkbox/systemTray/swingUI/SwingMenu.java @@ -16,6 +16,7 @@ package dorkbox.systemTray.swingUI; +import java.awt.event.ActionListener; import java.io.File; import java.util.concurrent.atomic.AtomicReference; @@ -23,7 +24,7 @@ import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.JMenuItem; -import dorkbox.systemTray.Action; +import dorkbox.systemTray.Checkbox; import dorkbox.systemTray.Entry; import dorkbox.systemTray.Menu; import dorkbox.systemTray.Status; @@ -100,36 +101,28 @@ class SwingMenu extends MenuBase implements SwingUI { } /** - * Will add a new menu entry, or update one if it already exists + * Will add a new menu entry * NOT ALWAYS CALLED ON EDT */ protected final - Entry addEntry_(final String menuText, final File imagePath, final Action callback) { + Entry addEntry_(final String menuText, final File imagePath, final ActionListener callback) { if (menuText == null) { throw new NullPointerException("Menu text cannot be null"); } final AtomicReference value = new AtomicReference(); + // must always be called on the EDT dispatchAndWait(new Runnable() { @Override public void run() { synchronized (menuEntries) { - Entry entry = get(menuText); - - if (entry == null) { - // must always be called on the EDT - entry = new SwingEntryItem(SwingMenu.this, callback); - entry.setText(menuText); - entry.setImage(imagePath); - - menuEntries.add(entry); - } else if (entry instanceof SwingEntryItem) { - entry.setText(menuText); - entry.setImage(imagePath); - } + Entry entry = new SwingEntryItem(SwingMenu.this, callback); + entry.setText(menuText); + entry.setImage(imagePath); + menuEntries.add(entry); value.set(entry); } } @@ -139,7 +132,38 @@ class SwingMenu extends MenuBase implements SwingUI { } /** - * Will add a new sub-menu entry, or update one if it already exists + * Will add a new checkbox menu entry + * NOT ALWAYS CALLED ON DISPATCH + */ + @Override + protected + Checkbox addCheckbox_(final String menuText, final ActionListener callback) { + if (menuText == null) { + throw new NullPointerException("Menu text cannot be null"); + } + + final AtomicReference value = new AtomicReference(); + + // must always be called on the EDT + dispatchAndWait(new Runnable() { + @Override + public + void run() { + synchronized (menuEntries) { + Entry entry = new SwingEntryCheckbox(SwingMenu.this, callback); + entry.setText(menuText); + + menuEntries.add(entry); + value.set((Checkbox) entry); + } + } + }); + + return value.get(); + } + + /** + * Will add a new sub-menu entry * NOT ALWAYS CALLED ON EDT */ protected final @@ -150,28 +174,20 @@ class SwingMenu extends MenuBase implements SwingUI { final AtomicReference value = new AtomicReference(); + // must always be called on the EDT dispatchAndWait(new Runnable() { @Override public void run() { synchronized (menuEntries) { - Entry entry = get(menuText); + Entry entry = new SwingMenu(getSystemTray(), SwingMenu.this, new AdjustedJMenu()); + _native.add(((SwingMenu) entry)._native); // have to add it separately - if (entry == null) { - // must always be called on the EDT - entry = new SwingMenu(getSystemTray(), SwingMenu.this, new AdjustedJMenu()); - _native.add(((SwingMenu) entry)._native); // have to add it separately - - entry.setText(menuText); - entry.setImage(imagePath); - value.set((Menu) entry); - - } else if (entry instanceof SwingMenu) { - entry.setText(menuText); - entry.setImage(imagePath); - } + entry.setText(menuText); + entry.setImage(imagePath); menuEntries.add(entry); + value.set((Menu) entry); } } }); diff --git a/src/dorkbox/systemTray/util/ImageUtils.java b/src/dorkbox/systemTray/util/ImageUtils.java index 946f6ab4..988250bf 100644 --- a/src/dorkbox/systemTray/util/ImageUtils.java +++ b/src/dorkbox/systemTray/util/ImageUtils.java @@ -17,7 +17,6 @@ package dorkbox.systemTray.util; import static dorkbox.systemTray.jna.Windows.Gdi32.GetDeviceCaps; import static dorkbox.systemTray.jna.Windows.Gdi32.LOGPIXELSX; -import static dorkbox.systemTray.jna.Windows.Gdi32.LOGPIXELSY; import java.awt.Color; import java.awt.Dimension; @@ -134,8 +133,6 @@ class ImageUtils { } else if (windowsVersion.startsWith("6.3")) { // Windows 8.1 // Windows Server 2012 6.3.9200 - - scalingFactor = 4; } else if (windowsVersion.startsWith("6.4")) { @@ -154,11 +151,9 @@ class ImageUtils { Pointer screen = User32.GetDC(null); int dpiX = GetDeviceCaps (screen, LOGPIXELSX); - int dpiY = GetDeviceCaps (screen, LOGPIXELSY); User32.ReleaseDC(null, screen); - System.err.println("DPI : " + dpiX + "," + dpiY); - + System.err.println("DPI : " + dpiX); if (SystemTray.DEBUG) { diff --git a/src/dorkbox/systemTray/util/MenuBase.java b/src/dorkbox/systemTray/util/MenuBase.java index f27f6082..c9e50462 100644 --- a/src/dorkbox/systemTray/util/MenuBase.java +++ b/src/dorkbox/systemTray/util/MenuBase.java @@ -16,6 +16,7 @@ package dorkbox.systemTray.util; +import java.awt.event.ActionListener; import java.io.File; import java.io.InputStream; import java.net.URL; @@ -23,7 +24,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.concurrent.atomic.AtomicInteger; -import dorkbox.systemTray.Action; +import dorkbox.systemTray.Checkbox; import dorkbox.systemTray.Entry; import dorkbox.systemTray.Menu; import dorkbox.systemTray.Separator; @@ -62,14 +63,21 @@ class MenuBase implements Menu { /** - * Will add a new menu entry, or update one if it already exists + * Will add a new menu entry * NOT ALWAYS CALLED ON DISPATCH */ protected abstract - Entry addEntry_(final String menuText, final File imagePath, final Action callback); + Entry addEntry_(final String menuText, final File imagePath, final ActionListener callback); /** - * Will add a new sub-menu entry, or update one if it already exists + * Will add a new checkbox menu entry + * NOT ALWAYS CALLED ON DISPATCH + */ + protected abstract + Checkbox addCheckbox_(final String menuText, final ActionListener callback); + + /** + * Will add a new sub-menu entry * NOT ALWAYS CALLED ON DISPATCH */ protected abstract @@ -225,13 +233,13 @@ class MenuBase implements Menu { @Override public final - Entry addEntry(String menuText, Action callback) { + Entry addEntry(String menuText, ActionListener callback) { return addEntry(menuText, (String) null, callback); } @Override public final - Entry addEntry(String menuText, String imagePath, Action callback) { + Entry addEntry(String menuText, String imagePath, ActionListener callback) { if (imagePath == null) { return addEntry_(menuText, null, callback); } @@ -242,7 +250,7 @@ class MenuBase implements Menu { @Override public final - Entry addEntry(String menuText, URL imageUrl, Action callback) { + Entry addEntry(String menuText, URL imageUrl, ActionListener callback) { if (imageUrl == null) { return addEntry_(menuText, null, callback); } @@ -253,7 +261,7 @@ class MenuBase implements Menu { @Override public final - Entry addEntry(String menuText, String cacheName, InputStream imageStream, Action callback) { + Entry addEntry(String menuText, String cacheName, InputStream imageStream, ActionListener callback) { if (imageStream == null) { return addEntry_(menuText, null, callback); } @@ -264,7 +272,7 @@ class MenuBase implements Menu { @Override public final - Entry addEntry(String menuText, InputStream imageStream, Action callback) { + Entry addEntry(String menuText, InputStream imageStream, ActionListener callback) { if (imageStream == null) { return addEntry_(menuText, null, callback); } @@ -274,6 +282,11 @@ class MenuBase implements Menu { } + @Override + public + Checkbox addCheckbox(final String menuText, final ActionListener callback) { + return addCheckbox_(menuText, callback); + } @@ -386,36 +399,12 @@ class MenuBase implements Menu { } - - - @Override public final - void setCallback(final Action callback) { + void setCallback(final ActionListener callback) { } - - - - - - - - - - - - - - - - - - - - - /** * This removes a menu entry from the dropdown menu. * @@ -526,27 +515,6 @@ class MenuBase implements Menu { }); } - -// @Override -// public final -// void remove() { -// dispatchAndWait(new Runnable() { -// @Override -// public -// void run() { -// _native.setVisible(false); -// if (_native instanceof TrayPopup) { -// ((TrayPopup) _native).close(); -// } -// -// MenuBase parent = (MenuBase) getParent(); -// if (parent != null) { -// parent._native.remove(_native); -// } -// } -// }); -// } - @Override public final void removeAll() { diff --git a/src/dorkbox/systemTray/util/checked_32.png b/src/dorkbox/systemTray/util/checked_32.png new file mode 100644 index 0000000000000000000000000000000000000000..afa259baa640dc3a713cad9ccde673c846e15ad0 GIT binary patch literal 298 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzwj^(N7l!{JxM1({$v_d#0*}aI zppNSx%;=;sy8^u_bb6IK%woPE{-7)hu=T_%_bo@vkd|9hrq8{_oTU)f(pHvV>CoYSz7 zwT9cqIo8Da$cJ#*tq-gj>sZ~S4u^4kkT&>ttbjqM;mV@VEEX)2*$w_P+~0oF(m`%! z=z);W=cYHkl%0{mcgM?MeM4mUaXsI!Q4LcPmxqSEjqUKWp6-+q_93mKS9Fi0()Om~ n>V?-TZr6XB{IFWzjLkuy};n<>gTe~DWM4f4^eZO literal 0 HcmV?d00001 diff --git a/test/dorkbox/TestTray.java b/test/dorkbox/TestTray.java index fba4200e..188d3e21 100644 --- a/test/dorkbox/TestTray.java +++ b/test/dorkbox/TestTray.java @@ -16,9 +16,11 @@ package dorkbox; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.net.URL; -import dorkbox.systemTray.Action; +import dorkbox.systemTray.Checkbox; import dorkbox.systemTray.Entry; import dorkbox.systemTray.Menu; import dorkbox.systemTray.SystemTray; @@ -49,8 +51,8 @@ class TestTray { } private SystemTray systemTray; - private Action callbackGreen; - private Action callbackGray; + private ActionListener callbackGreen; + private ActionListener callbackGray; public TestTray() { @@ -59,13 +61,23 @@ class TestTray { throw new RuntimeException("Unable to load SystemTray!"); } +// final JPopupMenu popupMenu = new JPopupMenu(); +// JMenu submenu2 = new JMenu("SubMenu1"); +// submenu2.add("asdf"); +// submenu2.add("asdf"); +// +// // Add submenu to popup menu +// popupMenu.add(submenu2); + + systemTray.setImage(LT_GRAY_TRAIN); systemTray.setStatus("No Mail"); - callbackGreen = new Action() { + callbackGreen = new ActionListener() { @Override public - void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) { + void actionPerformed(final ActionEvent e) { + final Entry entry = (Entry) e.getSource(); systemTray.setStatus("Some Mail!"); systemTray.setImage(GREEN_TRAIN); @@ -76,10 +88,11 @@ class TestTray { } }; - callbackGray = new Action() { + callbackGray = new ActionListener() { @Override public - void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) { + void actionPerformed(final ActionEvent e) { + final Entry entry = (Entry) e.getSource(); systemTray.setStatus(null); systemTray.setImage(BLACK_TRAIN); @@ -94,14 +107,18 @@ class TestTray { // case does not matter menuEntry.setShortcut('G'); + final Checkbox menuCheckbox = this.systemTray.addCheckbox("Euro € Mail", callbackGreen); + // case does not matter +// menuCheckbox.setShortcut('€'); + this.systemTray.addSeparator(); final Menu submenu = this.systemTray.addMenu("Options", BLUE_CAMPING); submenu.setShortcut('t'); - submenu.addEntry("Disable menu", BLACK_BUS, new Action() { + submenu.addEntry("Disable menu", BLACK_BUS, new ActionListener() { @Override public - void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) { + void actionPerformed(final ActionEvent e) { submenu.setEnabled(false); } }); @@ -116,26 +133,26 @@ class TestTray { // systemTray.addWidget(progressBar); // } // }); - submenu.addEntry("Hide tray", LT_GRAY_BUS, new Action() { + submenu.addEntry("Hide tray", LT_GRAY_BUS, new ActionListener() { @Override public - void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) { + void actionPerformed(final ActionEvent e) { systemTray.setEnabled(false); } }); - submenu.addEntry("Remove menu", BLACK_FIRE, new Action() { + submenu.addEntry("Remove menu", BLACK_FIRE, new ActionListener() { @Override public - void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) { + void actionPerformed(final ActionEvent e) { submenu.remove(); } }); - systemTray.addEntry("Quit", new Action() { + systemTray.addEntry("Quit", new ActionListener() { @Override public - void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) { + void actionPerformed(final ActionEvent e) { systemTray.shutdown(); //System.exit(0); not necessary if all non-daemon threads have stopped. } diff --git a/test/dorkbox/TestTrayJavaFX.java b/test/dorkbox/TestTrayJavaFX.java index 1f89bd2f..3f2d4061 100644 --- a/test/dorkbox/TestTrayJavaFX.java +++ b/test/dorkbox/TestTrayJavaFX.java @@ -16,9 +16,9 @@ package dorkbox; +import java.awt.event.ActionListener; import java.net.URL; -import dorkbox.systemTray.Action; import dorkbox.systemTray.Entry; import dorkbox.systemTray.Menu; import dorkbox.systemTray.SystemTray; @@ -59,8 +59,8 @@ class TestTrayJavaFX extends Application { } private SystemTray systemTray; - private Action callbackGreen; - private Action callbackGray; + private ActionListener callbackGreen; + private ActionListener callbackGray; public TestTrayJavaFX() { @@ -95,10 +95,11 @@ class TestTrayJavaFX extends Application { systemTray.setImage(LT_GRAY_TRAIN); systemTray.setStatus("No Mail"); - callbackGreen = new Action() { + callbackGreen = new ActionListener() { @Override public - void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) { + void actionPerformed(final java.awt.event.ActionEvent e) { + final Entry entry = (Entry) e.getSource(); systemTray.setStatus("Some Mail!"); systemTray.setImage(GREEN_TRAIN); @@ -109,10 +110,11 @@ class TestTrayJavaFX extends Application { } }; - callbackGray = new Action() { + callbackGray = new ActionListener() { @Override public - void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) { + void actionPerformed(final java.awt.event.ActionEvent e) { + final Entry entry = (Entry) e.getSource(); systemTray.setStatus(null); systemTray.setImage(BLACK_TRAIN); @@ -127,14 +129,18 @@ class TestTrayJavaFX extends Application { // case does not matter menuEntry.setShortcut('G'); + menuEntry = this.systemTray.addEntry("Euro € Mail", GREEN_MAIL, callbackGreen); + // case does not matter + menuEntry.setShortcut('€'); + this.systemTray.addSeparator(); final Menu submenu = this.systemTray.addMenu("Options", BLUE_CAMPING); submenu.setShortcut('t'); - submenu.addEntry("Disable menu", BLACK_BUS, new Action() { + submenu.addEntry("Disable menu", BLACK_BUS, new ActionListener() { @Override public - void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) { + void actionPerformed(final java.awt.event.ActionEvent e) { submenu.setEnabled(false); } }); @@ -149,25 +155,25 @@ class TestTrayJavaFX extends Application { // systemTray.addWidget(progressBar); // } // }); - submenu.addEntry("Hide tray", LT_GRAY_BUS, new Action() { + submenu.addEntry("Hide tray", LT_GRAY_BUS, new ActionListener() { @Override public - void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) { + void actionPerformed(final java.awt.event.ActionEvent e) { systemTray.setEnabled(false); } }); - submenu.addEntry("Remove menu", BLACK_FIRE, new Action() { + submenu.addEntry("Remove menu", BLACK_FIRE, new ActionListener() { @Override public - void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) { + void actionPerformed(final java.awt.event.ActionEvent e) { submenu.remove(); } }); - systemTray.addEntry("Quit", new Action() { + systemTray.addEntry("Quit", new ActionListener() { @Override public - void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) { + void actionPerformed(final java.awt.event.ActionEvent e) { systemTray.shutdown(); Platform.exit(); // necessary to close javaFx //System.exit(0); not necessary if all non-daemon threads have stopped. diff --git a/test/dorkbox/TestTraySwt.java b/test/dorkbox/TestTraySwt.java index 1cf0a16f..c5d0ed16 100644 --- a/test/dorkbox/TestTraySwt.java +++ b/test/dorkbox/TestTraySwt.java @@ -16,6 +16,7 @@ package dorkbox; +import java.awt.event.ActionListener; import java.net.URL; import org.eclipse.swt.SWT; @@ -23,7 +24,6 @@ import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; -import dorkbox.systemTray.Action; import dorkbox.systemTray.Entry; import dorkbox.systemTray.Menu; import dorkbox.systemTray.SystemTray; @@ -58,8 +58,8 @@ class TestTraySwt { } private SystemTray systemTray; - private Action callbackGreen; - private Action callbackGray; + private ActionListener callbackGreen; + private ActionListener callbackGray; public TestTraySwt() { @@ -79,10 +79,11 @@ class TestTraySwt { systemTray.setImage(LT_GRAY_TRAIN); systemTray.setStatus("No Mail"); - callbackGreen = new Action() { + callbackGreen = new ActionListener() { @Override public - void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) { + void actionPerformed(final java.awt.event.ActionEvent e) { + final Entry entry = (Entry) e.getSource(); systemTray.setStatus("Some Mail!"); systemTray.setImage(GREEN_TRAIN); @@ -93,10 +94,11 @@ class TestTraySwt { } }; - callbackGray = new Action() { + callbackGray = new ActionListener() { @Override public - void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) { + void actionPerformed(final java.awt.event.ActionEvent e) { + final Entry entry = (Entry) e.getSource(); systemTray.setStatus(null); systemTray.setImage(BLACK_TRAIN); @@ -111,14 +113,18 @@ class TestTraySwt { // case does not matter menuEntry.setShortcut('G'); + menuEntry = this.systemTray.addEntry("Euro € Mail", GREEN_MAIL, callbackGreen); + // case does not matter + menuEntry.setShortcut('€'); + this.systemTray.addSeparator(); final Menu submenu = this.systemTray.addMenu("Options", BLUE_CAMPING); submenu.setShortcut('t'); - submenu.addEntry("Disable menu", BLACK_BUS, new Action() { + submenu.addEntry("Disable menu", BLACK_BUS, new ActionListener() { @Override public - void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) { + void actionPerformed(final java.awt.event.ActionEvent e) { submenu.setEnabled(false); } }); @@ -133,25 +139,25 @@ class TestTraySwt { // systemTray.addWidget(progressBar); // } // }); - submenu.addEntry("Hide tray", LT_GRAY_BUS, new Action() { + submenu.addEntry("Hide tray", LT_GRAY_BUS, new ActionListener() { @Override public - void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) { + void actionPerformed(final java.awt.event.ActionEvent e) { systemTray.setEnabled(false); } }); - submenu.addEntry("Remove menu", BLACK_FIRE, new Action() { + submenu.addEntry("Remove menu", BLACK_FIRE, new ActionListener() { @Override public - void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) { + void actionPerformed(final java.awt.event.ActionEvent e) { submenu.remove(); } }); - systemTray.addEntry("Quit", new Action() { + systemTray.addEntry("Quit", new ActionListener() { @Override public - void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) { + void actionPerformed(final java.awt.event.ActionEvent e) { systemTray.shutdown(); display.asyncExec(new Runnable() {