From aaa204dbd9610e3c86e71e2bdcb05f48771d8c12 Mon Sep 17 00:00:00 2001 From: Zmax0 Date: Thu, 25 Apr 2024 14:44:05 +0800 Subject: [PATCH] feat(gui): use Swing to create the tray menu --- .../client/gui/console/component/Tray.java | 63 ++++++++------- .../urbanspork/client/gui/tray/TrayIcon.java | 81 +++++++++++++++++++ .../gui/tray/menu/item/ConsoleMenuItem.java | 6 -- .../gui/tray/menu/item/ExitMenuItem.java | 8 -- .../gui/tray/menu/item/LanguageMenuItem.java | 65 +++++---------- .../gui/tray/menu/item/ServersMenuItem.java | 71 +++++++--------- .../tray/menu/item/TrayMenuItemBuilder.java | 18 ++--- 7 files changed, 169 insertions(+), 143 deletions(-) create mode 100644 urban-spork-client-gui/src/com/urbanspork/client/gui/tray/TrayIcon.java diff --git a/urban-spork-client-gui/src/com/urbanspork/client/gui/console/component/Tray.java b/urban-spork-client-gui/src/com/urbanspork/client/gui/console/component/Tray.java index 315cc1f6..1cda8f12 100644 --- a/urban-spork-client-gui/src/com/urbanspork/client/gui/console/component/Tray.java +++ b/urban-spork-client-gui/src/com/urbanspork/client/gui/console/component/Tray.java @@ -11,23 +11,25 @@ import java.awt.*; import java.awt.TrayIcon.MessageType; +import com.urbanspork.client.gui.tray.TrayIcon; +import javafx.application.Platform; + public class Tray { private static final boolean IS_SUPPORTED = SystemTray.isSupported(); - - private static final PopupMenu menu = new PopupMenu(); - + private static final JPopupMenu menu = new JPopupMenu(); private static final ImageIcon icon = new ImageIcon(Resource.TRAY_ICON); - - private static final TrayIcon trayIcon = IS_SUPPORTED ? new TrayIcon(icon.getImage(), I18N.getString(I18N.PROGRAM_TITLE), menu) : null; - + private static TrayIcon trayIcon; private static Console console; private Tray() {} public static void init(Console console) { Tray.console = console; - start(); + if (IS_SUPPORTED) { + trayIcon = new TrayIcon(icon.getImage(), I18N.getString(I18N.PROGRAM_TITLE), () -> Platform.runLater(console::show), menu); + start(); + } } public static void displayMessage(String caption, String text, MessageType messageType) { @@ -54,28 +56,31 @@ public static void exit() { } private static void start() { - if (IS_SUPPORTED) { - // ============================== - // tray icon - // ============================== - SystemTray tray = SystemTray.getSystemTray(); - trayIcon.setImageAutoSize(true); - try { - tray.add(trayIcon); - } catch (AWTException e) { - displayMessage("Error", e.getMessage(), MessageType.ERROR); - } - // ============================== - // tray menu - // ============================== - menu.add(new ServersMenuItem(console).build()); - menu.addSeparator(); - menu.add(new ConsoleMenuItem(console).build()); - menu.addSeparator(); - menu.add(new LanguageMenuItem().build()); - menu.addSeparator(); - menu.add(new ExitMenuItem().build()); + // ============================== + // tray icon + // ============================== + SystemTray tray = SystemTray.getSystemTray(); + trayIcon.setImageAutoSize(true); + try { + tray.add(trayIcon); + } catch (AWTException e) { + displayMessage("Error", e.getMessage(), MessageType.ERROR); } + // ============================== + // tray menu + // ============================== + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception ignore) { + // ignore + } + SwingUtilities.updateComponentTreeUI(menu); + menu.add(new ServersMenuItem(console).build()); + menu.addSeparator(); + menu.add(new ConsoleMenuItem(console).build()); + menu.addSeparator(); + menu.add(new LanguageMenuItem().build()); + menu.addSeparator(); + menu.add(new ExitMenuItem().build()); } - } \ No newline at end of file diff --git a/urban-spork-client-gui/src/com/urbanspork/client/gui/tray/TrayIcon.java b/urban-spork-client-gui/src/com/urbanspork/client/gui/tray/TrayIcon.java new file mode 100644 index 00000000..5bcf1928 --- /dev/null +++ b/urban-spork-client-gui/src/com/urbanspork/client/gui/tray/TrayIcon.java @@ -0,0 +1,81 @@ +package com.urbanspork.client.gui.tray; + +import javax.swing.*; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.Arrays; + +public class TrayIcon extends java.awt.TrayIcon { + private JDialog dialog; + + public TrayIcon(Image image, String tooltip, Runnable onLeftClick, JPopupMenu popup) { + super(image, tooltip); + setImageAutoSize(true); + // add mouse listener + addMouseListener(new MouseAdapter() { + @Override + public void mouseReleased(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1 && onLeftClick != null) { + onLeftClick.run(); + } + if (e.getButton() == MouseEvent.BUTTON3 && e.isPopupTrigger()) { + dialog.setLocation(adjustLocation(e.getPoint(), popup.getPreferredSize().getHeight())); + dialog.setVisible(true); + popup.show(dialog, 0, 0); + } + } + }); + // add property change listener + SystemTray.getSystemTray().addPropertyChangeListener("trayIcons", e -> { + java.awt.TrayIcon[] oldArray = (java.awt.TrayIcon[]) e.getOldValue(); + java.awt.TrayIcon[] newArray = (java.awt.TrayIcon[]) e.getNewValue(); + if (contains(oldArray, this) && !contains(newArray, this)) { + dialog.dispose(); + } + if (!contains(oldArray, this) && contains(newArray, this)) { + dialog = new JDialog(); + dialog.setUndecorated(true); + } + }); + // add popup menu listener + popup.addPopupMenuListener(new PopupMenuListener() { + @Override + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + // should do nothing + } + + @Override + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + dialog.setVisible(false); + } + + @Override + public void popupMenuCanceled(PopupMenuEvent e) { + dialog.setVisible(false); + } + }); + } + + private static Point adjustLocation(Point p, double menuHeight) { + GraphicsDevice screenDevice = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()[0]; + Rectangle bounds = screenDevice.getDefaultConfiguration().getBounds(); + if (bounds.contains(p)) { + return p; + } else { + double scale = screenDevice.getDisplayMode().getWidth() / bounds.getWidth(); + int x = (int) (p.getX() / scale); + int y = (int) (p.getY() / scale - menuHeight); + return new Point(x, y); + } + } + + private boolean contains(Object[] arr, Object obj) { + if (arr == null || arr.length == 0) { + return false; + } + return Arrays.asList(arr).contains(obj); + } +} diff --git a/urban-spork-client-gui/src/com/urbanspork/client/gui/tray/menu/item/ConsoleMenuItem.java b/urban-spork-client-gui/src/com/urbanspork/client/gui/tray/menu/item/ConsoleMenuItem.java index 8a574e10..3749968d 100644 --- a/urban-spork-client-gui/src/com/urbanspork/client/gui/tray/menu/item/ConsoleMenuItem.java +++ b/urban-spork-client-gui/src/com/urbanspork/client/gui/tray/menu/item/ConsoleMenuItem.java @@ -4,7 +4,6 @@ import com.urbanspork.client.gui.i18n.I18N; import javafx.application.Platform; -import java.awt.*; import java.awt.event.ActionListener; public class ConsoleMenuItem implements TrayMenuItemBuilder { @@ -15,11 +14,6 @@ public ConsoleMenuItem(Console console) { this.console = console; } - @Override - public Menu getMenuItem() { - return null; - } - @Override public String getLabel() { return I18N.getString(I18N.TRAY_MENU_CONSOLE); diff --git a/urban-spork-client-gui/src/com/urbanspork/client/gui/tray/menu/item/ExitMenuItem.java b/urban-spork-client-gui/src/com/urbanspork/client/gui/tray/menu/item/ExitMenuItem.java index 1b8566fe..bb376eee 100644 --- a/urban-spork-client-gui/src/com/urbanspork/client/gui/tray/menu/item/ExitMenuItem.java +++ b/urban-spork-client-gui/src/com/urbanspork/client/gui/tray/menu/item/ExitMenuItem.java @@ -5,16 +5,9 @@ import com.urbanspork.client.gui.i18n.I18N; import javafx.application.Platform; -import java.awt.*; import java.awt.event.ActionListener; public class ExitMenuItem implements TrayMenuItemBuilder { - - @Override - public MenuItem getMenuItem() { - return null; - } - @Override public String getLabel() { return I18N.getString(I18N.TRAY_EXIT); @@ -28,5 +21,4 @@ public ActionListener getActionListener() { Proxy.exit(); }; } - } \ No newline at end of file diff --git a/urban-spork-client-gui/src/com/urbanspork/client/gui/tray/menu/item/LanguageMenuItem.java b/urban-spork-client-gui/src/com/urbanspork/client/gui/tray/menu/item/LanguageMenuItem.java index 6c62e1ac..60304ef7 100644 --- a/urban-spork-client-gui/src/com/urbanspork/client/gui/tray/menu/item/LanguageMenuItem.java +++ b/urban-spork-client-gui/src/com/urbanspork/client/gui/tray/menu/item/LanguageMenuItem.java @@ -6,65 +6,44 @@ import com.urbanspork.common.config.ClientConfig; import com.urbanspork.common.config.ConfigHandler; -import java.awt.*; +import javax.swing.*; import java.awt.TrayIcon.MessageType; -import java.awt.event.ActionListener; -import java.util.ArrayList; -import java.util.List; import java.util.Locale; -public class LanguageMenuItem implements TrayMenuItemBuilder { - - @Override - public MenuItem getMenuItem() { - Menu menu = new Menu(getLabel()); +public class LanguageMenuItem { + public JMenuItem build() { + JMenu menu = new JMenu(getLabel()); ClientConfig config = Resource.config(); String language = config.getLanguage(); final Locale configLanguage = Locale.of(language); Locale[] languages = I18N.languages(); - List items = new ArrayList<>(languages.length); + ButtonGroup group = new ButtonGroup(); for (Locale locale : languages) { - CheckboxMenuItem item = new CheckboxMenuItem(); + JRadioButtonMenuItem item = new JRadioButtonMenuItem(); item.setName(locale.getLanguage()); - item.setLabel(locale.getDisplayLanguage(configLanguage)); + item.setText(locale.getDisplayLanguage(configLanguage)); if (locale.equals(configLanguage)) { - item.setState(true); + item.setSelected(true); } - item.addItemListener(l -> itemStateChanged(config, items, item)); - items.add(item); + item.addActionListener(evt -> { + if (item.isSelected()) { + config.setLanguage(item.getName()); + try { + ConfigHandler.DEFAULT.save(config); + } catch (Exception e) { + Tray.displayMessage("Error", "Save file error, cause: " + e.getMessage(), MessageType.ERROR); + return; + } + Tray.displayMessage("Config is saved", "Take effect after restart", MessageType.INFO); + } + }); + group.add(item); menu.add(item); } return menu; } - private void itemStateChanged(ClientConfig config, List items, CheckboxMenuItem item) { - if (item.getState()) { - config.setLanguage(item.getName()); - try { - ConfigHandler.DEFAULT.save(config); - } catch (Exception e) { - Tray.displayMessage("Error", "Save file error, cause: " + e.getMessage(), MessageType.ERROR); - return; - } - Tray.displayMessage("Config is saved", "Take effect after restart", MessageType.INFO); - for (CheckboxMenuItem i : items) { - if (!i.equals(item) && i.getState()) { - i.setState(false); - } - } - } else { - item.setState(true); - } - } - - @Override - public String getLabel() { + private String getLabel() { return I18N.getString(I18N.TRAY_MENU_LANGUAGE); } - - @Override - public ActionListener getActionListener() { - return null; - } - } \ No newline at end of file diff --git a/urban-spork-client-gui/src/com/urbanspork/client/gui/tray/menu/item/ServersMenuItem.java b/urban-spork-client-gui/src/com/urbanspork/client/gui/tray/menu/item/ServersMenuItem.java index c85f7156..5785f3bc 100644 --- a/urban-spork-client-gui/src/com/urbanspork/client/gui/tray/menu/item/ServersMenuItem.java +++ b/urban-spork-client-gui/src/com/urbanspork/client/gui/tray/menu/item/ServersMenuItem.java @@ -9,13 +9,12 @@ import com.urbanspork.common.config.ConfigHandler; import com.urbanspork.common.config.ServerConfig; -import java.awt.*; +import javax.swing.*; import java.awt.TrayIcon.MessageType; -import java.awt.event.ActionListener; -import java.util.ArrayList; +import java.awt.event.ItemListener; import java.util.List; -public class ServersMenuItem implements TrayMenuItemBuilder { +public class ServersMenuItem { private final Console console; @@ -23,59 +22,44 @@ public ServersMenuItem(Console console) { this.console = console; } - @Override - public MenuItem getMenuItem() { - Menu menu = new Menu(getLabel()); + public JMenuItem build() { + JMenu menu = new JMenu(getLabel()); ClientConfig config = Resource.config(); List servers = config.getServers(); + ButtonGroup group = new ButtonGroup(); if (servers != null && !servers.isEmpty()) { - final List items = new ArrayList<>(); - for (int j = 0; j < servers.size(); j++) { - ServerConfig server = servers.get(j); - CheckboxMenuItem item = new CheckboxMenuItem(); - item.setLabel(getLabel(server)); - if (config.getIndex() == j) { - item.setState(true); + for (int i = 0; i < servers.size(); i++) { + ServerConfig server = servers.get(i); + JRadioButtonMenuItem item = new JRadioButtonMenuItem(); + item.setText(getLabel(server)); + if (config.getIndex() == i) { + item.setSelected(true); } - item.addItemListener(listener -> itemStateChanged(config, items, item)); - items.add(item); + item.addItemListener(createItemListener(item, config, i)); + group.add(item); menu.add(item); } } return menu; } - private void itemStateChanged(ClientConfig config, List items, CheckboxMenuItem item) { - if (item.getState()) { - for (int k = 0; k < items.size(); k++) { - CheckboxMenuItem i = items.get(k); - if (i.equals(item)) { - config.setIndex(k); - console.getServerConfigJFXListView().getSelectionModel().select(k); + private ItemListener createItemListener(JRadioButtonMenuItem item, ClientConfig config, int index) { + return event -> { + if (item.isSelected()) { + config.setIndex(index); + console.getServerConfigJFXListView().getSelectionModel().select(index); + try { + ConfigHandler.DEFAULT.save(config); + } catch (Exception e) { + Tray.displayMessage("Error", "Save file error, cause: " + e.getMessage(), MessageType.ERROR); + return; } - if (!i.equals(item) && i.getState()) { - i.setState(false); - } - } - try { - ConfigHandler.DEFAULT.save(config); - } catch (Exception e) { - Tray.displayMessage("Error", "Save file error, cause: " + e.getMessage(), MessageType.ERROR); - return; + Proxy.launch(); } - Proxy.launch(); - } else { - item.setState(true); - } + }; } - @Override - public ActionListener getActionListener() { - return null; - } - - @Override - public String getLabel() { + private String getLabel() { return I18N.getString(I18N.TRAY_MENU_SERVERS); } @@ -87,5 +71,4 @@ private String getLabel(ServerConfig config) { } return builder.toString(); } - } \ No newline at end of file diff --git a/urban-spork-client-gui/src/com/urbanspork/client/gui/tray/menu/item/TrayMenuItemBuilder.java b/urban-spork-client-gui/src/com/urbanspork/client/gui/tray/menu/item/TrayMenuItemBuilder.java index 95762f77..198a1f43 100644 --- a/urban-spork-client-gui/src/com/urbanspork/client/gui/tray/menu/item/TrayMenuItemBuilder.java +++ b/urban-spork-client-gui/src/com/urbanspork/client/gui/tray/menu/item/TrayMenuItemBuilder.java @@ -1,26 +1,18 @@ package com.urbanspork.client.gui.tray.menu.item; -import java.awt.*; +import javax.swing.*; import java.awt.event.ActionListener; -import java.util.Optional; public interface TrayMenuItemBuilder { - MenuItem getMenuItem(); - String getLabel(); ActionListener getActionListener(); - default MenuItem build() { - return Optional.ofNullable(getMenuItem()).orElse(build(getLabel(), getActionListener())); - } - - default MenuItem build(String label, ActionListener listener) { - MenuItem item = new MenuItem(); - item.setLabel(label); - item.addActionListener(listener); + default JMenuItem build() { + JMenuItem item = new JMenuItem(); + item.setText(getLabel()); + item.addActionListener(getActionListener()); return item; } - }