2015-11-09 16:13:57 +01:00
|
|
|
/*
|
|
|
|
* Copyright 2014 dorkbox, llc
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
2016-02-13 15:06:19 +01:00
|
|
|
package dorkbox.systemTray.swing;
|
2014-11-03 02:11:03 +01:00
|
|
|
|
2016-10-09 22:40:28 +02:00
|
|
|
import java.awt.Dimension;
|
2016-10-03 20:13:00 +02:00
|
|
|
import java.awt.Frame;
|
2016-10-08 17:41:21 +02:00
|
|
|
import java.awt.Image;
|
2016-10-09 22:40:28 +02:00
|
|
|
import java.awt.Point;
|
|
|
|
import java.awt.Rectangle;
|
2016-10-03 20:13:00 +02:00
|
|
|
import java.awt.event.WindowEvent;
|
|
|
|
import java.awt.event.WindowFocusListener;
|
2016-10-08 17:41:21 +02:00
|
|
|
import java.io.File;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.lang.reflect.Method;
|
2015-06-28 01:47:02 +02:00
|
|
|
|
2016-10-08 17:41:21 +02:00
|
|
|
import javax.imageio.ImageIO;
|
|
|
|
import javax.swing.ImageIcon;
|
2016-10-03 20:13:00 +02:00
|
|
|
import javax.swing.JDialog;
|
2016-09-28 17:20:13 +02:00
|
|
|
import javax.swing.JPopupMenu;
|
2016-09-28 18:35:25 +02:00
|
|
|
import javax.swing.border.EmptyBorder;
|
2016-10-08 17:41:21 +02:00
|
|
|
import javax.swing.event.PopupMenuEvent;
|
|
|
|
import javax.swing.event.PopupMenuListener;
|
|
|
|
|
|
|
|
import dorkbox.systemTray.SystemTray;
|
|
|
|
import dorkbox.util.OS;
|
2016-10-09 22:40:28 +02:00
|
|
|
import dorkbox.util.ScreenUtil;
|
2016-09-28 17:20:13 +02:00
|
|
|
|
2016-10-03 20:13:00 +02:00
|
|
|
/**
|
2016-10-08 21:53:43 +02:00
|
|
|
* This custom popup is required if we want to be able to show images on the menu,
|
2016-10-09 16:28:32 +02:00
|
|
|
*
|
|
|
|
* This is our "golden standard" since we have 100% control over it.
|
2016-10-03 20:13:00 +02:00
|
|
|
*/
|
2016-10-09 22:40:28 +02:00
|
|
|
class TrayPopup extends JPopupMenu {
|
2014-11-24 17:40:06 +01:00
|
|
|
private static final long serialVersionUID = 1L;
|
2014-11-03 02:11:03 +01:00
|
|
|
|
2016-10-08 21:53:43 +02:00
|
|
|
// NOTE: we can use the "hidden dialog" focus window trick...
|
2016-10-03 20:13:00 +02:00
|
|
|
private JDialog hiddenDialog;
|
2016-10-08 17:41:21 +02:00
|
|
|
private volatile File iconFile;
|
2016-10-09 22:40:28 +02:00
|
|
|
private volatile Runnable runnable;
|
2016-02-21 00:56:14 +01:00
|
|
|
|
2016-10-08 17:41:21 +02:00
|
|
|
@SuppressWarnings("unchecked")
|
2016-10-09 22:40:28 +02:00
|
|
|
TrayPopup() {
|
2014-11-24 17:40:06 +01:00
|
|
|
super();
|
2015-01-23 02:52:09 +01:00
|
|
|
setFocusable(true);
|
2016-09-28 18:35:25 +02:00
|
|
|
// setBorder(new BorderUIResource.EmptyBorderUIResource(0, 0, 0, 0)); // borderUI resource border type will get changed!
|
|
|
|
setBorder(new EmptyBorder(1, 1, 1, 1));
|
2014-11-03 02:11:03 +01:00
|
|
|
|
2016-09-29 13:07:58 +02:00
|
|
|
|
2016-10-08 17:41:21 +02:00
|
|
|
// Initialize the hidden dialog as a headless, title-less dialog window
|
2016-10-08 21:53:43 +02:00
|
|
|
hiddenDialog = new JDialog((Frame)null, "Tray menu");
|
|
|
|
hiddenDialog.setUndecorated(true);
|
|
|
|
hiddenDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
|
|
|
|
hiddenDialog.setAlwaysOnTop(true);
|
2016-10-08 17:41:21 +02:00
|
|
|
|
|
|
|
// on Linux, the following two entries will **MOST OF THE TIME** prevent the hidden dialog from showing in the task-bar
|
|
|
|
// on MacOS, you need "special permission" to not have a hidden dialog show on the dock.
|
2016-10-08 21:53:43 +02:00
|
|
|
hiddenDialog.getContentPane().setLayout(null);
|
2016-10-08 17:41:21 +02:00
|
|
|
|
2016-10-08 21:53:43 +02:00
|
|
|
// this is java 1.7, so we have to use reflection. It's not critical for this to be set, but it helps hide the title in the taskbar
|
|
|
|
// hiddenDialog.setType(Window.Type.POPUP);
|
2016-10-08 17:41:21 +02:00
|
|
|
if (OS.javaVersion >= 7) {
|
|
|
|
try {
|
2016-10-08 21:53:43 +02:00
|
|
|
Class<? extends JDialog> hiddenDialogClass = hiddenDialog.getClass();
|
2016-10-08 17:41:21 +02:00
|
|
|
Method[] methods = hiddenDialogClass.getMethods();
|
|
|
|
for (Method method : methods) {
|
|
|
|
if (method.getName()
|
|
|
|
.equals("setType")) {
|
|
|
|
|
|
|
|
Class<Enum> cl = (Class<Enum>) Class.forName("java.awt.Window$Type");
|
2016-10-08 21:53:43 +02:00
|
|
|
method.invoke(hiddenDialog, Enum.valueOf(cl, "POPUP"));
|
2016-10-08 17:41:21 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
|
|
SystemTray.logger.error("Error setting the tray popup menu type. The parent window might show on the task bar.");
|
|
|
|
}
|
|
|
|
}
|
2016-02-21 00:56:14 +01:00
|
|
|
|
2016-10-08 21:53:43 +02:00
|
|
|
hiddenDialog.pack();
|
|
|
|
hiddenDialog.setBounds(0,0,0,0);
|
2016-10-08 17:41:21 +02:00
|
|
|
|
|
|
|
addPopupMenuListener(new PopupMenuListener() {
|
|
|
|
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
|
|
|
|
}
|
|
|
|
|
|
|
|
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
|
|
|
|
hiddenDialog.setVisible(false);
|
|
|
|
hiddenDialog.toBack();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void popupMenuCanceled(PopupMenuEvent e) {
|
|
|
|
}
|
|
|
|
});
|
2016-09-29 13:07:58 +02:00
|
|
|
|
2016-10-03 20:13:00 +02:00
|
|
|
// Add the window focus listener to the hidden dialog
|
2016-10-08 21:53:43 +02:00
|
|
|
hiddenDialog.addWindowFocusListener(new WindowFocusListener() {
|
2014-11-24 17:40:06 +01:00
|
|
|
@Override
|
2016-10-03 20:13:00 +02:00
|
|
|
public void windowLostFocus (WindowEvent we ) {
|
2016-10-09 22:40:28 +02:00
|
|
|
TrayPopup.this.setVisible(false);
|
2014-11-03 02:11:03 +01:00
|
|
|
}
|
2016-10-03 20:13:00 +02:00
|
|
|
@Override
|
2016-10-08 17:41:21 +02:00
|
|
|
public void windowGainedFocus (WindowEvent we) {
|
|
|
|
}
|
2014-11-03 02:11:03 +01:00
|
|
|
});
|
2015-01-23 02:52:09 +01:00
|
|
|
}
|
2014-11-24 17:40:06 +01:00
|
|
|
|
2016-10-08 17:41:21 +02:00
|
|
|
/**
|
2016-10-09 20:20:23 +02:00
|
|
|
* Sets the image for the title-bar, so IF it shows in the task-bar, it will have the corresponding image as the SystemTray image
|
2016-10-08 17:41:21 +02:00
|
|
|
*/
|
2016-10-09 20:20:23 +02:00
|
|
|
void setTitleBarImage(final File imageFile) {
|
|
|
|
if (this.iconFile == null || !this.iconFile.equals(imageFile)) {
|
|
|
|
this.iconFile = imageFile;
|
2016-10-08 17:41:21 +02:00
|
|
|
|
|
|
|
try {
|
2016-10-09 20:20:23 +02:00
|
|
|
Image image = new ImageIcon(ImageIO.read(imageFile)).getImage();
|
2016-10-08 17:41:21 +02:00
|
|
|
image.flush();
|
|
|
|
|
|
|
|
// we set the dialog window to have the same icon as what is on the system tray
|
2016-10-08 21:53:43 +02:00
|
|
|
hiddenDialog.setIconImage(image);
|
2016-10-08 17:41:21 +02:00
|
|
|
} catch (IOException e) {
|
2016-10-09 20:20:23 +02:00
|
|
|
SystemTray.logger.error("Error setting the title-bar image for the popup menu task tray dialog");
|
2016-10-08 17:41:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-09 22:40:28 +02:00
|
|
|
void setOnHideRunnable(final Runnable runnable) {
|
|
|
|
this.runnable = runnable;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2016-10-09 20:20:23 +02:00
|
|
|
public
|
2016-10-09 22:40:28 +02:00
|
|
|
void setVisible(final boolean b) {
|
|
|
|
if (!b) {
|
|
|
|
Runnable r = this.runnable;
|
|
|
|
if (r != null) {
|
|
|
|
r.run();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
super.setVisible(b);
|
|
|
|
}
|
|
|
|
|
|
|
|
void close() {
|
|
|
|
hiddenDialog.setVisible(false);
|
|
|
|
hiddenDialog.dispatchEvent(new WindowEvent(hiddenDialog, WindowEvent.WINDOW_CLOSING));
|
|
|
|
}
|
|
|
|
|
|
|
|
void doShow(final Point point, final int offset) {
|
|
|
|
|
|
|
|
Dimension size = getPreferredSize();
|
|
|
|
|
|
|
|
Rectangle bounds = ScreenUtil.getScreenBoundsAt(point);
|
|
|
|
|
|
|
|
int x = point.x;
|
|
|
|
int y = point.y;
|
|
|
|
|
|
|
|
if (y < bounds.y) {
|
|
|
|
y = bounds.y;
|
|
|
|
}
|
|
|
|
else if (y + size.height > bounds.y + bounds.height) {
|
|
|
|
// our menu cannot have the top-edge snap to the mouse
|
|
|
|
// so we make the bottom-edge snap to the mouse
|
|
|
|
y -= size.height; // snap to edge of mouse
|
|
|
|
}
|
|
|
|
|
|
|
|
if (x < bounds.x) {
|
|
|
|
x = bounds.x;
|
|
|
|
|
|
|
|
x -= offset; // display over the stupid appindicator menu (which has to show, this is a major hack!)
|
|
|
|
}
|
|
|
|
else if (x + size.width > bounds.x + bounds.width) {
|
|
|
|
// our menu cannot have the left-edge snap to the mouse so we make the right-edge snap to the mouse
|
|
|
|
x -= size.width; // snap right edge of menu to mouse
|
|
|
|
|
|
|
|
x += offset; // display over the stupid appindicator menu (which has to show, this is a major hack!)
|
|
|
|
} else {
|
|
|
|
x -= offset; // display over the stupid appindicator menu (which has to show, this is a major hack!)
|
|
|
|
}
|
|
|
|
|
|
|
|
System.err.println("SHOWING POPUP @" + x + "," + y);
|
|
|
|
|
|
|
|
|
2016-10-08 17:41:21 +02:00
|
|
|
// critical to get the keyboard listeners working for the popup menu
|
2016-10-08 21:53:43 +02:00
|
|
|
setInvoker(hiddenDialog.getContentPane());
|
2016-10-08 17:41:21 +02:00
|
|
|
|
2016-10-08 21:53:43 +02:00
|
|
|
hiddenDialog.setLocation(x, y);
|
|
|
|
hiddenDialog.setVisible(true);
|
2016-02-21 00:56:14 +01:00
|
|
|
|
2016-10-08 17:41:21 +02:00
|
|
|
setLocation(x, y);
|
|
|
|
setVisible(true);
|
|
|
|
requestFocusInWindow();
|
2014-11-24 17:40:06 +01:00
|
|
|
}
|
2016-10-03 23:12:00 +02:00
|
|
|
|
2016-10-09 22:40:28 +02:00
|
|
|
|
2014-11-16 22:01:27 +01:00
|
|
|
}
|