8187639: TrayIcon is not properly supported on macOS in multi-screen environment
Reviewed-by: prr, ssadetsky
--- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CTrayIcon.java Wed Oct 25 13:11:07 2017 -0700
+++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CTrayIcon.java Thu Oct 26 19:45:37 2017 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -25,26 +25,37 @@
package sun.lwawt.macosx;
-import sun.awt.AWTAccessor;
-import sun.awt.SunToolkit;
-
-import javax.swing.*;
-import java.awt.*;
-import java.awt.event.*;
+import java.awt.AWTEvent;
+import java.awt.Button;
+import java.awt.Frame;
+import java.awt.Graphics2D;
+import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsDevice;
+import java.awt.GraphicsEnvironment;
+import java.awt.Image;
+import java.awt.MediaTracker;
+import java.awt.PopupMenu;
+import java.awt.RenderingHints;
+import java.awt.Toolkit;
+import java.awt.Transparency;
+import java.awt.TrayIcon;
+import java.awt.event.ActionEvent;
+import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.awt.peer.TrayIconPeer;
-import java.beans.PropertyChangeEvent;
-import java.beans.PropertyChangeListener;
-import java.util.concurrent.atomic.AtomicReference;
+
+import javax.swing.Icon;
+import javax.swing.UIManager;
-import static sun.awt.AWTAccessor.*;
+import sun.awt.SunToolkit;
+
+import static sun.awt.AWTAccessor.MenuComponentAccessor;
+import static sun.awt.AWTAccessor.getMenuComponentAccessor;
public class CTrayIcon extends CFRetainedResource implements TrayIconPeer {
private TrayIcon target;
private PopupMenu popup;
- private JDialog messageDialog;
- private DialogEventHandler handler;
// In order to construct MouseEvent object, we need to specify a
// Component target. Because TrayIcon isn't Component's subclass,
@@ -59,8 +70,6 @@
CTrayIcon(TrayIcon target) {
super(0, true);
- this.messageDialog = null;
- this.handler = null;
this.target = target;
this.popup = target.getPopupMenu();
this.dummyFrame = new Frame();
@@ -129,28 +138,25 @@
*/
public void displayMessage(final String caption, final String text,
final String messageType) {
-
- if (SwingUtilities.isEventDispatchThread()) {
- displayMessageOnEDT(caption, text, messageType);
+ // obtain icon to show along the message
+ Icon icon = getIconForMessageType(messageType);
+ CImage cimage = null;
+ if (icon != null) {
+ BufferedImage image = scaleIcon(icon, 0.75);
+ cimage = CImage.getCreator().createFromImage(image);
+ }
+ if (cimage != null) {
+ cimage.execute(imagePtr -> {
+ execute(ptr -> nativeShowNotification(ptr, caption, text,
+ imagePtr));
+ });
} else {
- try {
- SwingUtilities.invokeAndWait(new Runnable() {
- public void run() {
- displayMessageOnEDT(caption, text, messageType);
- }
- });
- } catch (Exception e) {
- throw new AssertionError(e);
- }
+ execute(ptr -> nativeShowNotification(ptr, caption, text, 0));
}
}
@Override
public void dispose() {
- if (messageDialog != null) {
- disposeMessageDialog();
- }
-
dummyFrame.dispose();
if (popup != null) {
@@ -276,154 +282,14 @@
}
}
- private native Point2D nativeGetIconLocation(long trayIconModel);
-
- public void displayMessageOnEDT(String caption, String text,
- String messageType) {
- if (messageDialog != null) {
- disposeMessageDialog();
- }
-
- // obtain icon to show along the message
- Icon icon = getIconForMessageType(messageType);
- if (icon != null) {
- icon = new ImageIcon(scaleIcon(icon, 0.75));
- }
-
- // We want the message dialog text area to be about 1/8 of the screen
- // size. There is nothing special about this value, it's just makes the
- // message dialog to look nice
- Dimension screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
- int textWidth = screenSize.width / 8;
-
- // create dialog to show
- messageDialog = createMessageDialog(caption, text, textWidth, icon);
-
- // finally, show the dialog to user
- showMessageDialog();
- }
+ private native void nativeShowNotification(long trayIconModel,
+ String caption, String text,
+ long nsimage);
/**
- * Creates dialog window used to display the message
+ * Used by the automated tests.
*/
- private JDialog createMessageDialog(String caption, String text,
- int textWidth, Icon icon) {
- JDialog dialog;
- handler = new DialogEventHandler();
-
- JTextArea captionArea = null;
- if (caption != null) {
- captionArea = createTextArea(caption, textWidth, false, true);
- }
-
- JTextArea textArea = null;
- if (text != null){
- textArea = createTextArea(text, textWidth, true, false);
- }
-
- Object[] panels = null;
- if (captionArea != null) {
- if (textArea != null) {
- panels = new Object[] {captionArea, new JLabel(), textArea};
- } else {
- panels = new Object[] {captionArea};
- }
- } else {
- if (textArea != null) {
- panels = new Object[] {textArea};
- }
- }
-
- // We want message dialog with small title bar. There is a client
- // property property that does it, however, it must be set before
- // dialog's native window is created. This is why we create option
- // pane and dialog separately
- final JOptionPane op = new JOptionPane(panels);
- op.setIcon(icon);
- op.addPropertyChangeListener(handler);
-
- // Make Ok button small. Most likely won't work for L&F other then Aqua
- try {
- JPanel buttonPanel = (JPanel)op.getComponent(1);
- JButton ok = (JButton)buttonPanel.getComponent(0);
- ok.putClientProperty("JComponent.sizeVariant", "small");
- } catch (Throwable t) {
- // do nothing, we tried and failed, no big deal
- }
-
- dialog = new JDialog((Dialog) null);
- JRootPane rp = dialog.getRootPane();
-
- // gives us dialog window with small title bar and not zoomable
- rp.putClientProperty(CPlatformWindow.WINDOW_STYLE, "small");
- rp.putClientProperty(CPlatformWindow.WINDOW_ZOOMABLE, "false");
-
- dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
- dialog.setModal(false);
- dialog.setModalExclusionType(Dialog.ModalExclusionType.TOOLKIT_EXCLUDE);
- dialog.setAlwaysOnTop(true);
- dialog.setAutoRequestFocus(false);
- dialog.setResizable(false);
- dialog.setContentPane(op);
-
- dialog.addWindowListener(handler);
-
- // suppress security warning for untrusted windows
- AWTAccessor.getWindowAccessor().setTrayIconWindow(dialog, true);
-
- dialog.pack();
-
- return dialog;
- }
-
- private void showMessageDialog() {
-
- Dimension screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
- AtomicReference<Point2D> ref = new AtomicReference<>();
- execute(ptr -> {
- ref.set(nativeGetIconLocation(ptr));
- });
- Point2D iconLoc = ref.get();
- if (iconLoc == null) {
- return;
- }
-
- int dialogY = (int)iconLoc.getY();
- int dialogX = (int)iconLoc.getX();
- if (dialogX + messageDialog.getWidth() > screenSize.width) {
- dialogX = screenSize.width - messageDialog.getWidth();
- }
-
- messageDialog.setLocation(dialogX, dialogY);
- messageDialog.setVisible(true);
- }
-
- private void disposeMessageDialog() {
- if (SwingUtilities.isEventDispatchThread()) {
- disposeMessageDialogOnEDT();
- } else {
- try {
- SwingUtilities.invokeAndWait(new Runnable() {
- public void run() {
- disposeMessageDialogOnEDT();
- }
- });
- } catch (Exception e) {
- throw new AssertionError(e);
- }
- }
- }
-
- private void disposeMessageDialogOnEDT() {
- if (messageDialog != null) {
- messageDialog.removeWindowListener(handler);
- messageDialog.removePropertyChangeListener(handler);
- messageDialog.dispose();
-
- messageDialog = null;
- handler = null;
- }
- }
+ private native Point2D nativeGetIconLocation(long trayIconModel);
/**
* Scales an icon using specified scale factor
@@ -480,56 +346,5 @@
return UIManager.getIcon("OptionPane.informationIcon");
}
}
-
- private static JTextArea createTextArea(String text, int width,
- boolean isSmall, boolean isBold) {
- JTextArea textArea = new JTextArea(text);
-
- textArea.setLineWrap(true);
- textArea.setWrapStyleWord(true);
- textArea.setEditable(false);
- textArea.setFocusable(false);
- textArea.setBorder(null);
- textArea.setBackground(new JLabel().getBackground());
-
- if (isSmall) {
- textArea.putClientProperty("JComponent.sizeVariant", "small");
- }
-
- if (isBold) {
- Font font = textArea.getFont();
- Font boldFont = new Font(font.getName(), Font.BOLD, font.getSize());
- textArea.setFont(boldFont);
- }
-
- textArea.setSize(width, 1);
-
- return textArea;
- }
-
- /**
- * Implements all the Listeners needed by message dialog
- */
- private final class DialogEventHandler extends WindowAdapter
- implements PropertyChangeListener {
-
- public void windowClosing(WindowEvent we) {
- disposeMessageDialog();
- }
-
- public void propertyChange(PropertyChangeEvent e) {
- if (messageDialog == null) {
- return;
- }
-
- String prop = e.getPropertyName();
- Container cp = messageDialog.getContentPane();
-
- if (messageDialog.isVisible() && e.getSource() == cp &&
- (prop.equals(JOptionPane.VALUE_PROPERTY))) {
- disposeMessageDialog();
- }
- }
- }
}
--- a/src/java.desktop/macosx/native/libawt_lwawt/awt/CTrayIcon.m Wed Oct 25 13:11:07 2017 -0700
+++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/CTrayIcon.m Thu Oct 26 19:45:37 2017 -0700
@@ -409,3 +409,28 @@
return jpt;
}
+
+JNIEXPORT void JNICALL
+Java_sun_lwawt_macosx_CTrayIcon_nativeShowNotification
+(JNIEnv *env, jobject self, jlong model, jobject jcaption, jobject jtext,
+ long nsimage) {
+JNF_COCOA_ENTER(env);
+
+ AWTTrayIcon *icon = jlong_to_ptr(model);
+ NSString *caption = JNFJavaToNSString(env, jcaption);
+ NSString *text = JNFJavaToNSString(env, jtext);
+ NSImage * contentImage = jlong_to_ptr(nsimage);
+
+ [ThreadUtilities performOnMainThreadWaiting:NO block:^(){
+ NSUserNotification *notification = [[NSUserNotification alloc] init];
+ notification.title = caption;
+ notification.informativeText = text;
+ notification.contentImage = contentImage;
+ notification.soundName = NSUserNotificationDefaultSoundName;
+
+ [[NSUserNotificationCenter defaultUserNotificationCenter]
+ deliverNotification:notification];
+ }];
+
+JNF_COCOA_EXIT(env);
+}
--- a/src/java.desktop/macosx/native/libosxapp/NSApplicationAWT.h Wed Oct 25 13:11:07 2017 -0700
+++ b/src/java.desktop/macosx/native/libosxapp/NSApplicationAWT.h Thu Oct 26 19:45:37 2017 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -32,7 +32,7 @@
#import <Cocoa/Cocoa.h>
#import <JavaNativeFoundation/JavaNativeFoundation.h>
-@interface NSApplicationAWT : NSApplication {
+@interface NSApplicationAWT : NSApplication <NSUserNotificationCenterDelegate> {
NSString *fApplicationName;
NSWindow *eventTransparentWindow;
NSTimeInterval dummyEventTimestamp;
--- a/src/java.desktop/macosx/native/libosxapp/NSApplicationAWT.m Wed Oct 25 13:11:07 2017 -0700
+++ b/src/java.desktop/macosx/native/libosxapp/NSApplicationAWT.m Thu Oct 26 19:45:37 2017 -0700
@@ -77,6 +77,8 @@
- (void)dealloc
{
+ [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:nil];
+
[fApplicationName release];
fApplicationName = nil;
@@ -138,10 +140,18 @@
[super finishLaunching];
+ [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self];
+
// inform any interested parties that the AWT has arrived and is pumping
[[NSNotificationCenter defaultCenter] postNotificationName:JNFRunLoopDidStartNotification object:self];
}
+- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center
+ shouldPresentNotification:(NSUserNotification *)notification
+{
+ return YES; // We always show notifications to the user
+}
+
- (void) registerWithProcessManager
{
// Headless: NO