From f0fe9fa9a267e0ad8fc75f27a6bf1f00d826af52 Mon Sep 17 00:00:00 2001 From: nathan Date: Sat, 8 Oct 2016 17:41:21 +0200 Subject: [PATCH] Fixed issue with keyboard navigation/mnemonics on popup window. --- .../systemTray/swing/SwingSystemTray.java | 15 +-- .../SwingSystemTrayMenuWindowsPopup.java | 99 ++++++++++++++++--- 2 files changed, 93 insertions(+), 21 deletions(-) diff --git a/src/dorkbox/systemTray/swing/SwingSystemTray.java b/src/dorkbox/systemTray/swing/SwingSystemTray.java index 0edb3bd..22bc05f 100644 --- a/src/dorkbox/systemTray/swing/SwingSystemTray.java +++ b/src/dorkbox/systemTray/swing/SwingSystemTray.java @@ -147,6 +147,10 @@ class SwingSystemTray extends SwingMenu { // here we init. everything trayIcon = new TrayIcon(trayImage); + JPopupMenu popupMenu = (JPopupMenu) _native; + popupMenu.pack(); + popupMenu.setFocusable(true); + // 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); @@ -181,22 +185,19 @@ class SwingSystemTray extends SwingMenu { x -= size.width; // snap to edge of mouse } - // voodoo to get this to popup to have the correct parent - // from: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6285881 - ((JPopupMenu) _native).setInvoker(_native); - _native.setLocation(x, y); - _native.setVisible(true); - _native.setFocusable(true); - _native.requestFocusInWindow(); + SwingSystemTrayMenuWindowsPopup popupMenu = (SwingSystemTrayMenuWindowsPopup) _native; + popupMenu.doShow(x, y); } }); try { tray.add(trayIcon); + ((SwingSystemTrayMenuWindowsPopup) _native).setIcon(iconFile); } catch (AWTException e) { dorkbox.systemTray.SystemTray.logger.error("TrayIcon could not be added.", e); } } else { + ((SwingSystemTrayMenuWindowsPopup) _native).setIcon(iconFile); trayIcon.setImage(trayImage); } } diff --git a/src/dorkbox/systemTray/swing/SwingSystemTrayMenuWindowsPopup.java b/src/dorkbox/systemTray/swing/SwingSystemTrayMenuWindowsPopup.java index 9948411..ad895d9 100644 --- a/src/dorkbox/systemTray/swing/SwingSystemTrayMenuWindowsPopup.java +++ b/src/dorkbox/systemTray/swing/SwingSystemTrayMenuWindowsPopup.java @@ -16,24 +16,36 @@ package dorkbox.systemTray.swing; import java.awt.Frame; +import java.awt.Image; import java.awt.event.WindowEvent; import java.awt.event.WindowFocusListener; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import javax.imageio.ImageIO; +import javax.swing.ImageIcon; import javax.swing.JDialog; import javax.swing.JPopupMenu; import javax.swing.border.EmptyBorder; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; + +import dorkbox.systemTray.SystemTray; +import dorkbox.util.OS; /** * This custom popup is required, because we cannot close this popup by clicking OUTSIDE the popup. For whatever reason, that does not * work, so we must implement an "auto-hide" feature that checks if our mouse is still inside a menu every POPUP_HIDE_DELAY seconds */ -public class SwingSystemTrayMenuWindowsPopup extends JPopupMenu { private static final long serialVersionUID = 1L; // NOTE: we can use the "hidden dialog" focus window trick... only on windows and mac private JDialog hiddenDialog; + private volatile File iconFile; + @SuppressWarnings("unchecked") SwingSystemTrayMenuWindowsPopup() { super(); setFocusable(true); @@ -41,14 +53,51 @@ class SwingSystemTrayMenuWindowsPopup extends JPopupMenu { setBorder(new EmptyBorder(1, 1, 1, 1)); - // Does not work correctly on linux. a window in the taskbar still shows up - // Initialize the hidden dialog as a headless, titleless dialog window - this.hiddenDialog = new JDialog((Frame)null); - this.hiddenDialog.setEnabled(false); + // Initialize the hidden dialog as a headless, title-less dialog window + this.hiddenDialog = new JDialog((Frame)null, "Tray menu"); this.hiddenDialog.setUndecorated(true); this.hiddenDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + this.hiddenDialog.setAlwaysOnTop(true); - this.hiddenDialog.setSize(1, 1); + // on Linux, the following two entries will **MOST OF THE TIME** prevent the hidden dialog from showing in the task-bar + // on MacOS, you need "special permission" to not have a hidden dialog show on the dock. + this.hiddenDialog.getContentPane().setLayout(null); + + // this is a java 1.7 deal, so we have to use reflection to set this. It's not critical for this to be set, but it helps + // this.hiddenDialog.setType(Window.Type.POPUP); + if (OS.javaVersion >= 7) { + try { + Class hiddenDialogClass = this.hiddenDialog.getClass(); + Method[] methods = hiddenDialogClass.getMethods(); + for (Method method : methods) { + if (method.getName() + .equals("setType")) { + + Class cl = (Class) Class.forName("java.awt.Window$Type"); + method.invoke(this.hiddenDialog, Enum.valueOf(cl, "POPUP")); + break; + } + } + } catch (Exception e) { + SystemTray.logger.error("Error setting the tray popup menu type. The parent window might show on the task bar."); + } + } + + this.hiddenDialog.pack(); + this.hiddenDialog.setBounds(0,0,0,0); + + addPopupMenuListener(new PopupMenuListener() { + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + } + + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + hiddenDialog.setVisible(false); + hiddenDialog.toBack(); + } + + public void popupMenuCanceled(PopupMenuEvent e) { + } + }); // Add the window focus listener to the hidden dialog this.hiddenDialog.addWindowFocusListener(new WindowFocusListener() { @@ -57,20 +106,42 @@ class SwingSystemTrayMenuWindowsPopup extends JPopupMenu { SwingSystemTrayMenuWindowsPopup.this.setVisible(false); } @Override - public void windowGainedFocus (WindowEvent we) {} + public void windowGainedFocus (WindowEvent we) { + } }); } - @Override - public - void setVisible(boolean makeVisible) { - this.hiddenDialog.setVisible(makeVisible); - this.hiddenDialog.setEnabled(false); + /** + * 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 + */ + void setIcon(final File iconFile) { + if (this.iconFile == null || !this.iconFile.equals(iconFile)) { + this.iconFile = iconFile; - super.setVisible(makeVisible); + try { + Image image = new ImageIcon(ImageIO.read(iconFile)).getImage(); + image.flush(); + + // we set the dialog window to have the same icon as what is on the system tray + this.hiddenDialog.setIconImage(image); + } catch (IOException e) { + SystemTray.logger.error("Error setting the icon for the popup menu task tray dialog"); + } + } + } + + void doShow(final int x, final int y) { + // critical to get the keyboard listeners working for the popup menu + setInvoker(this.hiddenDialog.getContentPane()); + + this.hiddenDialog.setLocation(x, y); + this.hiddenDialog.setVisible(true); + + setLocation(x, y); + setVisible(true); + requestFocusInWindow(); } - public void close() { this.hiddenDialog.setVisible(false); this.hiddenDialog.dispatchEvent(new WindowEvent(this.hiddenDialog, WindowEvent.WINDOW_CLOSING));