diff --git a/src/main/java/org/qora/gui/SysTray.java b/src/main/java/org/qora/gui/SysTray.java index 2133445b..affdf4bd 100644 --- a/src/main/java/org/qora/gui/SysTray.java +++ b/src/main/java/org/qora/gui/SysTray.java @@ -1,15 +1,22 @@ package org.qora.gui; import java.awt.AWTException; -import java.awt.MenuItem; -import java.awt.PopupMenu; import java.awt.SystemTray; import java.awt.TrayIcon; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.WindowEvent; +import java.awt.event.WindowFocusListener; import java.net.URL; +import javax.swing.JDialog; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; import javax.swing.SwingWorker; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -24,15 +31,47 @@ public class SysTray { private static SysTray instance; private TrayIcon trayIcon = null; - private PopupMenu popupMenu = null; + private JPopupMenu popupMenu = null; + /** The hidden dialog has 'focus' when menu displayed so closes the menu when user clicks elsewhere. */ + private JDialog hiddenDialog = null; private SysTray() { if (!SystemTray.isSupported()) return; - this.popupMenu = createPopupMenu(); + this.popupMenu = createJPopupMenu(); - this.trayIcon = new TrayIcon(Gui.loadImage("icons/icon32.png"), "qora-core", popupMenu); + // Build TrayIcon without AWT PopupMenu (which doesn't support Unicode)... + this.trayIcon = new TrayIcon(Gui.loadImage("icons/icon32.png"), "qora-core", null); + // ...and attach mouse listener instead so we can use JPopupMenu (which does support Unicode) + this.trayIcon.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed (MouseEvent me) { + this.maybePopupMenu(me); + } + + @Override + public void mouseReleased (MouseEvent me) { + this.maybePopupMenu(me); + } + + private void maybePopupMenu(MouseEvent me) { + if (me.isPopupTrigger()) { + // We destroy, then recreate, the hidden dialog to prevent taskbar entries on X11 + if (!popupMenu.isVisible()) + destroyHiddenDialog(); + + createHiddenDialog(); + hiddenDialog.setLocation(me.getX() + 1, me.getY() - 1); + popupMenu.setLocation(me.getX() + 1, me.getY() - 1); + + popupMenu.setInvoker(hiddenDialog); + + hiddenDialog.setVisible(true); + popupMenu.setVisible(true); + } + } + }); this.trayIcon.setImageAutoSize(true); @@ -43,6 +82,81 @@ public class SysTray { } } + private void createHiddenDialog() { + if (hiddenDialog != null) + return; + + hiddenDialog = new JDialog(); + hiddenDialog.setUndecorated(true); + hiddenDialog.setSize(10, 10); + hiddenDialog.addWindowFocusListener(new WindowFocusListener () { + @Override + public void windowLostFocus (WindowEvent we ) { + destroyHiddenDialog(); + } + + @Override + public void windowGainedFocus (WindowEvent we) { + } + }); + } + + private void destroyHiddenDialog() { + if (hiddenDialog == null) + return; + + hiddenDialog.setVisible(false); + hiddenDialog.dispose(); + hiddenDialog = null; + } + + private JPopupMenu createJPopupMenu() { + JPopupMenu menu = new JPopupMenu(); + + menu.addPopupMenuListener(new PopupMenuListener() { + @Override + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + } + + @Override + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + destroyHiddenDialog(); + } + + @Override + public void popupMenuCanceled(PopupMenuEvent e) { + } + }); + + JMenuItem openUi = new JMenuItem(Translator.INSTANCE.translate("SysTray", "OPEN_NODE_UI")); + openUi.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + destroyHiddenDialog(); + + try { + URLViewer.openWebpage(new URL("http://localhost:" + Settings.getInstance().getUiPort())); + } catch (Exception e1) { + LOGGER.error("Unable to open node UI in browser"); + } + } + }); + menu.add(openUi); + + JMenuItem exit = new JMenuItem(Translator.INSTANCE.translate("SysTray", "EXIT")); + exit.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + destroyHiddenDialog(); + + new ClosingWorker().execute(); + } + }); + menu.add(exit); + + return menu; + } + class ClosingWorker extends SwingWorker { @Override protected Void doInBackground() { @@ -56,32 +170,6 @@ public class SysTray { } } - private PopupMenu createPopupMenu() { - PopupMenu menu = new PopupMenu(); - - MenuItem openUi = new MenuItem(Translator.INSTANCE.translate("SysTray", "OPEN_NODE_UI")); - openUi.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - try { - URLViewer.openWebpage(new URL("http://localhost:" + Settings.getInstance().getUiPort())); - } catch (Exception e1) { - LOGGER.error("Unable to open node UI in browser"); - } - } - }); - menu.add(openUi); - - MenuItem exit = new MenuItem(Translator.INSTANCE.translate("SysTray", "EXIT")); - exit.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - new ClosingWorker().execute(); - } - }); - menu.add(exit); - - return menu; - } - public static SysTray getInstance() { if (instance == null) instance = new SysTray(); diff --git a/src/main/resources/i18n/SysTray_zh.properties b/src/main/resources/i18n/SysTray_zh.properties index 3a501c3c..1e56cba7 100644 --- a/src/main/resources/i18n/SysTray_zh.properties +++ b/src/main/resources/i18n/SysTray_zh.properties @@ -1,7 +1,7 @@ # SysTray pop-up menu -OPEN_NODE_UI=开启界面 -EXIT=退出软件 +OPEN_NODE_UI=\u5F00\u542F\u754C\u9762 +EXIT=\u9000\u51FA\u8F6F\u4EF6 # Nagging about lack of NTP time sync -NTP_NAG_CAPTION=没有连接上节点? -NTP_NAG_TEXT=请启用Windows自动时间同步。 +NTP_NAG_CAPTION=\u6CA1\u6709\u8FDE\u63A5\u4E0A\u8282\u70B9\uFF1F +NTP_NAG_TEXT=\u8BF7\u542F\u7528Windows\u81EA\u52A8\u65F6\u95F4\u540C\u6B65\u3002 diff --git a/src/test/java/org/qora/test/GuiTests.java b/src/test/java/org/qora/test/GuiTests.java index 6b2cb35b..27a9219a 100644 --- a/src/test/java/org/qora/test/GuiTests.java +++ b/src/test/java/org/qora/test/GuiTests.java @@ -2,6 +2,7 @@ package org.qora.test; import org.junit.Test; import org.qora.gui.SplashFrame; +import org.qora.gui.SysTray; public class GuiTests { @@ -14,4 +15,13 @@ public class GuiTests { splashFrame.dispose(); } + @Test + public void testSysTray() throws InterruptedException { + SysTray.getInstance(); + + while(true) { + Thread.sleep(2000L); + } + } + }