/* * 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 * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.lwawt.macosx; 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.image.ImageObserver; import java.awt.peer.TrayIconPeer; import javax.swing.Icon; import javax.swing.UIManager; 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; // In order to construct MouseEvent object, we need to specify a // Component target. Because TrayIcon isn't Component's subclass, // we use this dummy frame instead private final Frame dummyFrame; IconObserver observer = new IconObserver(); // A bitmask that indicates what mouse buttons produce MOUSE_CLICKED events // on MOUSE_RELEASE. Click events are only generated if there were no drag // events between MOUSE_PRESSED and MOUSE_RELEASED for particular button private static int mouseClickButtons = 0; CTrayIcon(TrayIcon target) { super(0, true); this.target = target; this.popup = target.getPopupMenu(); this.dummyFrame = new Frame(); setPtr(createModel()); //if no one else is creating the peer. checkAndCreatePopupPeer(); updateImage(); } private CPopupMenu checkAndCreatePopupPeer() { CPopupMenu menuPeer = null; if (popup != null) { try { final MenuComponentAccessor acc = getMenuComponentAccessor(); menuPeer = acc.getPeer(popup); if (menuPeer == null) { popup.addNotify(); menuPeer = acc.getPeer(popup); } } catch (Exception e) { e.printStackTrace(); } } return menuPeer; } private long createModel() { return nativeCreate(); } private native long nativeCreate(); //invocation from the AWTTrayIcon.m public long getPopupMenuModel() { PopupMenu newPopup = target.getPopupMenu(); if (popup == newPopup) { if (popup == null) { return 0L; } } else { if (newPopup != null) { if (popup != null) { popup.removeNotify(); popup = newPopup; } else { popup = newPopup; } } else { return 0L; } } // This method is executed on Appkit, so if ptr is not zero means that, // it is still not deallocated(even if we call NSApp postRunnableEvent) // and sent CFRelease to the native queue return checkAndCreatePopupPeer().ptr; } /** * We display tray icon message as a small dialog with OK button. * This is lame, but JDK 1.6 does basically the same. There is a new * kind of window in Lion, NSPopover, so perhaps it could be used it * to implement better looking notifications. */ public void displayMessage(final String caption, final String text, final String 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, null); } if (cimage != null) { cimage.execute(imagePtr -> { execute(ptr -> nativeShowNotification(ptr, caption, text, imagePtr)); }); } else { execute(ptr -> nativeShowNotification(ptr, caption, text, 0)); } } @Override public void dispose() { dummyFrame.dispose(); if (popup != null) { popup.removeNotify(); } LWCToolkit.targetDisposedPeer(target, this); target = null; super.dispose(); } @Override public void setToolTip(String tooltip) { execute(ptr -> nativeSetToolTip(ptr, tooltip)); } //adds tooltip to the NSStatusBar's NSButton. private native void nativeSetToolTip(long trayIconModel, String tooltip); @Override public void showPopupMenu(int x, int y) { //Not used. The popupmenu is shown from the native code. } @Override public void updateImage() { Image image = target.getImage(); if (image != null) { updateNativeImage(image); } } void updateNativeImage(Image image) { MediaTracker tracker = new MediaTracker(new Button("")); tracker.addImage(image, 0); try { tracker.waitForAll(); } catch (InterruptedException ignore) { } if (image.getWidth(null) <= 0 || image.getHeight(null) <= 0) { return; } CImage cimage = CImage.getCreator().createFromImage(image, observer); boolean imageAutoSize = target.isImageAutoSize(); if (cimage != null) { cimage.execute(imagePtr -> { execute(ptr -> { setNativeImage(ptr, imagePtr, imageAutoSize); }); }); } } private native void setNativeImage(final long model, final long nsimage, final boolean autosize); private void postEvent(final AWTEvent event) { SunToolkit.executeOnEventHandlerThread(target, new Runnable() { public void run() { SunToolkit.postEvent(SunToolkit.targetToAppContext(target), event); } }); } //invocation from the AWTTrayIcon.m private void handleMouseEvent(NSEvent nsEvent) { int buttonNumber = nsEvent.getButtonNumber(); final SunToolkit tk = (SunToolkit)Toolkit.getDefaultToolkit(); if ((buttonNumber > 2 && !tk.areExtraMouseButtonsEnabled()) || buttonNumber > tk.getNumberOfButtons() - 1) { return; } int jeventType = NSEvent.nsToJavaEventType(nsEvent.getType()); int jbuttonNumber = MouseEvent.NOBUTTON; int jclickCount = 0; if (jeventType != MouseEvent.MOUSE_MOVED) { jbuttonNumber = NSEvent.nsToJavaButton(buttonNumber); jclickCount = nsEvent.getClickCount(); } int jmodifiers = NSEvent.nsToJavaModifiers( nsEvent.getModifierFlags()); boolean isPopupTrigger = NSEvent.isPopupTrigger(jmodifiers); int eventButtonMask = (jbuttonNumber > 0)? MouseEvent.getMaskForButton(jbuttonNumber) : 0; long when = System.currentTimeMillis(); if (jeventType == MouseEvent.MOUSE_PRESSED) { mouseClickButtons |= eventButtonMask; } else if (jeventType == MouseEvent.MOUSE_DRAGGED) { mouseClickButtons = 0; } // The MouseEvent's coordinates are relative to screen int absX = nsEvent.getAbsX(); int absY = nsEvent.getAbsY(); MouseEvent mouseEvent = new MouseEvent(dummyFrame, jeventType, when, jmodifiers, absX, absY, absX, absY, jclickCount, isPopupTrigger, jbuttonNumber); mouseEvent.setSource(target); postEvent(mouseEvent); // fire ACTION event if (jeventType == MouseEvent.MOUSE_PRESSED && isPopupTrigger) { final String cmd = target.getActionCommand(); final ActionEvent event = new ActionEvent(target, ActionEvent.ACTION_PERFORMED, cmd); postEvent(event); } // synthesize CLICKED event if (jeventType == MouseEvent.MOUSE_RELEASED) { if ((mouseClickButtons & eventButtonMask) != 0) { MouseEvent clickEvent = new MouseEvent(dummyFrame, MouseEvent.MOUSE_CLICKED, when, jmodifiers, absX, absY, absX, absY, jclickCount, isPopupTrigger, jbuttonNumber); clickEvent.setSource(target); postEvent(clickEvent); } mouseClickButtons &= ~eventButtonMask; } } private native void nativeShowNotification(long trayIconModel, String caption, String text, long nsimage); /** * Used by the automated tests. */ private native Point2D nativeGetIconLocation(long trayIconModel); /** * Scales an icon using specified scale factor * * @param icon icon to scale * @param scaleFactor scale factor to use * @return scaled icon as BuffedredImage */ private static BufferedImage scaleIcon(Icon icon, double scaleFactor) { if (icon == null) { return null; } int w = icon.getIconWidth(); int h = icon.getIconHeight(); GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice gd = ge.getDefaultScreenDevice(); GraphicsConfiguration gc = gd.getDefaultConfiguration(); // convert icon into image BufferedImage iconImage = gc.createCompatibleImage(w, h, Transparency.TRANSLUCENT); Graphics2D g = iconImage.createGraphics(); icon.paintIcon(null, g, 0, 0); g.dispose(); // and scale it nicely int scaledW = (int) (w * scaleFactor); int scaledH = (int) (h * scaleFactor); BufferedImage scaledImage = gc.createCompatibleImage(scaledW, scaledH, Transparency.TRANSLUCENT); g = scaledImage.createGraphics(); g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g.drawImage(iconImage, 0, 0, scaledW, scaledH, null); g.dispose(); return scaledImage; } /** * Gets Aqua icon used in message dialog. */ private static Icon getIconForMessageType(String messageType) { if (messageType.equals("ERROR")) { return UIManager.getIcon("OptionPane.errorIcon"); } else if (messageType.equals("WARNING")) { return UIManager.getIcon("OptionPane.warningIcon"); } else { // this is just an application icon return UIManager.getIcon("OptionPane.informationIcon"); } } class IconObserver implements ImageObserver { @Override public boolean imageUpdate(Image image, int flags, int x, int y, int width, int height) { if (target == null || image != target.getImage()) //if the image has been changed { return false; } if ((flags & (ImageObserver.FRAMEBITS | ImageObserver.ALLBITS | ImageObserver.WIDTH | ImageObserver.HEIGHT)) != 0) { SunToolkit.executeOnEventHandlerThread(target, new Runnable() { public void run() { updateNativeImage(image); } }); } return (flags & ImageObserver.ALLBITS) == 0; } } }