forked from I2P_Developers/i2p.i2p
DTG: Implement second TrayManager menu implementation in Swing.
Use Swing for non-Windows menus because AWT looks terrible on Linux and the button handling there is almost impossible to fix TODO: test on Mac
This commit is contained in:
@ -6,6 +6,8 @@ import java.awt.TrayIcon;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.SwingWorker;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
@ -20,20 +22,17 @@ import net.i2p.desktopgui.router.RouterManager;
|
||||
*/
|
||||
class ExternalTrayManager extends TrayManager {
|
||||
|
||||
public ExternalTrayManager(I2PAppContext ctx, Main main) {
|
||||
super(ctx, main);
|
||||
public ExternalTrayManager(I2PAppContext ctx, Main main, boolean useSwing) {
|
||||
super(ctx, main, useSwing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PopupMenu getMainMenu() {
|
||||
PopupMenu popup = new PopupMenu();
|
||||
MenuItem startItem = new MenuItem(_t("Start I2P"));
|
||||
startItem.addActionListener(new ActionListener() {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0) {
|
||||
new SwingWorker<Object, Object>() {
|
||||
|
||||
@Override
|
||||
protected Object doInBackground() throws Exception {
|
||||
RouterManager.start();
|
||||
@ -48,10 +47,36 @@ class ExternalTrayManager extends TrayManager {
|
||||
//since that risks killing the I2P process as well.
|
||||
tray.remove(trayIcon);
|
||||
}
|
||||
|
||||
}.execute();
|
||||
}
|
||||
|
||||
});
|
||||
popup.add(startItem);
|
||||
return popup;
|
||||
}
|
||||
|
||||
public JPopupMenu getSwingMainMenu() {
|
||||
JPopupMenu popup = new JPopupMenu();
|
||||
JMenuItem startItem = new JMenuItem(_t("Start I2P"));
|
||||
startItem.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0) {
|
||||
new SwingWorker<Object, Object>() {
|
||||
@Override
|
||||
protected Object doInBackground() throws Exception {
|
||||
RouterManager.start();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
trayIcon.displayMessage(_t("Starting"), _t("I2P is starting!"), TrayIcon.MessageType.INFO);
|
||||
//Hide the tray icon.
|
||||
//We cannot stop the desktopgui program entirely,
|
||||
//since that risks killing the I2P process as well.
|
||||
tray.remove(trayIcon);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
popup.add(startItem);
|
||||
return popup;
|
||||
|
@ -5,6 +5,9 @@ import java.awt.PopupMenu;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.SwingWorker;
|
||||
|
||||
import net.i2p.desktopgui.router.RouterManager;
|
||||
@ -23,14 +26,14 @@ class InternalTrayManager extends TrayManager {
|
||||
private final RouterContext _context;
|
||||
private final Log log;
|
||||
private MenuItem _restartItem, _stopItem, _cancelItem;
|
||||
private JMenuItem _jrestartItem, _jstopItem, _jcancelItem;
|
||||
|
||||
public InternalTrayManager(RouterContext ctx, Main main) {
|
||||
super(ctx, main);
|
||||
public InternalTrayManager(RouterContext ctx, Main main, boolean useSwing) {
|
||||
super(ctx, main, useSwing);
|
||||
_context = ctx;
|
||||
log = ctx.logManager().getLog(InternalTrayManager.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PopupMenu getMainMenu() {
|
||||
PopupMenu popup = new PopupMenu();
|
||||
|
||||
@ -171,6 +174,146 @@ class InternalTrayManager extends TrayManager {
|
||||
return popup;
|
||||
}
|
||||
|
||||
public JPopupMenu getSwingMainMenu() {
|
||||
JPopupMenu popup = new JPopupMenu();
|
||||
|
||||
JMenuItem browserLauncher = new JMenuItem(_t("Launch I2P Browser"));
|
||||
browserLauncher.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0) {
|
||||
new SwingWorker<Object, Object>() {
|
||||
@Override
|
||||
protected Object doInBackground() throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
try {
|
||||
I2PDesktop.browse("http://localhost:7657");
|
||||
} catch (BrowseException e1) {
|
||||
log.log(Log.WARN, "Failed to open browser!", e1);
|
||||
}
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
|
||||
JMenu desktopguiConfigurationLauncher = new JMenu(_t("Configure I2P System Tray"));
|
||||
JMenuItem configSubmenu = new JMenuItem(_t("Disable"));
|
||||
configSubmenu.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0) {
|
||||
new SwingWorker<Object, Object>() {
|
||||
@Override
|
||||
protected Object doInBackground() throws Exception {
|
||||
configureDesktopgui(false);
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
|
||||
final JMenuItem restartItem;
|
||||
if (_context.hasWrapper()) {
|
||||
restartItem = new JMenuItem(_t("Restart I2P"));
|
||||
restartItem.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0) {
|
||||
new SwingWorker<Object, Object>() {
|
||||
@Override
|
||||
protected Object doInBackground() throws Exception {
|
||||
RouterManager.restartGracefully(_context);
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
restartItem = null;
|
||||
}
|
||||
|
||||
final JMenuItem stopItem = new JMenuItem(_t("Stop I2P"));
|
||||
stopItem.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0) {
|
||||
new SwingWorker<Object, Object>() {
|
||||
@Override
|
||||
protected Object doInBackground() throws Exception {
|
||||
RouterManager.shutDownGracefully(_context);
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
|
||||
final JMenuItem restartItem2;
|
||||
if (_context.hasWrapper()) {
|
||||
restartItem2 = new JMenuItem(_t("Restart I2P Immediately"));
|
||||
restartItem2.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0) {
|
||||
new SwingWorker<Object, Object>() {
|
||||
@Override
|
||||
protected Object doInBackground() throws Exception {
|
||||
RouterManager.restart(_context);
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
restartItem2 = null;
|
||||
}
|
||||
|
||||
final JMenuItem stopItem2 = new JMenuItem(_t("Stop I2P Immediately"));
|
||||
stopItem2.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0) {
|
||||
new SwingWorker<Object, Object>() {
|
||||
@Override
|
||||
protected Object doInBackground() throws Exception {
|
||||
RouterManager.shutDown(_context);
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
|
||||
final JMenuItem cancelItem = new JMenuItem(_t("Cancel I2P Shutdown"));
|
||||
cancelItem.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0) {
|
||||
new SwingWorker<Object, Object>() {
|
||||
@Override
|
||||
protected Object doInBackground() throws Exception {
|
||||
RouterManager.cancelShutdown(_context);
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
|
||||
popup.add(browserLauncher);
|
||||
popup.addSeparator();
|
||||
desktopguiConfigurationLauncher.add(configSubmenu);
|
||||
popup.add(desktopguiConfigurationLauncher);
|
||||
popup.addSeparator();
|
||||
if (_context.hasWrapper())
|
||||
popup.add(restartItem);
|
||||
popup.add(stopItem);
|
||||
if (_context.hasWrapper())
|
||||
popup.add(restartItem2);
|
||||
popup.add(stopItem2);
|
||||
popup.add(cancelItem);
|
||||
|
||||
_jrestartItem = restartItem;
|
||||
_jstopItem = stopItem;
|
||||
_jcancelItem = cancelItem;
|
||||
|
||||
return popup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the menu
|
||||
* @since 0.9.26
|
||||
@ -179,15 +322,23 @@ class InternalTrayManager extends TrayManager {
|
||||
boolean x = RouterManager.isShutdownInProgress(_context);
|
||||
if (_restartItem != null)
|
||||
_restartItem.setEnabled(!x);
|
||||
_stopItem.setEnabled(!x);
|
||||
_cancelItem.setEnabled(x);
|
||||
if (_stopItem != null)
|
||||
_stopItem.setEnabled(!x);
|
||||
if (_cancelItem != null)
|
||||
_cancelItem.setEnabled(x);
|
||||
if (_jrestartItem != null)
|
||||
_jrestartItem.setEnabled(!x);
|
||||
if (_jstopItem != null)
|
||||
_jstopItem.setEnabled(!x);
|
||||
if (_jcancelItem != null)
|
||||
_jcancelItem.setEnabled(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.26 from removed gui/DesktopguiConfigurationFrame
|
||||
*/
|
||||
private void configureDesktopgui(boolean enable) {
|
||||
String property = "desktopgui.enabled";
|
||||
String property = Main.PROP_ENABLE;
|
||||
String value = Boolean.toString(enable);
|
||||
try {
|
||||
|
||||
|
@ -15,6 +15,7 @@ import net.i2p.desktopgui.util.*;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.app.RouterApp;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SystemVersion;
|
||||
import net.i2p.util.Translate;
|
||||
import net.i2p.util.I2PProperties.I2PPropertyCallback;
|
||||
|
||||
@ -29,6 +30,8 @@ public class Main implements RouterApp {
|
||||
private final Log log;
|
||||
private ClientAppState _state = UNINITIALIZED;
|
||||
private TrayManager _trayManager;
|
||||
public static final String PROP_ENABLE = "desktopgui.enabled";
|
||||
private static final String PROP_SWING = "desktopgui.swing";
|
||||
|
||||
/**
|
||||
* @since 0.9.26
|
||||
@ -60,10 +63,11 @@ public class Main implements RouterApp {
|
||||
*/
|
||||
private synchronized void startUp() throws Exception {
|
||||
final TrayManager trayManager;
|
||||
boolean useSwing = _appContext.getProperty(PROP_SWING, !SystemVersion.isWindows());
|
||||
if (_context != null)
|
||||
trayManager = new InternalTrayManager(_context, this);
|
||||
trayManager = new InternalTrayManager(_context, this, useSwing);
|
||||
else
|
||||
trayManager = new ExternalTrayManager(_appContext, this);
|
||||
trayManager = new ExternalTrayManager(_appContext, this, useSwing);
|
||||
trayManager.startManager();
|
||||
_trayManager = trayManager;
|
||||
changeState(RUNNING);
|
||||
@ -72,14 +76,12 @@ public class Main implements RouterApp {
|
||||
|
||||
if (_context != null) {
|
||||
_context.addPropertyCallback(new I2PPropertyCallback() {
|
||||
|
||||
@Override
|
||||
public void propertyChanged(String arg0, String arg1) {
|
||||
if(arg0.equals(Translate.PROP_LANG)) {
|
||||
trayManager.languageChanged();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,26 @@
|
||||
package net.i2p.desktopgui;
|
||||
|
||||
import java.awt.AWTException;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.Image;
|
||||
import java.awt.PopupMenu;
|
||||
import java.awt.SystemTray;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.TrayIcon;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.awt.event.FocusListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.net.URL;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.event.MenuKeyEvent;
|
||||
import javax.swing.event.MenuKeyListener;
|
||||
import javax.swing.event.PopupMenuEvent;
|
||||
import javax.swing.event.PopupMenuListener;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.desktopgui.i18n.DesktopguiTranslator;
|
||||
import net.i2p.util.SystemVersion;
|
||||
@ -21,6 +32,7 @@ abstract class TrayManager {
|
||||
|
||||
protected final I2PAppContext _appContext;
|
||||
protected final Main _main;
|
||||
protected final boolean _useSwing;
|
||||
///The tray area, or null if unsupported
|
||||
protected SystemTray tray;
|
||||
///Our tray icon, or null if unsupported
|
||||
@ -29,37 +41,99 @@ abstract class TrayManager {
|
||||
/**
|
||||
* Instantiate tray manager.
|
||||
*/
|
||||
protected TrayManager(I2PAppContext ctx, Main main) {
|
||||
protected TrayManager(I2PAppContext ctx, Main main, boolean useSwing) {
|
||||
_appContext = ctx;
|
||||
_main = main;
|
||||
_useSwing = useSwing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the tray icon to the system tray and start everything up.
|
||||
*/
|
||||
public synchronized void startManager() throws AWTException {
|
||||
if(SystemTray.isSupported()) {
|
||||
// TODO figure out how to get menu to pop up on left-click
|
||||
// left-click does nothing by default
|
||||
// MouseListener, MouseEvent, ...
|
||||
tray = SystemTray.getSystemTray();
|
||||
// Windows typically has tooltips; Linux (at least Ubuntu) doesn't
|
||||
String tooltip = SystemVersion.isWindows() ? _t("I2P: Right-click for menu") : null;
|
||||
trayIcon = new TrayIcon(getTrayImage(), tooltip, getMainMenu());
|
||||
trayIcon.setImageAutoSize(true); //Resize image to fit the system tray
|
||||
tray.add(trayIcon);
|
||||
// 16x16 on Windows, 24x24 on Linux, but that will probably vary
|
||||
//System.out.println("Tray icon size is " + trayIcon.getSize());
|
||||
trayIcon.addMouseListener(new MouseListener() {
|
||||
public void mouseClicked(MouseEvent m) { updateMenu(); }
|
||||
public void mouseEntered(MouseEvent m) { updateMenu(); }
|
||||
public void mouseExited(MouseEvent m) { updateMenu(); }
|
||||
public void mousePressed(MouseEvent m) { updateMenu(); }
|
||||
public void mouseReleased(MouseEvent m) { updateMenu(); }
|
||||
});
|
||||
} else {
|
||||
public synchronized void startManager() throws AWTException {
|
||||
if (!SystemTray.isSupported())
|
||||
throw new AWTException("SystemTray not supported");
|
||||
}
|
||||
tray = SystemTray.getSystemTray();
|
||||
// Windows typically has tooltips; Linux (at least Ubuntu) doesn't
|
||||
String tooltip = SystemVersion.isWindows() ? _t("I2P: Right-click for menu") : null;
|
||||
TrayIcon ti;
|
||||
if (_useSwing)
|
||||
ti = getSwingTrayIcon(tooltip);
|
||||
else
|
||||
ti = getAWTTrayIcon(tooltip);
|
||||
ti.setImageAutoSize(true); //Resize image to fit the system tray
|
||||
tray.add(ti);
|
||||
trayIcon = ti;
|
||||
}
|
||||
|
||||
private TrayIcon getAWTTrayIcon(String tooltip) throws AWTException {
|
||||
PopupMenu menu = getMainMenu();
|
||||
if (!SystemVersion.isWindows())
|
||||
menu.setFont(new Font("Arial", Font.BOLD, 14));
|
||||
TrayIcon ti = new TrayIcon(getTrayImage(), tooltip, menu);
|
||||
ti.addMouseListener(new MouseListener() {
|
||||
public void mouseClicked(MouseEvent m) {}
|
||||
public void mouseEntered(MouseEvent m) {}
|
||||
public void mouseExited(MouseEvent m) {}
|
||||
public void mousePressed(MouseEvent m) { updateMenu(); }
|
||||
public void mouseReleased(MouseEvent m) { updateMenu(); }
|
||||
});
|
||||
return ti;
|
||||
}
|
||||
|
||||
private TrayIcon getSwingTrayIcon(String tooltip) throws AWTException {
|
||||
// A JPopupMenu by itself is hard to get rid of,
|
||||
// so we hang it off a zero-size, undecorated JFrame.
|
||||
// http://stackoverflow.com/questions/1498789/jpopupmenu-behavior
|
||||
// http://stackoverflow.com/questions/2581314/how-do-you-hide-a-swing-popup-when-you-click-somewhere-else
|
||||
final JFrame frame = new JFrame();
|
||||
// http://stackoverflow.com/questions/2011601/jframe-without-frame-border-maximum-button-minimum-button-and-frame-icon
|
||||
frame.setUndecorated(true);
|
||||
frame.setMinimumSize(new Dimension(0, 0));
|
||||
frame.setSize(0, 0);
|
||||
final JPopupMenu menu = getSwingMainMenu();
|
||||
menu.setFocusable(true);
|
||||
frame.add(menu);
|
||||
TrayIcon ti = new TrayIcon(getTrayImage(), tooltip, null);
|
||||
ti.addMouseListener(new MouseListener() {
|
||||
public void mouseClicked(MouseEvent e) {}
|
||||
public void mouseEntered(MouseEvent e) {}
|
||||
public void mouseExited(MouseEvent e) {}
|
||||
public void mousePressed(MouseEvent e) { handle(e); }
|
||||
public void mouseReleased(MouseEvent e) { handle(e); }
|
||||
private void handle(MouseEvent e) {
|
||||
// http://stackoverflow.com/questions/17258250/changing-the-laf-of-a-popupmenu-for-a-trayicon-in-java
|
||||
// menu visible check is failsafe, for when menu gets cancelled
|
||||
if (!frame.isVisible() || !menu.isVisible()) {
|
||||
frame.setLocation(e.getX(), e.getY());
|
||||
frame.setVisible(true);
|
||||
menu.show(frame, 0, 0);
|
||||
}
|
||||
updateMenu();
|
||||
}
|
||||
});
|
||||
menu.addPopupMenuListener(new PopupMenuListener() {
|
||||
public void popupMenuCanceled(PopupMenuEvent e) { frame.setVisible(false); }
|
||||
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {}
|
||||
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {}
|
||||
});
|
||||
// this is to make it go away when we click elsewhere
|
||||
// doesn't do anything
|
||||
menu.addFocusListener(new FocusListener() {
|
||||
public void focusGained(FocusEvent e) {}
|
||||
public void focusLost(FocusEvent e) { frame.setVisible(false); }
|
||||
});
|
||||
// this is to make it go away when we hit escape
|
||||
// doesn't do anything
|
||||
menu.addMenuKeyListener(new MenuKeyListener() {
|
||||
public void menuKeyPressed(MenuKeyEvent e) {}
|
||||
public void menuKeyReleased(MenuKeyEvent e) {}
|
||||
public void menuKeyTyped(MenuKeyEvent e) {
|
||||
if (e.getKeyChar() == (char) 0x1b)
|
||||
frame.setVisible(false);
|
||||
}
|
||||
});
|
||||
return ti;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,8 +150,11 @@ abstract class TrayManager {
|
||||
}
|
||||
|
||||
public synchronized void languageChanged() {
|
||||
if (trayIcon != null)
|
||||
trayIcon.setPopupMenu(getMainMenu());
|
||||
if (trayIcon != null) {
|
||||
if (!_useSwing)
|
||||
trayIcon.setPopupMenu(getMainMenu());
|
||||
// else TODO
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -86,6 +163,13 @@ abstract class TrayManager {
|
||||
*/
|
||||
protected abstract PopupMenu getMainMenu();
|
||||
|
||||
/**
|
||||
* Build a popup menu, adding callbacks to the different items.
|
||||
* @return popup menu
|
||||
* @since 0.9.26
|
||||
*/
|
||||
protected abstract JPopupMenu getSwingMainMenu();
|
||||
|
||||
/**
|
||||
* Update the menu
|
||||
* @since 0.9.26
|
||||
|
Reference in New Issue
Block a user