1 /* 2 * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.lwawt.macosx; 27 28 import java.awt.AWTEvent; 29 import java.awt.Button; 30 import java.awt.Frame; 31 import java.awt.Graphics2D; 32 import java.awt.GraphicsConfiguration; 33 import java.awt.GraphicsDevice; 34 import java.awt.GraphicsEnvironment; 35 import java.awt.Image; 36 import java.awt.MediaTracker; 37 import java.awt.PopupMenu; 38 import java.awt.RenderingHints; 39 import java.awt.Toolkit; 40 import java.awt.Transparency; 41 import java.awt.TrayIcon; 42 import java.awt.event.ActionEvent; 43 import java.awt.event.MouseEvent; 44 import java.awt.geom.Point2D; 45 import java.awt.image.BufferedImage; 46 import java.awt.peer.TrayIconPeer; 47 48 import javax.swing.Icon; 49 import javax.swing.UIManager; 50 51 import sun.awt.SunToolkit; 52 53 import static sun.awt.AWTAccessor.MenuComponentAccessor; 54 import static sun.awt.AWTAccessor.getMenuComponentAccessor; 55 56 public class CTrayIcon extends CFRetainedResource implements TrayIconPeer { 57 private TrayIcon target; 58 private PopupMenu popup; 59 60 // In order to construct MouseEvent object, we need to specify a 61 // Component target. Because TrayIcon isn't Component's subclass, 62 // we use this dummy frame instead 63 private final Frame dummyFrame; 64 65 // A bitmask that indicates what mouse buttons produce MOUSE_CLICKED events 66 // on MOUSE_RELEASE. Click events are only generated if there were no drag 67 // events between MOUSE_PRESSED and MOUSE_RELEASED for particular button 68 private static int mouseClickButtons = 0; 69 70 CTrayIcon(TrayIcon target) { 71 super(0, true); 72 73 this.target = target; 74 this.popup = target.getPopupMenu(); 75 this.dummyFrame = new Frame(); 76 setPtr(createModel()); 77 78 //if no one else is creating the peer. 79 checkAndCreatePopupPeer(); 80 updateImage(); 81 } 82 83 private CPopupMenu checkAndCreatePopupPeer() { 84 CPopupMenu menuPeer = null; 85 if (popup != null) { 86 try { 87 final MenuComponentAccessor acc = getMenuComponentAccessor(); 88 menuPeer = acc.getPeer(popup); 89 if (menuPeer == null) { 90 popup.addNotify(); 91 menuPeer = acc.getPeer(popup); 92 } 93 } catch (Exception e) { 94 e.printStackTrace(); 95 } 96 } 97 return menuPeer; 98 } 99 100 private long createModel() { 101 return nativeCreate(); 102 } 103 104 private native long nativeCreate(); 105 106 //invocation from the AWTTrayIcon.m 107 public long getPopupMenuModel() { 108 PopupMenu newPopup = target.getPopupMenu(); 109 110 if (popup == newPopup) { 111 if (popup == null) { 112 return 0L; 113 } 114 } else { 115 if (newPopup != null) { 116 if (popup != null) { 117 popup.removeNotify(); 118 popup = newPopup; 119 } else { 120 popup = newPopup; 121 } 122 } else { 123 return 0L; 124 } 125 } 126 127 // This method is executed on Appkit, so if ptr is not zero means that, 128 // it is still not deallocated(even if we call NSApp postRunnableEvent) 129 // and sent CFRelease to the native queue 130 return checkAndCreatePopupPeer().ptr; 131 } 132 133 /** 134 * We display tray icon message as a small dialog with OK button. 135 * This is lame, but JDK 1.6 does basically the same. There is a new 136 * kind of window in Lion, NSPopover, so perhaps it could be used it 137 * to implement better looking notifications. 138 */ 139 public void displayMessage(final String caption, final String text, 140 final String messageType) { 141 // obtain icon to show along the message 142 Icon icon = getIconForMessageType(messageType); 143 CImage cimage = null; 144 if (icon != null) { 145 BufferedImage image = scaleIcon(icon, 0.75); 146 cimage = CImage.getCreator().createFromImage(image); 147 } 148 if (cimage != null) { 149 cimage.execute(imagePtr -> { 150 execute(ptr -> nativeShowNotification(ptr, caption, text, 151 imagePtr)); 152 }); 153 } else { 154 execute(ptr -> nativeShowNotification(ptr, caption, text, 0)); 155 } 156 } 157 158 @Override 159 public void dispose() { 160 dummyFrame.dispose(); 161 162 if (popup != null) { 163 popup.removeNotify(); 164 } 165 166 LWCToolkit.targetDisposedPeer(target, this); 167 target = null; 168 169 super.dispose(); 170 } 171 172 @Override 173 public void setToolTip(String tooltip) { 174 execute(ptr -> nativeSetToolTip(ptr, tooltip)); 175 } 176 177 //adds tooltip to the NSStatusBar's NSButton. 178 private native void nativeSetToolTip(long trayIconModel, String tooltip); 179 180 @Override 181 public void showPopupMenu(int x, int y) { 182 //Not used. The popupmenu is shown from the native code. 183 } 184 185 @Override 186 public void updateImage() { 187 Image image = target.getImage(); 188 if (image == null) return; 189 190 MediaTracker tracker = new MediaTracker(new Button("")); 191 tracker.addImage(image, 0); 192 try { 193 tracker.waitForAll(); 194 } catch (InterruptedException ignore) { } 195 196 if (image.getWidth(null) <= 0 || 197 image.getHeight(null) <= 0) 198 { 199 return; 200 } 201 202 CImage cimage = CImage.getCreator().createFromImage(image); 203 boolean imageAutoSize = target.isImageAutoSize(); 204 cimage.execute(imagePtr -> { 205 execute(ptr -> { 206 setNativeImage(ptr, imagePtr, imageAutoSize); 207 }); 208 }); 209 } 210 211 private native void setNativeImage(final long model, final long nsimage, final boolean autosize); 212 213 private void postEvent(final AWTEvent event) { 214 SunToolkit.executeOnEventHandlerThread(target, new Runnable() { 215 public void run() { 216 SunToolkit.postEvent(SunToolkit.targetToAppContext(target), event); 217 } 218 }); 219 } 220 221 //invocation from the AWTTrayIcon.m 222 private void handleMouseEvent(NSEvent nsEvent) { 223 int buttonNumber = nsEvent.getButtonNumber(); 224 final SunToolkit tk = (SunToolkit)Toolkit.getDefaultToolkit(); 225 if ((buttonNumber > 2 && !tk.areExtraMouseButtonsEnabled()) 226 || buttonNumber > tk.getNumberOfButtons() - 1) { 227 return; 228 } 229 230 int jeventType = NSEvent.nsToJavaEventType(nsEvent.getType()); 231 232 int jbuttonNumber = MouseEvent.NOBUTTON; 233 int jclickCount = 0; 234 if (jeventType != MouseEvent.MOUSE_MOVED) { 235 jbuttonNumber = NSEvent.nsToJavaButton(buttonNumber); 236 jclickCount = nsEvent.getClickCount(); 237 } 238 239 int jmodifiers = NSEvent.nsToJavaModifiers( 240 nsEvent.getModifierFlags()); 241 boolean isPopupTrigger = NSEvent.isPopupTrigger(jmodifiers); 242 243 int eventButtonMask = (jbuttonNumber > 0)? 244 MouseEvent.getMaskForButton(jbuttonNumber) : 0; 245 long when = System.currentTimeMillis(); 246 247 if (jeventType == MouseEvent.MOUSE_PRESSED) { 248 mouseClickButtons |= eventButtonMask; 249 } else if (jeventType == MouseEvent.MOUSE_DRAGGED) { 250 mouseClickButtons = 0; 251 } 252 253 // The MouseEvent's coordinates are relative to screen 254 int absX = nsEvent.getAbsX(); 255 int absY = nsEvent.getAbsY(); 256 257 MouseEvent mouseEvent = new MouseEvent(dummyFrame, jeventType, when, 258 jmodifiers, absX, absY, absX, absY, jclickCount, isPopupTrigger, 259 jbuttonNumber); 260 mouseEvent.setSource(target); 261 postEvent(mouseEvent); 262 263 // fire ACTION event 264 if (jeventType == MouseEvent.MOUSE_PRESSED && isPopupTrigger) { 265 final String cmd = target.getActionCommand(); 266 final ActionEvent event = new ActionEvent(target, 267 ActionEvent.ACTION_PERFORMED, cmd); 268 postEvent(event); 269 } 270 271 // synthesize CLICKED event 272 if (jeventType == MouseEvent.MOUSE_RELEASED) { 273 if ((mouseClickButtons & eventButtonMask) != 0) { 274 MouseEvent clickEvent = new MouseEvent(dummyFrame, 275 MouseEvent.MOUSE_CLICKED, when, jmodifiers, absX, absY, 276 absX, absY, jclickCount, isPopupTrigger, jbuttonNumber); 277 clickEvent.setSource(target); 278 postEvent(clickEvent); 279 } 280 281 mouseClickButtons &= ~eventButtonMask; 282 } 283 } 284 285 private native void nativeShowNotification(long trayIconModel, 286 String caption, String text, 287 long nsimage); 288 289 /** 290 * Used by the automated tests. 291 */ 292 private native Point2D nativeGetIconLocation(long trayIconModel); 293 294 /** 295 * Scales an icon using specified scale factor 296 * 297 * @param icon icon to scale 298 * @param scaleFactor scale factor to use 299 * @return scaled icon as BuffedredImage 300 */ 301 private static BufferedImage scaleIcon(Icon icon, double scaleFactor) { 302 if (icon == null) { 303 return null; 304 } 305 306 int w = icon.getIconWidth(); 307 int h = icon.getIconHeight(); 308 309 GraphicsEnvironment ge = 310 GraphicsEnvironment.getLocalGraphicsEnvironment(); 311 GraphicsDevice gd = ge.getDefaultScreenDevice(); 312 GraphicsConfiguration gc = gd.getDefaultConfiguration(); 313 314 // convert icon into image 315 BufferedImage iconImage = gc.createCompatibleImage(w, h, 316 Transparency.TRANSLUCENT); 317 Graphics2D g = iconImage.createGraphics(); 318 icon.paintIcon(null, g, 0, 0); 319 g.dispose(); 320 321 // and scale it nicely 322 int scaledW = (int) (w * scaleFactor); 323 int scaledH = (int) (h * scaleFactor); 324 BufferedImage scaledImage = gc.createCompatibleImage(scaledW, scaledH, 325 Transparency.TRANSLUCENT); 326 g = scaledImage.createGraphics(); 327 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, 328 RenderingHints.VALUE_INTERPOLATION_BILINEAR); 329 g.drawImage(iconImage, 0, 0, scaledW, scaledH, null); 330 g.dispose(); 331 332 return scaledImage; 333 } 334 335 336 /** 337 * Gets Aqua icon used in message dialog. 338 */ 339 private static Icon getIconForMessageType(String messageType) { 340 if (messageType.equals("ERROR")) { 341 return UIManager.getIcon("OptionPane.errorIcon"); 342 } else if (messageType.equals("WARNING")) { 343 return UIManager.getIcon("OptionPane.warningIcon"); 344 } else { 345 // this is just an application icon 346 return UIManager.getIcon("OptionPane.informationIcon"); 347 } 348 } 349 } 350