8187639: TrayIcon is not properly supported on macOS in multi-screen environment
authorserb
Thu, 26 Oct 2017 19:45:37 -0700
changeset 47508 33da1153954c
parent 47507 5a270d2dfa5d
child 47509 7d0f05e7c7f5
8187639: TrayIcon is not properly supported on macOS in multi-screen environment Reviewed-by: prr, ssadetsky
src/java.desktop/macosx/classes/sun/lwawt/macosx/CTrayIcon.java
src/java.desktop/macosx/native/libawt_lwawt/awt/CTrayIcon.m
src/java.desktop/macosx/native/libosxapp/NSApplicationAWT.h
src/java.desktop/macosx/native/libosxapp/NSApplicationAWT.m
--- 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