diff --git a/src/org/jdesktop/swinghelper/tray/JXTrayIcon.java b/src/org/jdesktop/swinghelper/tray/JXTrayIcon.java
new file mode 100644
index 0000000..4feb9c0
--- /dev/null
+++ b/src/org/jdesktop/swinghelper/tray/JXTrayIcon.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2008 Sun Microsystems, Inc., 4150 Network Circle,
+ * Santa Clara, California 95054, U.S.A. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package org.jdesktop.swinghelper.tray;
+
+import javax.swing.*;
+import javax.swing.event.PopupMenuListener;
+import javax.swing.event.PopupMenuEvent;
+import java.awt.*;
+import java.awt.geom.Ellipse2D;
+import java.awt.image.BufferedImage;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+
+public class JXTrayIcon extends TrayIcon {
+ private JPopupMenu menu;
+ private static JDialog dialog;
+ static {
+ dialog = new JDialog((Frame) null);
+ dialog.setUndecorated(true);
+ dialog.setAlwaysOnTop(true);
+ }
+
+ private static PopupMenuListener popupListener = new PopupMenuListener() {
+ public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
+ }
+
+ public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
+ dialog.setVisible(false);
+ }
+
+ public void popupMenuCanceled(PopupMenuEvent e) {
+ dialog.setVisible(false);
+ }
+ };
+
+
+ public JXTrayIcon(Image image) {
+ super(image);
+ addMouseListener(new MouseAdapter() {
+ public void mousePressed(MouseEvent e) {
+ showJPopupMenu(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ showJPopupMenu(e);
+ }
+ });
+ }
+
+ protected void showJPopupMenu(MouseEvent e) {
+ if (menu != null) {
+ Dimension size = menu.getPreferredSize();
+ showJPopupMenu(e.getX(), e.getY() - size.height);
+ }
+ }
+
+ protected void showJPopupMenu(int x, int y) {
+ dialog.setLocation(x, y);
+ dialog.setVisible(true);
+ menu.show(dialog.getContentPane(), 0, 0);
+ // popup works only for focused windows
+ dialog.toFront();
+ }
+
+ public JPopupMenu getJPopupMenu() {
+ return menu;
+ }
+
+ public void setJPopupMenu(JPopupMenu menu) {
+ if (this.menu != null) {
+ this.menu.removePopupMenuListener(popupListener);
+ }
+ this.menu = menu;
+ menu.addPopupMenuListener(popupListener);
+ }
+
+ private static void createGui() {
+ JXTrayIcon tray = new JXTrayIcon(createImage());
+ tray.setJPopupMenu(createJPopupMenu());
+ try {
+ SystemTray.getSystemTray().add(tray);
+ } catch (AWTException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ createGui();
+ }
+ });
+ }
+
+ static Image createImage() {
+ BufferedImage i = new BufferedImage(32, 32, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g2 = (Graphics2D) i.getGraphics();
+ g2.setColor(Color.RED);
+ g2.fill(new Ellipse2D.Float(0, 0, i.getWidth(), i.getHeight()));
+ g2.dispose();
+ return i;
+ }
+
+ static JPopupMenu createJPopupMenu() {
+ final JPopupMenu m = new JPopupMenu();
+ m.add(new JMenuItem("Item 1"));
+ m.add(new JMenuItem("Item 2"));
+ JMenu submenu = new JMenu("Submenu");
+ submenu.add(new JMenuItem("item 1"));
+ submenu.add(new JMenuItem("item 2"));
+ submenu.add(new JMenuItem("item 3"));
+ m.add(submenu);
+ JMenuItem exitItem = new JMenuItem("Exit");
+ exitItem.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ System.exit(0);
+ }
+ });
+ m.add(exitItem);
+ return m;
+ }
+}
diff --git a/src/qz/common/TrayManager.java b/src/qz/common/TrayManager.java
index 38e5d9a..daec196 100644
--- a/src/qz/common/TrayManager.java
+++ b/src/qz/common/TrayManager.java
@@ -26,6 +26,7 @@
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.jdesktop.swinghelper.tray.JXTrayIcon;
import qz.auth.Certificate;
import qz.deploy.DeployUtilities;
import qz.deploy.WindowsDeploy;
@@ -58,11 +59,7 @@ public class TrayManager {
// The cached icons
private final IconCache iconCache;
- // Time in millis before the popup menu disappears
- final int POPUP_TIMEOUT = 2000;
-
- // Custom swing pop-up menu
- AutoHidePopupTray tray;
+ JXTrayIcon tray;
private ConfirmDialog confirmDialog;
private GatewayDialog gatewayDialog;
@@ -105,15 +102,27 @@ public TrayManager() {
shortcutCreator = DeployUtilities.getSystemShortcutCreator();
shortcutCreator.setShortcutName(Constants.ABOUT_TITLE);
- // Initialize a custom Swing system tray that hides after a timeout
- tray = new AutoHidePopupTray(POPUP_TIMEOUT);
- tray.setToolTipText(name);
+ SystemUtilities.setSystemLookAndFeel();
+
+ if (SystemTray.isSupported()) {
+ // Accommodate some OS-specific tray bugs
+ tray = SystemUtilities.isLinux() ? new ModernTrayIcon() : new JXTrayIcon(new ImageIcon(new byte[1]).getImage());
- // Iterates over all images denoted by IconCache.getTypes() and caches them
- iconCache = new IconCache(tray.getIconSize());
- tray.setImage(iconCache.getImage(IconCache.Icon.DANGER_ICON));
+ // Iterates over all images denoted by IconCache.getTypes() and caches them
+ iconCache = new IconCache(tray.getSize());
+ tray.setImage(iconCache.getImage(IconCache.Icon.DANGER_ICON));
+ tray.setToolTip(name);
+
+ try {
+ SystemTray.getSystemTray().add(tray);
+ } catch (AWTException awt) {
+ trayLogger.log(Level.SEVERE, "Could not attach tray", awt);
+ }
+ } else {
+ iconCache = new IconCache();
+ }
- // Linux spcecific tasks
+ // OS-specific tasks
if (SystemUtilities.isLinux()) {
// Fix the tray icon to look proper on Ubuntu
UbuntuUtilities.fixTrayIcons(iconCache);
@@ -131,32 +140,7 @@ public TrayManager() {
// The ok/cancel dialog
confirmDialog = new ConfirmDialog(null, "Please Confirm", iconCache);
- addMenuItems(tray);
- //tray.displayMessage(name, name + " is running.", Level.INFO);
-
- if (tray.getTrayIcon()!=null){
- tray.getTrayIcon().addMouseListener(new MouseListener() {
- @Override
- public void mouseClicked(MouseEvent e) {
- if (e.getClickCount() == 2) {
- tray.setVisible(false);
- aboutListener.actionPerformed(new ActionEvent(e.getSource(),e.getID(),null));
- }
- }
-
- @Override
- public void mousePressed(MouseEvent e) {}
-
- @Override
- public void mouseReleased(MouseEvent e) {}
-
- @Override
- public void mouseEntered(MouseEvent e) {}
-
- @Override
- public void mouseExited(MouseEvent e) {}
- });
- }
+ addMenuItems();
}
/**
@@ -176,7 +160,9 @@ public void run() {
/**
* Builds the swing pop-up menu with the specified items
*/
- private void addMenuItems(JPopupMenu popup) {
+ private void addMenuItems() {
+ JPopupMenu popup = new JPopupMenu();
+
JMenu advancedMenu = new JMenu("Advanced");
advancedMenu.setMnemonic(KeyEvent.VK_A);
advancedMenu.setIcon(iconCache.getIcon(IconCache.Icon.SETTINGS_ICON));
@@ -253,6 +239,8 @@ private void addMenuItems(JPopupMenu popup) {
popup.add(startupItem);
popup.add(separator);
popup.add(exitItem);
+
+ tray.setJPopupMenu(popup);
}
@@ -391,10 +379,10 @@ private void shortcutToggle(ActionEvent e, DeployUtilities.ToggleType toggleType
// Remove shortcut entry
if (confirmDialog.prompt("Remove " + name + " from " + toggleType + "?")) {
if (!shortcutCreator.removeShortcut(toggleType)) {
- tray.displayMessage(name, "Error removing " + toggleType + " entry", Level.SEVERE);
+ tray.displayMessage(name, "Error removing " + toggleType + " entry", TrayIcon.MessageType.ERROR);
checkBoxState = true; // Set our checkbox back to true
} else {
- tray.displayMessage(name, "Successfully removed " + toggleType + " entry", Level.INFO);
+ tray.displayMessage(name, "Successfully removed " + toggleType + " entry", TrayIcon.MessageType.INFO);
}
} else {
checkBoxState = true; // Set our checkbox back to true
@@ -402,10 +390,10 @@ private void shortcutToggle(ActionEvent e, DeployUtilities.ToggleType toggleType
} else {
// Add shortcut entry
if (!shortcutCreator.createShortcut(toggleType)) {
- tray.displayMessage(name, "Error creating " + toggleType + " entry", Level.SEVERE);
+ tray.displayMessage(name, "Error creating " + toggleType + " entry", TrayIcon.MessageType.ERROR);
checkBoxState = false; // Set our checkbox back to false
} else {
- tray.displayMessage(name, "Successfully added " + toggleType + " entry", Level.INFO);
+ tray.displayMessage(name, "Successfully added " + toggleType + " entry", TrayIcon.MessageType.INFO);
}
}
@@ -418,7 +406,7 @@ private void shortcutToggle(ActionEvent e, DeployUtilities.ToggleType toggleType
* Displays a basic error dialog.
*/
private void showErrorDialog(String message) {
- JOptionPane.showMessageDialog(tray, message, name, JOptionPane.ERROR_MESSAGE);
+ JOptionPane.showMessageDialog(null, message, name, JOptionPane.ERROR_MESSAGE);
}
public boolean showGatewayDialog(final Certificate cert) {
@@ -522,7 +510,7 @@ public static String getPorts(Server server) {
* Thread safe method for setting an info status message
*/
public void displayInfoMessage(String text) {
- displayMessage(name, text, Level.INFO);
+ displayMessage(name, text, TrayIcon.MessageType.INFO);
}
/**
@@ -530,7 +518,7 @@ public void displayInfoMessage(String text) {
* notifications" is checked.
*/
public void displayFineMessage(String text) {
- displayMessage(name, text, Level.FINE);
+ displayMessage(name, text, TrayIcon.MessageType.NONE);
}
/**
@@ -544,7 +532,7 @@ public void setDefaultIcon() {
* Thread safe method for setting the error status message
*/
public void displayErrorMessage(String text) {
- displayMessage(name, text, Level.SEVERE);
+ displayMessage(name, text, TrayIcon.MessageType.ERROR);
}
/**
@@ -558,7 +546,7 @@ public void setDangerIcon() {
* Thread safe method for setting the warning status message
*/
public void displayWarningMessage(String text) {
- displayMessage(name, text, Level.WARNING);
+ displayMessage(name, text, TrayIcon.MessageType.WARNING);
}
@@ -577,7 +565,7 @@ private void setIcon(final IconCache.Icon i) {
SwingUtilities.invokeLater(new Thread(new Runnable() {
@Override
public void run() {
- tray.setIcon(iconCache.getIcon(i));
+ tray.setImage(iconCache.getImage(i));
}
}));
}
@@ -590,21 +578,34 @@ public void run() {
* @param text The text body of the tray message
* @param level The message type: Level.INFO, .WARN, .SEVERE
*/
- private void displayMessage(final String caption, final String text, final Level level) {
+ private void displayMessage(final String caption, final String text, final TrayIcon.MessageType level) {
if (tray != null) {
SwingUtilities.invokeLater(new Thread(new Runnable() {
@Override
public void run() {
boolean showAllNotifications = prefs.getBoolean(notificationsKey, false);
- if (showAllNotifications || (level == Level.INFO || level == Level.SEVERE)) {
+ if (showAllNotifications || (level == TrayIcon.MessageType.INFO || level == TrayIcon.MessageType.ERROR)) {
tray.displayMessage(caption, text, level);
}
- trayLogger.log(level, "Tray Message: " + text);
+ trayLogger.log(convertLevel(level), "Tray Message: " + text);
}
}));
}
}
+ public static Level convertLevel(TrayIcon.MessageType level) {
+ switch (level) {
+ case NONE:
+ return Level.FINE;
+ case ERROR:
+ return Level.SEVERE;
+ case WARNING:
+ return Level.WARNING;
+ default:
+ return Level.INFO;
+ }
+ }
+
public void addLogHandler(Logger logger) {
if (logHandler == null) {
try {
diff --git a/src/qz/ui/AutoHidePopupTray.java b/src/qz/ui/AutoHidePopupTray.java
deleted file mode 100644
index 3f9fd7d..0000000
--- a/src/qz/ui/AutoHidePopupTray.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/**
- * @author Tres Finocchiaro
- *
- * Copyright (C) 2013 Tres Finocchiaro, QZ Industries
- *
- * IMPORTANT: This software is dual-licensed
- *
- * LGPL 2.1 This is free software. This software and source code are released
- * under the "LGPL 2.1 License". A copy of this license should be distributed
- * with this software. http://www.gnu.org/licenses/lgpl-2.1.html
- *
- * QZ INDUSTRIES SOURCE CODE LICENSE This software and source code *may* instead
- * be distributed under the "QZ Industries Source Code License", available by
- * request ONLY. If source code for this project is to be made proprietary for
- * an individual and/or a commercial entity, written permission via a copy of
- * the "QZ Industries Source Code License" must be obtained first. If you've
- * obtained a copy of the proprietary license, the terms and conditions of the
- * license apply only to the licensee identified in the agreement. Only THEN may
- * the LGPL 2.1 license be voided.
- *
- */
-
-package qz.ui;
-
-import javax.swing.*;
-import java.awt.Component;
-import java.awt.event.MouseEvent;
-import java.awt.event.MouseListener;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * Pop-up Menu which auto-hides after a certain timeout
- * @author A. Tres Finocchiaro
- */
-public class AutoHidePopupTray extends PopupTray {
- // The timeout in milliseconds before this item is hidden
- private final int timeout;
-
- // Attached to all menu items, needed for detecting mouse hover
- private final MouseListener globalMouseListener;
-
- // Stores the current mouse hover status
- private boolean mouseOver;
-
- // Notifies the "hide listener thread" to terminate
- private boolean threadActive;
-
- /**
- * Default constructor
- * @param timeout The amount of time before this menu hides itself
- */
- public AutoHidePopupTray(final int timeout) {
- super();
- this.mouseOver = false;
- this.threadActive = true;
- this.timeout = timeout;
- this.globalMouseListener = createGlobalMouseListener();
- setSystemLookAndFeel();
- }
-
- @Override
- public void setVisible(boolean visible) {
- super.setVisible(visible);
- setMouseOver(true);
- if (visible) {
- createHideListenerThread(timeout);
- } else {
- stopHideListenerThreads();
- }
- }
-
- /**
- * Sets a flag indicating whether or not the mouse is currently over the menu
- * @param mouseOver whether or not the mouse is currently over the menu
- */
- private void setMouseOver(boolean mouseOver) {
- this.mouseOver = mouseOver;
- }
-
- /**
- * Returns whether or not the mouse is currently over the menu
- * @return whether or not the mouse is currently over the menu
- */
- private boolean getMouseOver() {
- return mouseOver;
- }
-
-
- /**
- * Adds a specified JMenuItem and also attaches a mouse listener which
- * is used to detect focus
- * @param menuItem The MenuItem to add
- * @return The MenuItem argument
- */
- @Override
- public JMenuItem add(JMenuItem menuItem) {
- if (menuItem != null) { menuItem.addMouseListener(globalMouseListener); }
- recurseSubMenuItems(menuItem);
- return super.add(menuItem);
- }
-
- public void recurseSubMenuItems(Component c) {
- if (c instanceof JMenu) {
- for (Component component : ((JMenu)c).getMenuComponents()) {
- component.addMouseListener(globalMouseListener);
- recurseSubMenuItems(component);
- }
- }
- }
-
- /**
- * Adds a specified Component and also attaches a mouse listener which
- * is used to detect focus
- * @param comp The Component to add
- * @return The Component argument
- */
- @Override
- public Component add(Component comp) {
- if (comp != null) { comp.addMouseListener(globalMouseListener); }
- return super.add(comp);
- }
-
-
- /**
- * Creates a single mouse listener which all JMenuItems will share
- * @return A new MouseListener object
- */
- private MouseListener createGlobalMouseListener() {
- return new MouseListener() {
- public void mouseClicked(MouseEvent e) {}
- public void mousePressed(MouseEvent e) {}
- public void mouseReleased(MouseEvent e) {}
- public void mouseEntered(MouseEvent e) { setMouseOver(true); }
- public void mouseExited(MouseEvent e) { setMouseOver(false); }
- };
- }
-
- /**
- * Tells all internal "hide listener threads" to stop
- */
- private void stopHideListenerThreads() {
- threadActive = false;
- }
-
-
-
- /**
- * Creates a new thread which hides this component after a specified timeout value
- * Self-aware of mouse position, this will only be hidden if the mouse isn't
- * currently over the component.
- *
- * Since mouse position outside of a pop-up is impossible to determine on a
- * system tray, we use the JMenuItem's enter/exit to set the hide flag instead
- * @param timeout The timeout in milliseconds before this component is hidden
- */
- private void createHideListenerThread(final int timeout) {
- threadActive = true;
- new Thread(new Runnable() {
- final int STEP = 100;
- int counter = 0;
- @Override
- public void run() {
- while (threadActive) {
- try { Thread.sleep(STEP); }
- catch (InterruptedException e) {}
- if (!threadActive) {
- break;
- }
- if (!getMouseOver()) {
- counter++;
- } else {
- counter = 0;
- }
- if (counter * STEP > timeout) {
- SwingUtilities.invokeLater(new Thread(new Runnable() {
- public void run() { setVisible(false); }
- }));
- }
- }
- }
- }).start();
- }
-
- /**
- * Attempts to set the Java Look & Feel to that which matches the Operating
- * System
- */
- private void setSystemLookAndFeel() {
- try {
- UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
- } catch (Exception e) {
- Logger.getLogger(getClass().getName()).log(Level.WARNING,
- "Error getting the default look and feel");
- }
- }
-}
diff --git a/src/qz/ui/ModernTrayIcon.java b/src/qz/ui/ModernTrayIcon.java
new file mode 100644
index 0000000..989e3dc
--- /dev/null
+++ b/src/qz/ui/ModernTrayIcon.java
@@ -0,0 +1,146 @@
+/**
+ * @author Tres Finocchiaro
+ *
+ * Copyright (C) 2013 Tres Finocchiaro, QZ Industries
+ *
+ * IMPORTANT: This software is dual-licensed
+ *
+ * LGPL 2.1 This is free software. This software and source code are released
+ * under the "LGPL 2.1 License". A copy of this license should be distributed
+ * with this software. http://www.gnu.org/licenses/lgpl-2.1.html
+ *
+ * QZ INDUSTRIES SOURCE CODE LICENSE This software and source code *may* instead
+ * be distributed under the "QZ Industries Source Code License", available by
+ * request ONLY. If source code for this project is to be made proprietary for
+ * an individual and/or a commercial entity, written permission via a copy of
+ * the "QZ Industries Source Code License" must be obtained first. If you've
+ * obtained a copy of the proprietary license, the terms and conditions of the
+ * license apply only to the licensee identified in the agreement. Only THEN may
+ * the LGPL 2.1 license be voided.
+ *
+ */
+
+package qz.ui;
+
+import org.jdesktop.swinghelper.tray.JXTrayIcon;
+import qz.utils.SystemUtilities;
+
+import javax.swing.*;
+import javax.swing.event.PopupMenuEvent;
+import javax.swing.event.PopupMenuListener;
+import java.awt.*;
+import java.awt.event.AWTEventListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+
+/**
+ * @author A. Tres Finocchiaro
+ */
+public class ModernTrayIcon extends JXTrayIcon {
+ private JFrame invisibleFrame;
+ private JPopupMenu popup;
+ private int x = 0, y = 0;
+
+ public ModernTrayIcon() {
+ super(new ImageIcon(new byte[1]).getImage());
+ }
+
+ public ModernTrayIcon(Image image) {
+ super(image);
+ }
+
+ public ModernTrayIcon(Image image, String tooltip) {
+ super(image);
+ setToolTip(tooltip);
+ }
+
+ public ModernTrayIcon(Image image, String tooltip, JPopupMenu popup) {
+ super(image);
+ setToolTip(tooltip);
+ setJPopupMenu(popup);
+ }
+
+ @Override
+ public void setJPopupMenu(final JPopupMenu popup) {
+ this.popup = popup;
+
+ invisibleFrame = new JFrame();
+ invisibleFrame.setAlwaysOnTop(true);
+ invisibleFrame.setUndecorated(true);
+ invisibleFrame.setBackground(Color.BLACK);
+ invisibleFrame.setSize(0, 0);
+ invisibleFrame.pack();
+
+ popup.addPopupMenuListener(new PopupMenuListener() {
+ @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) {}
+ @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { invisibleFrame.setVisible(false); }
+ @Override public void popupMenuCanceled(PopupMenuEvent e) { invisibleFrame.setVisible(false); }
+ });
+
+ invisibleFrame.addWindowListener(new WindowListener() {
+ @Override
+ public void windowActivated(WindowEvent we) {
+ // X11 bug
+ if (SystemUtilities.isFedora()) {
+ popup.show(invisibleFrame, 0, 0);
+ } else {
+ popup.setInvoker(invisibleFrame);
+ popup.setVisible(true);
+ popup.setLocation(x, SystemUtilities.isWindows() ? y - popup.getHeight() : y);
+ }
+ popup.requestFocus();
+ }
+
+ @Override public void windowOpened(WindowEvent we) {}
+ @Override public void windowClosing(WindowEvent we) {}
+ @Override public void windowClosed(WindowEvent we) {}
+ @Override public void windowIconified(WindowEvent we) {}
+ @Override public void windowDeiconified(WindowEvent we) {}
+ @Override public void windowDeactivated(WindowEvent we) {}
+ });
+
+ addTrayListener();
+ }
+
+ @Override
+ public JPopupMenu getJPopupMenu() {
+ return popup;
+ }
+
+ /**
+ * Functional equivalent of a MouseAdapter
, but accommodates an edge-case in Gnome3 where the tray
+ * icon cannot listen on mouse events.
+ */
+ private void addTrayListener() {
+ Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
+ @Override
+ public void eventDispatched(AWTEvent e) {
+ Point p = isTrayEvent(e);
+ if (p != null) {
+ x = p.x; y = p.y;
+ invisibleFrame.setVisible(true);
+ invisibleFrame.requestFocus();
+ }
+ }
+ }, MouseEvent.MOUSE_EVENT_MASK);
+ }
+
+ /**
+ * Determines if TrayIcon event is detected
+ * @param e An AWTEvent
+ * @return A Point on the screen which the tray event occurred, or null if none is found
+ */
+ private static Point isTrayEvent(AWTEvent e) {
+ if (e instanceof MouseEvent) {
+ MouseEvent me = (MouseEvent)e;
+
+ if (me.getID() == MouseEvent.MOUSE_RELEASED && me.getSource() != null) {
+ if (me.getSource().getClass().getName().contains("TrayIcon")) {
+ return me.getLocationOnScreen();
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/qz/ui/PopupTray.java b/src/qz/ui/PopupTray.java
deleted file mode 100644
index 509bcba..0000000
--- a/src/qz/ui/PopupTray.java
+++ /dev/null
@@ -1,284 +0,0 @@
-/**
- * @author Tres Finocchiaro
- *
- * Copyright (C) 2013 Tres Finocchiaro, QZ Industries
- *
- * IMPORTANT: This software is dual-licensed
- *
- * LGPL 2.1 This is free software. This software and source code are released
- * under the "LGPL 2.1 License". A copy of this license should be distributed
- * with this software. http://www.gnu.org/licenses/lgpl-2.1.html
- *
- * QZ INDUSTRIES SOURCE CODE LICENSE This software and source code *may* instead
- * be distributed under the "QZ Industries Source Code License", available by
- * request ONLY. If source code for this project is to be made proprietary for
- * an individual and/or a commercial entity, written permission via a copy of
- * the "QZ Industries Source Code License" must be obtained first. If you've
- * obtained a copy of the proprietary license, the terms and conditions of the
- * license apply only to the licensee identified in the agreement. Only THEN may
- * the LGPL 2.1 license be voided.
- *
- */
-
-package qz.ui;
-
-import qz.utils.SystemUtilities;
-
-import javax.swing.*;
-import java.awt.*;
-import java.awt.event.AWTEventListener;
-import java.awt.event.MouseEvent;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * Creates a more robust System Tray menu by leveraging a JPopupMenu.
- * Java currently only supports an AWT PopupMenu, so this class is
- * a work-around for that limitation.
- *
- * @author A. Tres Finocchiaro
- */
-public class PopupTray extends JPopupMenu {
- private final SystemTray systemTray;
- private final TrayIcon trayIcon;
-
- /**
- * Constructs a JPopupMenu attached to the system's SystemTray with
- * the specified imageIcon and toolTipText
- * @param imageIcon The icon to display in the System Tray
- * @param toolTipText The text to display during tray mouse-over
- */
- public PopupTray(ImageIcon imageIcon, String toolTipText) {
- super();
- trayIcon = createTrayIcon();
- systemTray = getSystemTray();
- attachSystemTray(trayIcon);
- setIcon(imageIcon);
- setToolTipText(toolTipText);
- }
-
- /**
- * Constructs a JPopupMenu attached to the system's SystemTray with
- * the specified image and toolTipText
- * @param image The image to display in the System Tray
- * @param toolTipText The text to display during tray mouse-over
- */
- public PopupTray(Image image, String toolTipText) {
- super(toolTipText);
- trayIcon = createTrayIcon();
- systemTray = getSystemTray();
- attachSystemTray(trayIcon);
- setImage(image);
- setToolTipText(toolTipText);
- }
-
- /**
- * Constructs a JPopupMenu attached to the system's SystemTray with
- * the specified toolTipText
- * @param toolTipText The text to display during tray mouse-over
- */
- public PopupTray(String toolTipText) {
- super(toolTipText);
- trayIcon = createTrayIcon();
- systemTray = getSystemTray();
- attachSystemTray(trayIcon);
- setToolTipText(toolTipText);
- }
-
- /**
- * Constructs a JPopupMenu attached to the system's SystemTray with
- * no icon and no toolTipText
- */
- public PopupTray() {
- super();
- trayIcon = createTrayIcon();
- systemTray = getSystemTray();
- attachSystemTray(trayIcon);
- }
-
- /**
- * Sets the TrayIcon image based on the Image specified
- * @param image The image to set the TrayIcon to
- */
- public final void setImage(Image image) {
- if (image != null && trayIcon != null) {
- trayIcon.setImage(image);
- }
- }
-
- /**
- * Sets the toolTipText for the underlying TrayIcon instance, rather than
- * that of the JPopupMenu
- * @param toolTipText tool text to set
- */
- @Override
- public final void setToolTipText(String toolTipText) {
- if (trayIcon != null){
- trayIcon.setToolTip(toolTipText);
- }
- }
-
- /**
- * Returns the underlying TrayIcon for this object
- * @return The underlying TrayIcon object
- */
- public TrayIcon getTrayIcon() {
- return trayIcon;
- }
-
- /**
- * Returns the width of the underlying TrayIcon
- * @return Width measurement in pixels
- */
- public int getTrayWidth() {
- return (trayIcon != null)?(int)trayIcon.getSize().getWidth():1;
- }
-
- /**
- * Returns the height of the underlying TrayIcon
- * @return Height measurement in pixels
- */
- public int getTrayHeight() {
- return (trayIcon != null)?(int)trayIcon.getSize().getHeight():1;
- }
-
- /**
- * Returns the size of the TrayIcons
- * @return Height measurement in pixels
- */
- public Dimension getIconSize() {
- if (trayIcon != null){
- return trayIcon.getSize();
- }
- return new Dimension(1,1);
- }
-
- /**
- * Sets the TrayIcon image based on the ImageIcon specified
- * @param imageIcon The ImageIcon to set the TrayIcon to
- */
- public final void setIcon(ImageIcon imageIcon) {
- if (imageIcon != null && trayIcon != null) {
- trayIcon.setImage(imageIcon.getImage());
- }
- }
-
- /**
- * Creates a custom tray icon with this JPopupMenu attached
- * @return The AWT TrayIcon associated with this JPopupMenu
- */
- private TrayIcon createTrayIcon() {
- // Create an empty tray icon
- if (SystemTray.isSupported()){
- TrayIcon newTrayIcon = new TrayIcon(new ImageIcon(new byte[1]).getImage());
- addTrayListener();
- newTrayIcon.setImageAutoSize(true);
- return newTrayIcon;
- }
- return null;
- }
-
- /**
- * Functional equivalent of a MouseAdapter
, but accommodates an edge-cases in Gnome3 where the tray
- * icon cannot listen on mouse events.
- *
- * Since there's no component to listen on, show popup only when
- * MouseEvent.getComponent()
is null
- */
- private void addTrayListener() {
- Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
- @Override
- public void eventDispatched(AWTEvent e) {
- if (isTrayEvent(e)) {
- MouseEvent me = (MouseEvent)e;
- // X11 bug
- if (SystemUtilities.isFedora()) {
- show(me.getComponent(),0,0);
- } else {
- setInvoker(PopupTray.this);
- setVisible(true);
- setLocation(me.getX(), me.getY() - (SystemUtilities.isWindows() ? getHeight() : 0));
- }
- }
- }
- }, MouseEvent.MOUSE_EVENT_MASK);
- }
-
- private boolean isTrayEvent(AWTEvent e) {
- if (e instanceof MouseEvent) {
- MouseEvent me = (MouseEvent)e;
- if (me.getID() == MouseEvent.MOUSE_RELEASED && me.getSource() != null) {
- return me.getSource().getClass().getName().contains("TrayIcon");
- }
- }
- return false;
- }
-
-
- /**
- * Convenience method for getTrayIcon().displayMessage(...).
- * Displays a pop-up message near the tray icon. The message will
- * disappear after a time or if the user clicks on it.
- * @param caption the caption displayed above the text, usually in
- * bold; may be null
- * @param text the text displayed for the particular message; may be
- * null
- * @param level The Logger message severity, shouldn't be
- * null
- */
- public void displayMessage(String caption, String text, Level level) {
- if (trayIcon != null){
- trayIcon.displayMessage(caption, text, convert(level));
- }
- }
-
-
- /**
- * Attaches the specified TrayIcon to the SystemTray
- * @param trayIcon The TrayIcon to attach
- */
- private void attachSystemTray(TrayIcon trayIcon) {
- try {
- if (getSystemTray() != null) {
- getSystemTray().add(trayIcon);
- }
- } catch (AWTException e) {
- Logger.getLogger(getClass().getName()).log(Level.WARNING,
- "Tray menu could not be added: {0}", e.getLocalizedMessage());
- if (SystemUtilities.isLinux()) {
- Logger.getLogger(getClass().getName()).log(Level.WARNING,
- "Ubuntu users: If the tray icon does not appear try running the following:\n\n" +
- " $ gsettings set com.canonical.Unity.Panel systray-whitelist \"['all']\"\n\n" +
- "... then log out and log back in to restart Unity.");
- }
- }
- }
-
- /**
- * Returns the SystemTray, or null if none exists
- * @return The SystemTray, or null if none exists
- */
- private SystemTray getSystemTray() {
- if (SystemTray.isSupported()) {
- if (systemTray == null) {
- return SystemTray.getSystemTray();
- }
- return systemTray;
- } else {
- Logger.getLogger(getClass().getName()).log(Level.SEVERE,
- "System tray is not supported on this platform");
- return null;
- }
- }
-
- public static TrayIcon.MessageType convert(Level level) {
- if (level.equals(Level.SEVERE)) {
- return TrayIcon.MessageType.ERROR;
- } else if (level.equals(Level.WARNING)) {
- return TrayIcon.MessageType.WARNING;
- } else if (level.equals(Level.INFO)) {
- return TrayIcon.MessageType.INFO;
- }
- return TrayIcon.MessageType.NONE;
- }
-}
diff --git a/src/qz/utils/SystemUtilities.java b/src/qz/utils/SystemUtilities.java
index af51ba9..3678c1d 100644
--- a/src/qz/utils/SystemUtilities.java
+++ b/src/qz/utils/SystemUtilities.java
@@ -24,7 +24,10 @@
import qz.common.Constants;
+import javax.swing.*;
import java.io.File;
+import java.util.logging.Level;
+import java.util.logging.Logger;
/**
* Utility class for OS detection functions.
@@ -170,5 +173,15 @@ public static String getUname() {
}
return uname;
}
-
+
+ public static boolean setSystemLookAndFeel() {
+ try {
+ UIManager.getDefaults().put("Button.showMnemonics", Boolean.TRUE);
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ return true;
+ } catch (Exception e) {
+ Logger.getLogger(SystemUtilities.class.getName()).log(Level.WARNING, "Error getting the default look and feel");
+ }
+ return false;
+ }
}