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

/*
* 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);
}
}
}