diff --git a/src/dorkbox/systemTray/linux/jna/AppIndicator.java b/src/dorkbox/systemTray/linux/jna/AppIndicator.java index 72d21fe..e2ffd35 100644 --- a/src/dorkbox/systemTray/linux/jna/AppIndicator.java +++ b/src/dorkbox/systemTray/linux/jna/AppIndicator.java @@ -34,8 +34,6 @@ class AppIndicator { public static boolean isVersion3 = false; private static boolean isLoaded = false; - private static final boolean VERBOSE_DEBUG = false; - /** * Loader for AppIndicator, because it is absolutely mindboggling how those whom maintain the standard, can't agree to what that * standard library naming convention or features/API set is. We just try until we find one that work, and are able to map the @@ -69,11 +67,8 @@ class AppIndicator { isLoaded = true; } } catch (Throwable e) { - if (VERBOSE_DEBUG) { - logger.debug("Error loading library: {}", "appindicator1", e); - } - else if (SystemTray.DEBUG) { - logger.debug("Error loading GTK2 explicit appindicator1"); + if (SystemTray.DEBUG) { + logger.debug("Error loading GTK2 explicit appindicator1. {}", e.getMessage()); } } } @@ -104,7 +99,7 @@ class AppIndicator { isLoaded = true; } catch (Throwable e) { if (SystemTray.DEBUG) { - logger.debug("Error loading library: '{}'", nameToCheck1, e); + logger.debug("Error loading library: '{}'. \n{}", nameToCheck1, e.getMessage()); } } } @@ -141,7 +136,7 @@ class AppIndicator { break; } catch (Throwable e) { if (SystemTray.DEBUG) { - logger.debug("Error loading library: '{}'", "appindicator" + i, e); + logger.debug("Error loading library: '{}'. \n{}", "appindicator" + i, e.getMessage()); } } } @@ -168,7 +163,7 @@ class AppIndicator { break; } catch (Throwable e) { if (SystemTray.DEBUG) { - logger.debug("Error loading library: '{}'", "appindicator" + i, e); + logger.debug("Error loading library: '{}'. \n{}", "appindicator" + i, e.getMessage()); } } } @@ -196,7 +191,7 @@ class AppIndicator { isLoaded = true; } catch (Throwable e) { if (SystemTray.DEBUG) { - logger.debug("Error loading library: '{}'", nameToCheck1, e); + logger.debug("Error loading library: '{}'. \n{}", nameToCheck1, e.getMessage()); } } } @@ -208,7 +203,7 @@ class AppIndicator { isLoaded = true; } catch (Throwable e) { if (SystemTray.DEBUG) { - logger.debug("Error loading library: '{}'", nameToCheck2, e); + logger.debug("Error loading library: '{}'. \n{}", nameToCheck2, e.getMessage()); } } } @@ -233,4 +228,5 @@ class AppIndicator { public static native void app_indicator_set_status(AppIndicatorInstanceStruct self, int status); public static native void app_indicator_set_menu(AppIndicatorInstanceStruct self, Pointer menu); public static native void app_indicator_set_icon(AppIndicatorInstanceStruct self, String icon_name); + public static native void app_indicator_set_label(AppIndicatorInstanceStruct self, String label, String notused); } diff --git a/src/dorkbox/systemTray/linux/jna/Gobject.java b/src/dorkbox/systemTray/linux/jna/Gobject.java index 8af5d9b..cd993dc 100644 --- a/src/dorkbox/systemTray/linux/jna/Gobject.java +++ b/src/dorkbox/systemTray/linux/jna/Gobject.java @@ -18,6 +18,7 @@ package dorkbox.systemTray.linux.jna; import com.sun.jna.Callback; import com.sun.jna.NativeLong; import com.sun.jna.Pointer; +import com.sun.jna.ptr.PointerByReference; /** * bindings for libgobject-2.0 @@ -32,6 +33,8 @@ class Gobject { } + public static native void g_object_get(Pointer object, String objectName, PointerByReference objectVal, Pointer nullValue); + public static native void g_free(Pointer object); public static native void g_object_unref(Pointer object); diff --git a/src/dorkbox/systemTray/linux/jna/Gtk.java b/src/dorkbox/systemTray/linux/jna/Gtk.java index 74a48dd..e42be92 100644 --- a/src/dorkbox/systemTray/linux/jna/Gtk.java +++ b/src/dorkbox/systemTray/linux/jna/Gtk.java @@ -55,6 +55,7 @@ class Gtk { // there is ONLY a single thread EVER setting this value!! private static volatile boolean isDispatch = false; + public static boolean isKDE = false; // objdump -T /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0 | grep gtk // objdump -T /usr/lib/x86_64-linux-gnu/libgtk-3.so.0 | grep gtk diff --git a/src/dorkbox/systemTray/swing/AppIndicatorTray.java b/src/dorkbox/systemTray/swing/AppIndicatorTray.java new file mode 100644 index 0000000..dc9ddd5 --- /dev/null +++ b/src/dorkbox/systemTray/swing/AppIndicatorTray.java @@ -0,0 +1,291 @@ +/* + * 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 static dorkbox.systemTray.SystemTray.TIMEOUT; + +import java.awt.Dimension; +import java.awt.MouseInfo; +import java.awt.Point; +import java.awt.Rectangle; +import java.io.File; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.swing.JPopupMenu; + +import com.sun.jna.NativeLong; +import com.sun.jna.Pointer; +import com.sun.jna.ptr.PointerByReference; + +import dorkbox.systemTray.SystemTray; +import dorkbox.systemTray.linux.jna.AppIndicator; +import dorkbox.systemTray.linux.jna.AppIndicatorInstanceStruct; +import dorkbox.systemTray.linux.jna.GEventCallback; +import dorkbox.systemTray.linux.jna.GdkEventButton; +import dorkbox.systemTray.linux.jna.Gobject; +import dorkbox.systemTray.linux.jna.Gtk; +import dorkbox.systemTray.util.ImageUtils; +import dorkbox.util.ScreenUtil; +import dorkbox.util.SwingUtil; + +/** + * Class for handling all system tray interactions. + * specialization for using app indicators in ubuntu unity + * + * Derived from + * Lantern: https://github.com/getlantern/lantern/ Apache 2.0 License Copyright 2010 Brave New Software Project, Inc. + * + * AppIndicators DO NOT support anything other than plain gtk-menus, because of how they use dbus so no tooltips AND no custom widgets + * + * + * + * As a result of this decision by Canonical, we have to resort to hacks to get it to do what we want. BY NO MEANS IS THIS PERFECT. + * + * + * We still cannot have tooltips, but we *CAN* have custom widgets in the menu (because it's our swing menu now...) + * + * + * It would be too much work to re-implement AppIndicators, or even to use LD_PRELOAD + restart service to do what we want. + * + * As a result, we have some wicked little hacks which are rather effective (but have a small side-effect of very briefly + * showing a blank menu) + * + * // What are AppIndicators? + * http://unity.ubuntu.com/projects/appindicators/ + * + * + * // Entry-point into appindicators + * http://bazaar.launchpad.net/~unity-team/unity/trunk/view/head:/services/panel-main.c + * + * + * // The idiocy of appindicators + * https://bugs.launchpad.net/screenlets/+bug/522152 + * + * // Code of how the dbus menus work + * http://bazaar.launchpad.net/~dbusmenu-team/libdbusmenu/trunk.16.10/view/head:/libdbusmenu-gtk/client.c + * https://developer.ubuntu.com/api/devel/ubuntu-12.04/c/dbusmenugtk/index.html + * + * // more info about trying to put widgets into GTK menus + * http://askubuntu.com/questions/16431/putting-an-arbitrary-gtk-widget-into-an-appindicator-indicator + * + * // possible idea on how to get GTK widgets into GTK menus + * https://launchpad.net/ido + * http://bazaar.launchpad.net/~canonical-dx-team/ido/trunk/view/head:/src/idoentrymenuitem.c + * http://bazaar.launchpad.net/~ubuntu-desktop/ido/gtk3/files + */ +public +class AppIndicatorTray extends SwingGenericTray { + private AppIndicatorInstanceStruct appIndicator; + private boolean isActive = false; + + // This is required if we have JavaFX or SWT shutdown hooks (to prevent us from shutting down twice...) + private AtomicBoolean shuttingDown = new AtomicBoolean(); + + private volatile NativeLong nativeLong; + private volatile GEventCallback gtkCallback; + private Pointer dummyMenu; + private final Runnable popupRunnable; + + public + AppIndicatorTray(final SystemTray systemTray) { + super(systemTray,null, new SwingSystemTrayMenuPopup()); + + if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TYPE_GTK_STATUSICON) { + // if we force GTK type system tray, don't attempt to load AppIndicator libs + throw new IllegalArgumentException("Unable to start AppIndicator if 'SystemTray.FORCE_TRAY_TYPE' is set to GtkStatusIcon"); + } + + JPopupMenu popupMenu = (JPopupMenu) _native; + popupMenu.pack(); + popupMenu.setFocusable(true); + + popupRunnable = new Runnable() { + @Override + public + void run() { + Dimension size = _native.getPreferredSize(); + + Point point = MouseInfo.getPointerInfo() + .getLocation(); + Rectangle bounds = ScreenUtil.getScreenBoundsAt(point); + + int x = point.x; + int y = point.y; + + if (y < bounds.y) { + y = bounds.y; + } + else if (y + size.height > bounds.y + bounds.height) { + // our menu cannot have the top-edge snap to the mouse + // so we make the bottom-edge snap to the mouse + y -= size.height; // snap to edge of mouse + } + + if (x < bounds.x) { + x = bounds.x; + + x -= 32; // display over the stupid appindicator menu (which has to show, this is a major hack!) + } + else if (x + size.width > bounds.x + bounds.width) { + // our menu cannot have the left-edge snap to the mouse + // so we make the right-edge snap to the mouse + x -= size.width; // snap to edge of mouse + + x += 32; // display over the stupid appindicator menu (which has to show, this is a major hack!) + } + + SwingSystemTrayMenuPopup popupMenu = (SwingSystemTrayMenuPopup) _native; + popupMenu.doShow(x, y); + + // Such ugly hacks to get AppIndicator support properly working. This is so horrible I am ashamed. + Gtk.dispatch(new Runnable() { + @Override + public + void run() { + createAppIndicatorMenu(); + hookMenuOpen(); + } + }); + } + }; + + // appindicators DO NOT support anything other than PLAIN gtk-menus + // they ALSO do not support tooltips, so we cater to the lowest common denominator + // trayIcon.setToolTip(SwingSystemTray.this.appName); + + Gtk.startGui(); + + Gtk.dispatch(new Runnable() { + @Override + public + void run() { + // we initialize with a blank image + File image = ImageUtils.getTransparentImage(ImageUtils.ENTRY_SIZE); + String id = System.nanoTime() + "DBST"; + appIndicator = AppIndicator.app_indicator_new(id, image.getAbsolutePath(), AppIndicator.CATEGORY_APPLICATION_STATUS); + + createAppIndicatorMenu(); + } + }); + + Gtk.waitForStartup(); + } + + private + void hookMenuOpen() { + // now we have to setup a way for us to catch the "activation" click on this menu. Must be after the menu is set + PointerByReference menuServer = new PointerByReference(); + PointerByReference rootMenuItem = new PointerByReference(); + + Gobject.g_object_get(appIndicator.getPointer(), "dbus-menu-server", menuServer, null); + Gobject.g_object_get(menuServer.getValue(), "root-node", rootMenuItem, null); + + gtkCallback = new GEventCallback() { + @Override + public + void callback(Pointer notUsed, final GdkEventButton event) { + Gtk.gtk_widget_destroy(dummyMenu); + SwingUtil.invokeLater(popupRunnable); + } + }; + + nativeLong = Gobject.g_signal_connect_object(rootMenuItem.getValue(), "about-to-show", gtkCallback, null, 0); + } + + private void createAppIndicatorMenu() { + dummyMenu = Gtk.gtk_menu_new(); + Pointer item = Gtk.gtk_image_menu_item_new_with_mnemonic(""); + Gtk.gtk_menu_shell_append(dummyMenu, item); + Gtk.gtk_widget_show_all(item); + + AppIndicator.app_indicator_set_menu(appIndicator, dummyMenu); + } + + public + void shutdown() { + if (!shuttingDown.getAndSet(true)) { + final CountDownLatch countDownLatch = new CountDownLatch(1); + + Gtk.dispatch(new Runnable() { + @Override + public + void run() { + try { + // STATUS_PASSIVE hides the indicator + AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_PASSIVE); + Pointer p = appIndicator.getPointer(); + Gobject.g_object_unref(p); + + appIndicator = null; + } finally { + countDownLatch.countDown(); + } + } + }); + + // this is slightly different than how swing does it. We have a timeout here so that we can make sure that updates on the GUI + // thread occur in REASONABLE time-frames, and alert the user if not. + try { + if (!countDownLatch.await(TIMEOUT, TimeUnit.SECONDS)) { + SystemTray.logger.error("Event dispatch queue took longer than " + TIMEOUT + " seconds to shutdown. Please adjust " + + "`SystemTray.TIMEOUT` to a value which better suites your environment."); + } + } catch (InterruptedException e) { + SystemTray.logger.error("Error waiting for shutdown dispatch to complete.", new Exception()); + } + + Gtk.shutdownGui(); + + // uses EDT + super.remove(); + } + } + + public + void setImage_(final File imageFile) { + dispatch(new Runnable() { + @Override + public + void run() { + AppIndicator.app_indicator_set_icon(appIndicator, imageFile.getAbsolutePath()); + + if (!isActive) { + isActive = true; + + AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_ACTIVE); + + // kindof lame, but necessary for KDE + if (Gtk.isKDE) { + AppIndicator.app_indicator_set_label(appIndicator, "SystemTray", null); + } + + // now we have to setup a way for us to catch the "activation" click on this menu. Must be after the menu is set + hookMenuOpen(); + } + } + }); + + dispatch(new Runnable() { + @Override + public + void run() { + ((SwingSystemTrayMenuPopup) _native).setTitleBarImage(imageFile); + } + }); + } +} diff --git a/src/dorkbox/systemTray/swing/GtkStatusIconTray.java b/src/dorkbox/systemTray/swing/GtkStatusIconTray.java index 4aeface..f29793b 100644 --- a/src/dorkbox/systemTray/swing/GtkStatusIconTray.java +++ b/src/dorkbox/systemTray/swing/GtkStatusIconTray.java @@ -33,13 +33,11 @@ import javax.swing.JPopupMenu; import com.sun.jna.NativeLong; import com.sun.jna.Pointer; -import dorkbox.systemTray.MenuEntry; import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.linux.jna.GEventCallback; import dorkbox.systemTray.linux.jna.GdkEventButton; import dorkbox.systemTray.linux.jna.Gobject; import dorkbox.systemTray.linux.jna.Gtk; -import dorkbox.systemTray.util.ImageUtils; import dorkbox.util.ScreenUtil; /** @@ -49,7 +47,7 @@ import dorkbox.util.ScreenUtil; * swing menu popup INSTEAD of GTK menu popups. The "golden standard" is our swing menu popup, since we have 100% control over it. */ public -class GtkStatusIconTray extends SwingMenu { +class GtkStatusIconTray extends SwingGenericTray { private volatile Pointer trayIcon; // http://code.metager.de/source/xref/gnome/Platform/gtk%2B/gtk/deprecated/gtkstatusicon.c @@ -72,7 +70,6 @@ class GtkStatusIconTray extends SwingMenu { throw new IllegalArgumentException("Unable to start GtkStatusIcon if 'SystemTray.FORCE_TRAY_TYPE' is set to AppIndicator"); } - JPopupMenu popupMenu = (JPopupMenu) _native; popupMenu.pack(); popupMenu.setFocusable(true); @@ -117,7 +114,6 @@ class GtkStatusIconTray extends SwingMenu { // they ALSO do not support tooltips, so we cater to the lowest common denominator // trayIcon.setToolTip(SwingSystemTray.this.appName); - ImageUtils.determineIconSize(); Gtk.startGui(); Gtk.dispatch(new Runnable() { @@ -131,6 +127,7 @@ class GtkStatusIconTray extends SwingMenu { @Override public void callback(Pointer notUsed, final GdkEventButton event) { + // show the swing menu on the EDT // BUTTON_PRESS only (any mouse click) if (event.type == 4) { // show the swing menu on the EDT @@ -214,6 +211,7 @@ class GtkStatusIconTray extends SwingMenu { Gtk.shutdownGui(); + // uses EDT super.remove(); } } @@ -232,53 +230,12 @@ class GtkStatusIconTray extends SwingMenu { } } }); - } - public - String getStatus() { - synchronized (menuEntries) { - MenuEntry menuEntry = menuEntries.get(0); - if (menuEntry instanceof SwingEntryStatus) { - return menuEntry.getText(); - } - } - - return null; - } - - @SuppressWarnings("Duplicates") - public - void setStatus(final String statusText) { dispatch(new Runnable() { @Override public void run() { - synchronized (menuEntries) { - // status is ALWAYS at 0 index... - SwingEntry menuEntry = null; - if (!menuEntries.isEmpty()) { - menuEntry = (SwingEntry) menuEntries.get(0); - } - - if (menuEntry instanceof SwingEntryStatus) { - // set the text or delete... - - if (statusText == null) { - // delete - remove(menuEntry); - } - else { - // set text - menuEntry.setText(statusText); - } - - } else { - // create a new one - menuEntry = new SwingEntryStatus(GtkStatusIconTray.this, statusText); - // status is ALWAYS at 0 index... - menuEntries.add(0, menuEntry); - } - } + ((SwingSystemTrayMenuPopup) _native).setTitleBarImage(iconFile); } }); } diff --git a/src/dorkbox/systemTray/swing/SwingGenericTray.java b/src/dorkbox/systemTray/swing/SwingGenericTray.java new file mode 100644 index 0000000..c3d6379 --- /dev/null +++ b/src/dorkbox/systemTray/swing/SwingGenericTray.java @@ -0,0 +1,77 @@ +package dorkbox.systemTray.swing; + +import javax.swing.JComponent; + +import dorkbox.systemTray.Menu; +import dorkbox.systemTray.MenuEntry; +import dorkbox.systemTray.SystemTray; +import dorkbox.systemTray.util.ImageUtils; + +/** + * + */ +public abstract +class SwingGenericTray extends SwingMenu { + /** + * Called in the EDT + * + * @param systemTray + * the system tray (which is the object that sits in the system tray) + * @param parent + * @param _native + */ + SwingGenericTray(final SystemTray systemTray, final Menu parent, final JComponent _native) { + super(systemTray, parent, _native); + + ImageUtils.determineIconSize(); + } + + public + String getStatus() { + synchronized (menuEntries) { + MenuEntry menuEntry = menuEntries.get(0); + if (menuEntry instanceof SwingEntryStatus) { + return menuEntry.getText(); + } + } + + return null; + } + + public + void setStatus(final String statusText) { + final SwingMenu _this = this; + dispatchAndWait(new Runnable() { + @Override + public + void run() { + synchronized (menuEntries) { + // status is ALWAYS at 0 index... + SwingEntry menuEntry = null; + if (!menuEntries.isEmpty()) { + menuEntry = (SwingEntry) menuEntries.get(0); + } + + if (menuEntry instanceof SwingEntryStatus) { + // set the text or delete... + + if (statusText == null) { + // delete + remove(menuEntry); + } + else { + // set text + menuEntry.setText(statusText); + } + + } else { + // create a new one + menuEntry = new SwingEntryStatus(_this, statusText); + // status is ALWAYS at 0 index... + menuEntries.add(0, menuEntry); + } + } + } + }); + } +} diff --git a/src/dorkbox/systemTray/swing/SwingSystemTray.java b/src/dorkbox/systemTray/swing/SwingSystemTray.java index 12360aa..0ea57d7 100644 --- a/src/dorkbox/systemTray/swing/SwingSystemTray.java +++ b/src/dorkbox/systemTray/swing/SwingSystemTray.java @@ -30,7 +30,6 @@ import javax.swing.ImageIcon; import javax.swing.JPopupMenu; import dorkbox.systemTray.MenuEntry; -import dorkbox.systemTray.util.ImageUtils; import dorkbox.util.ScreenUtil; /** @@ -43,7 +42,7 @@ import dorkbox.util.ScreenUtil; */ @SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "WeakerAccess"}) public -class SwingSystemTray extends SwingMenu { +class SwingSystemTray extends SwingGenericTray { volatile SystemTray tray; volatile TrayIcon trayIcon; @@ -52,8 +51,6 @@ class SwingSystemTray extends SwingMenu { SwingSystemTray(final dorkbox.systemTray.SystemTray systemTray) { super(systemTray, null, new SwingSystemTrayMenuPopup()); - ImageUtils.determineIconSize(); - SwingSystemTray.this.tray = SystemTray.getSystemTray(); } @@ -77,55 +74,6 @@ class SwingSystemTray extends SwingMenu { }); } - public - String getStatus() { - synchronized (menuEntries) { - MenuEntry menuEntry = menuEntries.get(0); - if (menuEntry instanceof SwingEntryStatus) { - return menuEntry.getText(); - } - } - - return null; - } - - @SuppressWarnings("Duplicates") - public - void setStatus(final String statusText) { - dispatch(new Runnable() { - @Override - public - void run() { - synchronized (menuEntries) { - // status is ALWAYS at 0 index... - SwingEntry menuEntry = null; - if (!menuEntries.isEmpty()) { - menuEntry = (SwingEntry) menuEntries.get(0); - } - - if (menuEntry instanceof SwingEntryStatus) { - // set the text or delete... - - if (statusText == null) { - // delete - remove(menuEntry); - } - else { - // set text - menuEntry.setText(statusText); - } - - } else { - // create a new one - menuEntry = new SwingEntryStatus(SwingSystemTray.this, statusText); - // status is ALWAYS at 0 index... - menuEntries.add(0, menuEntry); - } - } - } - }); - } - public void setImage_(final File iconFile) { dispatch(new Runnable() { @@ -185,14 +133,14 @@ class SwingSystemTray extends SwingMenu { try { tray.add(trayIcon); - ((SwingSystemTrayMenuPopup) _native).setIcon(iconFile); } catch (AWTException e) { dorkbox.systemTray.SystemTray.logger.error("TrayIcon could not be added.", e); } } else { - ((SwingSystemTrayMenuPopup) _native).setIcon(iconFile); trayIcon.setImage(trayImage); } + + ((SwingSystemTrayMenuPopup) _native).setTitleBarImage(iconFile); } }); } diff --git a/src/dorkbox/systemTray/swing/SwingSystemTrayMenuPopup.java b/src/dorkbox/systemTray/swing/SwingSystemTrayMenuPopup.java index 9fc6fe0..7e220b8 100644 --- a/src/dorkbox/systemTray/swing/SwingSystemTrayMenuPopup.java +++ b/src/dorkbox/systemTray/swing/SwingSystemTrayMenuPopup.java @@ -39,6 +39,7 @@ import dorkbox.util.OS; * * This is our "golden standard" since we have 100% control over it. */ +public class SwingSystemTrayMenuPopup extends JPopupMenu { private static final long serialVersionUID = 1L; @@ -47,6 +48,7 @@ class SwingSystemTrayMenuPopup extends JPopupMenu { private volatile File iconFile; @SuppressWarnings("unchecked") + public SwingSystemTrayMenuPopup() { super(); setFocusable(true); @@ -113,24 +115,25 @@ class SwingSystemTrayMenuPopup extends JPopupMenu { } /** - * Sets the icon for the title-bar, so IF it shows in the task-bar, it will have the corresponding icon as the SystemTray icon + * Sets the image for the title-bar, so IF it shows in the task-bar, it will have the corresponding image as the SystemTray image */ - void setIcon(final File iconFile) { - if (this.iconFile == null || !this.iconFile.equals(iconFile)) { - this.iconFile = iconFile; + void setTitleBarImage(final File imageFile) { + if (this.iconFile == null || !this.iconFile.equals(imageFile)) { + this.iconFile = imageFile; try { - Image image = new ImageIcon(ImageIO.read(iconFile)).getImage(); + Image image = new ImageIcon(ImageIO.read(imageFile)).getImage(); image.flush(); // we set the dialog window to have the same icon as what is on the system tray hiddenDialog.setIconImage(image); } catch (IOException e) { - SystemTray.logger.error("Error setting the icon for the popup menu task tray dialog"); + SystemTray.logger.error("Error setting the title-bar image for the popup menu task tray dialog"); } } } + public void doShow(final int x, final int y) { // critical to get the keyboard listeners working for the popup menu setInvoker(hiddenDialog.getContentPane());