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 #import "jni_util.h" 27 28 #import <AppKit/AppKit.h> 29 #import <JavaNativeFoundation/JavaNativeFoundation.h> 30 31 #import "CTrayIcon.h" 32 #import "ThreadUtilities.h" 33 #include "GeomUtilities.h" 34 #import "LWCToolkit.h" 35 36 #define kImageInset 4.0 37 38 /** 39 * If the image of the specified size won't fit into the status bar, 40 * then scale it down proprtionally. Otherwise, leave it as is. 41 */ 42 static NSSize ScaledImageSizeForStatusBar(NSSize imageSize, BOOL autosize) { 43 NSRect imageRect = NSMakeRect(0.0, 0.0, imageSize.width, imageSize.height); 44 45 // There is a black line at the bottom of the status bar 46 // that we don't want to cover with image pixels. 47 CGFloat desiredSize = [[NSStatusBar systemStatusBar] thickness] - 1.0; 48 if (autosize) { 49 imageRect.size.width = desiredSize; 50 imageRect.size.height = desiredSize; 51 } else { 52 CGFloat scaleFactor = MIN(1.0, desiredSize/imageSize.height); 53 imageRect.size.width *= scaleFactor; 54 imageRect.size.height *= scaleFactor; 55 } 56 imageRect = NSIntegralRect(imageRect); 57 return imageRect.size; 58 } 59 60 @implementation AWTTrayIcon 61 62 - (id) initWithPeer:(jobject)thePeer { 63 if (!(self = [super init])) return nil; 64 65 peer = thePeer; 66 67 theItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength]; 68 [theItem retain]; 69 70 view = [[AWTTrayIconView alloc] initWithTrayIcon:self]; 71 [theItem setView:view]; 72 73 return self; 74 } 75 76 -(void) dealloc { 77 JNIEnv *env = [ThreadUtilities getJNIEnvUncached]; 78 JNFDeleteGlobalRef(env, peer); 79 80 [[NSStatusBar systemStatusBar] removeStatusItem: theItem]; 81 82 // Its a bad idea to force the item to release our view by setting 83 // the item's view to nil: it can lead to a crash in some scenarios. 84 // The item will release the view later on, so just set the view's image 85 // and tray icon to nil since we are done with it. 86 [view setImage: nil]; 87 [view setTrayIcon: nil]; 88 [view release]; 89 90 [theItem release]; 91 92 [super dealloc]; 93 } 94 95 - (void) setTooltip:(NSString *) tooltip{ 96 [view setToolTip:tooltip]; 97 } 98 99 -(NSStatusItem *) theItem{ 100 return theItem; 101 } 102 103 - (jobject) peer{ 104 return peer; 105 } 106 107 - (void) setImage:(NSImage *) imagePtr sizing:(BOOL)autosize { 108 NSSize imageSize = [imagePtr size]; 109 NSSize scaledSize = ScaledImageSizeForStatusBar(imageSize, autosize); 110 if (imageSize.width != scaledSize.width || 111 imageSize.height != scaledSize.height) { 112 [imagePtr setSize: scaledSize]; 113 } 114 115 CGFloat itemLength = scaledSize.width + 2.0*kImageInset; 116 [theItem setLength:itemLength]; 117 118 [view setImage:imagePtr]; 119 } 120 121 - (NSPoint) getLocationOnScreen { 122 return [[view window] convertBaseToScreen: NSZeroPoint]; 123 } 124 125 -(void) deliverJavaMouseEvent: (NSEvent *) event { 126 [AWTToolkit eventCountPlusPlus]; 127 128 JNIEnv *env = [ThreadUtilities getJNIEnv]; 129 130 NSPoint eventLocation = [event locationInWindow]; 131 NSPoint localPoint = [view convertPoint: eventLocation fromView: nil]; 132 localPoint.y = [view bounds].size.height - localPoint.y; 133 134 NSPoint absP = [NSEvent mouseLocation]; 135 NSEventType type = [event type]; 136 137 absP = ConvertNSScreenPoint(NULL, absP); 138 jint clickCount; 139 140 clickCount = [event clickCount]; 141 142 jdouble deltaX = [event deltaX]; 143 jdouble deltaY = [event deltaY]; 144 if ([AWTToolkit hasPreciseScrollingDeltas: event]) { 145 deltaX = [event scrollingDeltaX] * 0.1; 146 deltaY = [event scrollingDeltaY] * 0.1; 147 } 148 149 static JNF_CLASS_CACHE(jc_NSEvent, "sun/lwawt/macosx/NSEvent"); 150 static JNF_CTOR_CACHE(jctor_NSEvent, jc_NSEvent, "(IIIIIIIIDDI)V"); 151 jobject jEvent = JNFNewObject(env, jctor_NSEvent, 152 [event type], 153 [event modifierFlags], 154 clickCount, 155 [event buttonNumber], 156 (jint)localPoint.x, (jint)localPoint.y, 157 (jint)absP.x, (jint)absP.y, 158 deltaY, 159 deltaX, 160 [AWTToolkit scrollStateWithEvent: event]); 161 CHECK_NULL(jEvent); 162 163 static JNF_CLASS_CACHE(jc_TrayIcon, "sun/lwawt/macosx/CTrayIcon"); 164 static JNF_MEMBER_CACHE(jm_handleMouseEvent, jc_TrayIcon, "handleMouseEvent", "(Lsun/lwawt/macosx/NSEvent;)V"); 165 JNFCallVoidMethod(env, peer, jm_handleMouseEvent, jEvent); 166 (*env)->DeleteLocalRef(env, jEvent); 167 } 168 169 @end //AWTTrayIcon 170 //================================================ 171 172 @implementation AWTTrayIconView 173 174 -(id)initWithTrayIcon:(AWTTrayIcon *)theTrayIcon { 175 self = [super initWithFrame:NSMakeRect(0, 0, 1, 1)]; 176 177 [self setTrayIcon: theTrayIcon]; 178 isHighlighted = NO; 179 image = nil; 180 trackingArea = nil; 181 182 [self addTrackingArea]; 183 184 return self; 185 } 186 187 - (void)addTrackingArea { 188 NSTrackingAreaOptions options = NSTrackingMouseMoved | 189 NSTrackingInVisibleRect | 190 NSTrackingActiveAlways; 191 trackingArea = [[NSTrackingArea alloc] initWithRect: CGRectZero 192 options: options 193 owner: self 194 userInfo: nil]; 195 [self addTrackingArea:trackingArea]; 196 } 197 198 -(void) dealloc { 199 [image release]; 200 [trackingArea release]; 201 [super dealloc]; 202 } 203 204 - (void)setHighlighted:(BOOL)aFlag 205 { 206 if (isHighlighted != aFlag) { 207 isHighlighted = aFlag; 208 [self setNeedsDisplay:YES]; 209 } 210 } 211 212 - (void)setImage:(NSImage*)anImage { 213 [anImage retain]; 214 [image release]; 215 image = anImage; 216 217 if (image != nil) { 218 [self setNeedsDisplay:YES]; 219 } 220 } 221 222 -(void)setTrayIcon:(AWTTrayIcon*)theTrayIcon { 223 trayIcon = theTrayIcon; 224 } 225 226 - (void)menuWillOpen:(NSMenu *)menu 227 { 228 [self setHighlighted:YES]; 229 } 230 231 - (void)menuDidClose:(NSMenu *)menu 232 { 233 [menu setDelegate:nil]; 234 [self setHighlighted:NO]; 235 } 236 237 - (void)drawRect:(NSRect)dirtyRect 238 { 239 if (image == nil) { 240 return; 241 } 242 243 NSRect bounds = [self bounds]; 244 NSSize imageSize = [image size]; 245 246 NSRect drawRect = {{ (bounds.size.width - imageSize.width) / 2.0, 247 (bounds.size.height - imageSize.height) / 2.0 }, imageSize}; 248 249 // don't cover bottom pixels of the status bar with the image 250 if (drawRect.origin.y < 1.0) { 251 drawRect.origin.y = 1.0; 252 } 253 drawRect = NSIntegralRect(drawRect); 254 255 [trayIcon.theItem drawStatusBarBackgroundInRect:bounds 256 withHighlight:isHighlighted]; 257 [image drawInRect:drawRect 258 fromRect:NSZeroRect 259 operation:NSCompositeSourceOver 260 fraction:1.0 261 ]; 262 } 263 264 - (void)mouseDown:(NSEvent *)event { 265 [trayIcon deliverJavaMouseEvent: event]; 266 267 // don't show the menu on ctrl+click: it triggers ACTION event, like right click 268 if (([event modifierFlags] & NSControlKeyMask) == 0) { 269 //find CTrayIcon.getPopupMenuModel method and call it to get popup menu ptr. 270 JNIEnv *env = [ThreadUtilities getJNIEnv]; 271 static JNF_CLASS_CACHE(jc_CTrayIcon, "sun/lwawt/macosx/CTrayIcon"); 272 static JNF_MEMBER_CACHE(jm_getPopupMenuModel, jc_CTrayIcon, "getPopupMenuModel", "()J"); 273 jlong res = JNFCallLongMethod(env, trayIcon.peer, jm_getPopupMenuModel); 274 275 if (res != 0) { 276 CPopupMenu *cmenu = jlong_to_ptr(res); 277 NSMenu* menu = [cmenu menu]; 278 [menu setDelegate:self]; 279 [trayIcon.theItem popUpStatusItemMenu:menu]; 280 [self setNeedsDisplay:YES]; 281 } 282 } 283 } 284 285 - (void) mouseUp:(NSEvent *)event { 286 [trayIcon deliverJavaMouseEvent: event]; 287 } 288 289 - (void) mouseDragged:(NSEvent *)event { 290 [trayIcon deliverJavaMouseEvent: event]; 291 } 292 293 - (void) mouseMoved: (NSEvent *)event { 294 [trayIcon deliverJavaMouseEvent: event]; 295 } 296 297 - (void) rightMouseDown:(NSEvent *)event { 298 [trayIcon deliverJavaMouseEvent: event]; 299 } 300 301 - (void) rightMouseUp:(NSEvent *)event { 302 [trayIcon deliverJavaMouseEvent: event]; 303 } 304 305 - (void) rightMouseDragged:(NSEvent *)event { 306 [trayIcon deliverJavaMouseEvent: event]; 307 } 308 309 - (void) otherMouseDown:(NSEvent *)event { 310 [trayIcon deliverJavaMouseEvent: event]; 311 } 312 313 - (void) otherMouseUp:(NSEvent *)event { 314 [trayIcon deliverJavaMouseEvent: event]; 315 } 316 317 - (void) otherMouseDragged:(NSEvent *)event { 318 [trayIcon deliverJavaMouseEvent: event]; 319 } 320 321 322 @end //AWTTrayIconView 323 //================================================ 324 325 /* 326 * Class: sun_lwawt_macosx_CTrayIcon 327 * Method: nativeCreate 328 * Signature: ()J 329 */ 330 JNIEXPORT jlong JNICALL Java_sun_lwawt_macosx_CTrayIcon_nativeCreate 331 (JNIEnv *env, jobject peer) { 332 __block AWTTrayIcon *trayIcon = nil; 333 334 JNF_COCOA_ENTER(env); 335 336 jobject thePeer = JNFNewGlobalRef(env, peer); 337 [ThreadUtilities performOnMainThreadWaiting:YES block:^(){ 338 trayIcon = [[AWTTrayIcon alloc] initWithPeer:thePeer]; 339 }]; 340 341 JNF_COCOA_EXIT(env); 342 343 return ptr_to_jlong(trayIcon); 344 } 345 346 347 /* 348 * Class: java_awt_TrayIcon 349 * Method: initIDs 350 * Signature: ()V 351 */ 352 JNIEXPORT void JNICALL Java_java_awt_TrayIcon_initIDs 353 (JNIEnv *env, jclass cls) { 354 //Do nothing. 355 } 356 357 /* 358 * Class: sun_lwawt_macosx_CTrayIcon 359 * Method: nativeSetToolTip 360 * Signature: (JLjava/lang/String;)V 361 */ 362 JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CTrayIcon_nativeSetToolTip 363 (JNIEnv *env, jobject self, jlong model, jstring jtooltip) { 364 JNF_COCOA_ENTER(env); 365 366 AWTTrayIcon *icon = jlong_to_ptr(model); 367 NSString *tooltip = JNFJavaToNSString(env, jtooltip); 368 [ThreadUtilities performOnMainThreadWaiting:NO block:^(){ 369 [icon setTooltip:tooltip]; 370 }]; 371 372 JNF_COCOA_EXIT(env); 373 } 374 375 /* 376 * Class: sun_lwawt_macosx_CTrayIcon 377 * Method: setNativeImage 378 * Signature: (JJZ)V 379 */ 380 JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CTrayIcon_setNativeImage 381 (JNIEnv *env, jobject self, jlong model, jlong imagePtr, jboolean autosize) { 382 JNF_COCOA_ENTER(env); 383 384 AWTTrayIcon *icon = jlong_to_ptr(model); 385 [ThreadUtilities performOnMainThreadWaiting:YES block:^(){ 386 [icon setImage:jlong_to_ptr(imagePtr) sizing:autosize]; 387 }]; 388 389 JNF_COCOA_EXIT(env); 390 } 391 392 JNIEXPORT jobject JNICALL 393 Java_sun_lwawt_macosx_CTrayIcon_nativeGetIconLocation 394 (JNIEnv *env, jobject self, jlong model) { 395 jobject jpt = NULL; 396 397 JNF_COCOA_ENTER(env); 398 399 __block NSPoint pt = NSZeroPoint; 400 AWTTrayIcon *icon = jlong_to_ptr(model); 401 [ThreadUtilities performOnMainThreadWaiting:YES block:^(){ 402 NSPoint loc = [icon getLocationOnScreen]; 403 pt = ConvertNSScreenPoint(env, loc); 404 }]; 405 406 jpt = NSToJavaPoint(env, pt); 407 408 JNF_COCOA_EXIT(env); 409 410 return jpt; 411 } 412 413 JNIEXPORT void JNICALL 414 Java_sun_lwawt_macosx_CTrayIcon_nativeShowNotification 415 (JNIEnv *env, jobject self, jlong model, jobject jcaption, jobject jtext, 416 long nsimage) { 417 JNF_COCOA_ENTER(env); 418 419 AWTTrayIcon *icon = jlong_to_ptr(model); 420 NSString *caption = JNFJavaToNSString(env, jcaption); 421 NSString *text = JNFJavaToNSString(env, jtext); 422 NSImage * contentImage = jlong_to_ptr(nsimage); 423 424 [ThreadUtilities performOnMainThreadWaiting:NO block:^(){ 425 NSUserNotification *notification = [[NSUserNotification alloc] init]; 426 notification.title = caption; 427 notification.informativeText = text; 428 notification.contentImage = contentImage; 429 notification.soundName = NSUserNotificationDefaultSoundName; 430 431 [[NSUserNotificationCenter defaultUserNotificationCenter] 432 deliverNotification:notification]; 433 }]; 434 435 JNF_COCOA_EXIT(env); 436 }