You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
503 lines
16 KiB
Java
503 lines
16 KiB
Java
/*
|
|
* Copyright (C) 2009-2017 Alistair Neil <info@dazzleships.net>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
package lib;
|
|
|
|
import java.awt.AWTEvent;
|
|
import java.awt.Color;
|
|
import java.awt.Dimension;
|
|
import java.awt.GraphicsConfiguration;
|
|
import java.awt.Insets;
|
|
import java.awt.MouseInfo;
|
|
import java.awt.Point;
|
|
import java.awt.Rectangle;
|
|
import java.awt.SystemTray;
|
|
import java.awt.Toolkit;
|
|
import java.awt.TrayIcon;
|
|
import java.awt.Window.Type;
|
|
import java.awt.event.AWTEventListener;
|
|
import java.awt.event.ComponentEvent;
|
|
import java.awt.event.ComponentListener;
|
|
import java.awt.event.MouseEvent;
|
|
import java.awt.event.MouseListener;
|
|
import java.util.regex.Pattern;
|
|
import javax.swing.BorderFactory;
|
|
import javax.swing.JDialog;
|
|
import javax.swing.JTextArea;
|
|
import javax.swing.JToolTip;
|
|
import javax.swing.JWindow;
|
|
import javax.swing.UIManager;
|
|
import javax.swing.event.PopupMenuEvent;
|
|
import javax.swing.event.PopupMenuListener;
|
|
|
|
/**
|
|
*
|
|
* @author Alistair Neil <info@dazzleships.net>
|
|
*/
|
|
public final class SwingTrayIcon {
|
|
|
|
private InfoTip infoToolTip;
|
|
private JDialog jdiagPopup;
|
|
private TrayPopupMenu trayPopupMenu;
|
|
private AWTEventListener awtEventListen;
|
|
private volatile boolean mouseExited = false;
|
|
private SystemTray st = null;
|
|
private GraphicsConfiguration gc = null;
|
|
private Point mouseloc;
|
|
private TrayIcon ti;
|
|
private boolean loaded;
|
|
|
|
public SwingTrayIcon(GraphicsConfiguration gc, String resourcepath) {
|
|
loaded = false;
|
|
if (!SystemTray.isSupported()) {
|
|
return;
|
|
}
|
|
try {
|
|
st = SystemTray.getSystemTray();
|
|
ClassLoader cl = this.getClass().getClassLoader();
|
|
ti = new TrayIcon(new javax.swing.ImageIcon(cl.getResource(resourcepath)).getImage());
|
|
} catch (Exception ex) {
|
|
return;
|
|
}
|
|
ti.setImageAutoSize(true);
|
|
this.gc = gc;
|
|
initDiagPopup();
|
|
if (OSFunction.isLinux()) {
|
|
infoToolTip = new InfoTip();
|
|
infoToolTip.setFocusable(false);
|
|
}
|
|
initMouseHandler();
|
|
}
|
|
|
|
private void initMouseHandler() {
|
|
// We need a global mouse listener due to the way TrayIcon intercepts
|
|
// mousemovements but doesnt feed all the events back to us
|
|
awtEventListen = new java.awt.event.AWTEventListener() {
|
|
@Override
|
|
public void eventDispatched(AWTEvent event) {
|
|
// Catch mouse event so we can easily get coords
|
|
final MouseEvent e = (MouseEvent) event;
|
|
if (!(e.getSource() instanceof TrayIcon)) {
|
|
return;
|
|
}
|
|
mouseloc = MouseInfo.getPointerInfo().getLocation();
|
|
switch (e.getID()) {
|
|
case MouseEvent.MOUSE_ENTERED:
|
|
if (infoToolTip == null || trayPopupMenu.isVisible()) {
|
|
return;
|
|
}
|
|
mouseExited = false;
|
|
// Raise tooltip
|
|
Thread t = new Thread(new java.lang.Runnable() {
|
|
@Override
|
|
public void run() {
|
|
try {
|
|
Thread.sleep(1000);
|
|
} catch (InterruptedException ex) {
|
|
}
|
|
if (!trayPopupMenu.isVisible() && !mouseExited) {
|
|
infoToolTip.setLocation(getPlacementPosition(mouseloc, infoToolTip.getWidth(), infoToolTip.getHeight(), true));
|
|
infoToolTip.setVisible(true);
|
|
} else {
|
|
infoToolTip.setVisible(false);
|
|
}
|
|
}
|
|
});
|
|
t.start();
|
|
break;
|
|
|
|
case MouseEvent.MOUSE_EXITED:
|
|
mouseExited = true;
|
|
if (infoToolTip != null) {
|
|
infoToolTip.setVisible(false);
|
|
}
|
|
break;
|
|
|
|
case MouseEvent.MOUSE_RELEASED:
|
|
if (e.getButton() == MouseEvent.BUTTON3) {
|
|
if (infoToolTip != null) {
|
|
infoToolTip.setVisible(false);
|
|
}
|
|
mouseExited = true;
|
|
raisePopupMenu(mouseloc);
|
|
}
|
|
break;
|
|
}
|
|
|
|
}
|
|
};
|
|
Toolkit.getDefaultToolkit().addAWTEventListener(awtEventListen, AWTEvent.MOUSE_EVENT_MASK);
|
|
}
|
|
|
|
/**
|
|
* Initialise our hidden dialog which we use to grab focus so that the
|
|
* popumenu can be closed whenever we click on the desktop
|
|
*/
|
|
private void initDiagPopup() {
|
|
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
|
|
jdiagPopup = new JDialog();
|
|
jdiagPopup.setUndecorated(true);
|
|
jdiagPopup.setMinimumSize(new Dimension(0, 0));
|
|
jdiagPopup.setBounds(new Rectangle(0, 0));
|
|
jdiagPopup.setType(Type.POPUP);
|
|
jdiagPopup.setFocusable(false);
|
|
jdiagPopup.setAutoRequestFocus(false);
|
|
try {
|
|
jdiagPopup.setLocation(new Point(screenSize.width, screenSize.height));
|
|
jdiagPopup.setOpacity(0);
|
|
} catch (Exception ex) {
|
|
}
|
|
jdiagPopup.addComponentListener(new java.awt.event.ComponentListener() {
|
|
|
|
@Override
|
|
public void componentResized(ComponentEvent e) {
|
|
}
|
|
|
|
@Override
|
|
public void componentMoved(ComponentEvent e) {
|
|
}
|
|
|
|
@Override
|
|
public void componentShown(ComponentEvent e) {
|
|
trayPopupMenu.setVisible(true);
|
|
}
|
|
|
|
@Override
|
|
public void componentHidden(ComponentEvent e) {
|
|
}
|
|
});
|
|
}
|
|
|
|
public void dispose() {
|
|
// Remove from system tray
|
|
unload();
|
|
// Cleanup assets that may prevent shutdown
|
|
if (ti != null) {
|
|
for (MouseListener ml : ti.getMouseListeners()) {
|
|
ti.removeMouseListener(ml);
|
|
}
|
|
}
|
|
if (awtEventListen != null) {
|
|
Toolkit.getDefaultToolkit().removeAWTEventListener(awtEventListen);
|
|
awtEventListen = null;
|
|
}
|
|
if (trayPopupMenu != null) {
|
|
for (PopupMenuListener pml : trayPopupMenu.getPopupMenuListeners()) {
|
|
trayPopupMenu.removePopupMenuListener(pml);
|
|
}
|
|
trayPopupMenu = null;
|
|
}
|
|
if (jdiagPopup != null) {
|
|
for (ComponentListener cl : jdiagPopup.getComponentListeners()) {
|
|
jdiagPopup.removeComponentListener(cl);
|
|
}
|
|
jdiagPopup.dispose();
|
|
jdiagPopup = null;
|
|
}
|
|
if (infoToolTip != null) {
|
|
infoToolTip.dispose();
|
|
infoToolTip = null;
|
|
}
|
|
st = null;
|
|
}
|
|
|
|
public void disable() {
|
|
unload();
|
|
st = null;
|
|
}
|
|
|
|
/**
|
|
* Set tray right click popup
|
|
*
|
|
* @param tpm
|
|
*/
|
|
public void setTrayPopupMenu(TrayPopupMenu tpm) {
|
|
trayPopupMenu = tpm;
|
|
if (tpm == null || ti == null) {
|
|
return;
|
|
}
|
|
trayPopupMenu.setFocusable(false);
|
|
// Add popup menu listener to detect when it is hidden
|
|
trayPopupMenu.addPopupMenuListener(new javax.swing.event.PopupMenuListener() {
|
|
|
|
@Override
|
|
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
|
|
}
|
|
|
|
@Override
|
|
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
|
|
jdiagPopup.setVisible(false);
|
|
}
|
|
|
|
@Override
|
|
public void popupMenuCanceled(PopupMenuEvent e) {
|
|
jdiagPopup.setVisible(false);
|
|
}
|
|
});
|
|
trayPopupMenu.setEnabled(true);
|
|
trayPopupMenu.setInvoker(jdiagPopup);
|
|
}
|
|
|
|
/**
|
|
* Open tray popup
|
|
*
|
|
*/
|
|
private void raisePopupMenu(Point mouseloc) {
|
|
if (trayPopupMenu != null) {
|
|
// Set onscreen location of trayPopup
|
|
Point p = getPlacementPosition(mouseloc, trayPopupMenu.getVisibleRect().width, trayPopupMenu.getHeight(), false);
|
|
trayPopupMenu.setLocation(p);
|
|
jdiagPopup.setVisible(true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate onscreen placement position based on mouse pointer position
|
|
*
|
|
* @param boxwidth
|
|
* @param boxheight
|
|
* @param centre
|
|
* @return Point
|
|
*/
|
|
private Point getPlacementPosition(Point coords, int boxwidth, int boxheight, boolean istooltip) {
|
|
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
|
|
Insets screenInsets;
|
|
Point result = new Point();
|
|
double x, y, limit;
|
|
int yoff = 0;
|
|
|
|
if (OSFunction.isLinux()) {
|
|
screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(gc);
|
|
int i = screenInsets.bottom + screenInsets.left + screenInsets.right
|
|
+ screenInsets.top;
|
|
// If all insets are zero then offset them all by 32, bit of a hack
|
|
// for desktops that allow intelligent hiding of tray panel
|
|
if (i == 0) {
|
|
screenInsets = new Insets(32, 32, 32, 32);
|
|
}
|
|
} else {
|
|
screenInsets = new Insets(0, 0, 0, 0);
|
|
}
|
|
|
|
// Calculate X axis positioning
|
|
x = coords.x;
|
|
if (x > (screenSize.getWidth() / 2)) {
|
|
limit = screenSize.width - screenInsets.right;
|
|
if (x + boxwidth > limit) {
|
|
if (screenInsets.right == 0) {
|
|
x -= boxwidth;
|
|
} else {
|
|
x = limit - boxwidth;
|
|
}
|
|
}
|
|
} else {
|
|
limit = screenInsets.left;
|
|
if (x < limit) {
|
|
x = limit;
|
|
}
|
|
}
|
|
|
|
// Calculate Y axis positioning
|
|
y = coords.y;
|
|
if (y > (screenSize.getHeight() / 2)) {
|
|
// Bottom
|
|
if (istooltip && screenInsets.bottom == 0) {
|
|
yoff = (int) ((ti.getSize().getHeight() + 1) / 2);
|
|
}
|
|
} else // Bottom
|
|
{
|
|
if (istooltip && screenInsets.top == 0) {
|
|
yoff = (int) ((ti.getSize().getHeight() + 1) / 2);
|
|
}
|
|
}
|
|
|
|
if (screenInsets.top != 0 && y < screenInsets.top) {
|
|
y += (screenInsets.top - y);
|
|
}
|
|
limit = screenSize.height - screenInsets.bottom;
|
|
if (y + yoff + boxheight > limit) {
|
|
if (yoff > 0) {
|
|
y -= (boxheight + yoff);
|
|
} else if (screenInsets.bottom == 0) {
|
|
y -= (boxheight + yoff);
|
|
} else {
|
|
y = limit - boxheight;
|
|
}
|
|
} else {
|
|
y += yoff;
|
|
}
|
|
result.setLocation(x, y);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Set info tip text
|
|
*
|
|
* @param text
|
|
*/
|
|
public void setInfoTip(String text) {
|
|
if (ti == null) {
|
|
return;
|
|
}
|
|
if (OSFunction.isLinux()) {
|
|
// Check to see if tay icon need reloaded
|
|
if (SystemTray.isSupported() && isLoaded() && st.getTrayIcons().length == 0) {
|
|
try {
|
|
st.add(ti);
|
|
} catch (Exception ex) {
|
|
}
|
|
}
|
|
// Update tooltip (Linux only)
|
|
if (infoToolTip != null) {
|
|
infoToolTip.setText(text);
|
|
if (infoToolTip.isVisible()) {
|
|
infoToolTip.setLocation(getPlacementPosition(mouseloc, infoToolTip.getWidth(), infoToolTip.getHeight(), true));
|
|
}
|
|
}
|
|
} else {
|
|
// Update tooltip (Windows Only)
|
|
ti.setToolTip(text);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display desktop message (Windows only)
|
|
*
|
|
* @param title
|
|
* @param body
|
|
*/
|
|
public void displayMessage(String title, String body) {
|
|
if (ti != null) {
|
|
ti.displayMessage(title, body, TrayIcon.MessageType.INFO);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load tray icon
|
|
*/
|
|
public void load() {
|
|
// I know this looks odd but it is required for onscreen positioning
|
|
// of the tray popup to work
|
|
if (trayPopupMenu != null) {
|
|
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
|
|
trayPopupMenu.setLocation(screenSize.height, screenSize.width);
|
|
trayPopupMenu.setVisible(true);
|
|
trayPopupMenu.setVisible(false);
|
|
}
|
|
// Add this tray icon to system tray
|
|
try {
|
|
if (st != null) {
|
|
st.add(ti);
|
|
loaded = true;
|
|
}
|
|
} catch (Exception ex) {
|
|
loaded = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Unload Tray icon
|
|
*/
|
|
public void unload() {
|
|
// Remove the tray icon from system tray
|
|
if (st != null) {
|
|
st.remove(ti);
|
|
}
|
|
loaded = false;
|
|
}
|
|
|
|
/**
|
|
* Add a mouse listener
|
|
*
|
|
* @param ml
|
|
*/
|
|
public void addMouseListener(MouseListener ml) {
|
|
if (ti != null) {
|
|
ti.addMouseListener(ml);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Is tray icon loaded
|
|
*
|
|
* @return ture if it is
|
|
*/
|
|
public boolean isLoaded() {
|
|
return loaded;
|
|
}
|
|
|
|
public boolean isSupported() {
|
|
return st != null;
|
|
}
|
|
|
|
/**
|
|
* Info tip object
|
|
*/
|
|
private static final class InfoTip extends JWindow {
|
|
|
|
JTextArea jta;
|
|
Pattern p = Pattern.compile("\n");
|
|
|
|
public InfoTip() {
|
|
initInfoTip();
|
|
}
|
|
|
|
private void initInfoTip() {
|
|
setType(Type.POPUP);
|
|
jta = new JTextArea();
|
|
add(jta);
|
|
JToolTip jtt = jta.createToolTip();
|
|
jta.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY));
|
|
jta.setBackground(new Color(jtt.getBackground().getRGB()));
|
|
jta.setForeground(new Color(jtt.getForeground().getRGB()));
|
|
setBackground(jta.getBackground());
|
|
jta.setOpaque(true);
|
|
jta.setEditable(false);
|
|
jta.setLineWrap(true);
|
|
jta.setWrapStyleWord(true);
|
|
jta.setVisible(true);
|
|
setEnabled(true);
|
|
}
|
|
|
|
/**
|
|
* Set info tip text
|
|
*
|
|
* @param text
|
|
*/
|
|
public void setText(String text) {
|
|
String[] strs = p.split(text);
|
|
String largest = "";
|
|
int longest = 0;
|
|
int width;
|
|
for (String s : strs) {
|
|
width = jta.getFontMetrics(jta.getFont()).stringWidth(s);
|
|
if (width > longest) {
|
|
largest = s;
|
|
longest = width;
|
|
}
|
|
}
|
|
jta.setText(" " + text.replace("\n", "\n "));
|
|
width = jta.getFontMetrics(jta.getFont()).stringWidth(largest + "XXX");
|
|
int lineheight = jta.getFontMetrics(jta.getFont()).getHeight() + 1;
|
|
int boxheight = jta.getLineCount() * (lineheight);
|
|
setSize(width, boxheight);
|
|
}
|
|
|
|
}
|
|
|
|
}
|