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 30 #import "CTrayIcon.h" 31 #import "ThreadUtilities.h" 32 #include "GeomUtilities.h" 33 #import "LWCToolkit.h" 34 35 #define kImageInset 4.0 36 37 /** 38 * If the image of the specified size won't fit into the status bar, 39 * then scale it down proprtionally. Otherwise, leave it as is. 40 */ 41 static NSSize ScaledImageSizeForStatusBar(NSSize imageSize, BOOL autosize) { 42 NSRect imageRect = NSMakeRect(0.0, 0.0, imageSize.width, imageSize.height); 43 44 // There is a black line at the bottom of the status bar 45 // that we don't want to cover with image pixels. 46 CGFloat desiredSize = [[NSStatusBar systemStatusBar] thickness] - 1.0; 47 if (autosize) { 48 imageRect.size.width = desiredSize; 49 imageRect.size.height = desiredSize; 50 } else { 51 CGFloat scaleFactor = MIN(1.0, desiredSize/imageSize.height); 52 imageRect.size.width *= scaleFactor; 53 imageRect.size.height *= scaleFactor; 54 } 55 imageRect = NSIntegralRect(imageRect); 56 return imageRect.size; 57 } 58 59 @implementation AWTTrayIcon 60 61 - (id) initWithPeer:(jobject)thePeer { 62 if (!(self = [super init])) return nil; 63 64 peer = thePeer; 65 66 theItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength]; 67 [theItem retain]; 68 69 view = [[AWTTrayIconView alloc] initWithTrayIcon:self]; 70 [theItem setView:view]; 71 72 return self; 73 } 74 75 -(void) dealloc { 76 JNIEnv *env = [ThreadUtilities getJNIEnvUncached]; 77 (*env)->DeleteGlobalRef(env, peer); 78 79 [[NSStatusBar systemStatusBar] removeStatusItem: theItem]; 80 81 // Its a bad idea to force the item to release our view by setting 82 // the item's view to nil: it can lead to a crash in some scenarios. 83 // The item will release the view later on, so just set the view's image 84 // and tray icon to nil since we are done with it. 85 [view setImage: nil]; 86 [view setTrayIcon: nil]; 87 [view release]; 88 89 [theItem release]; 90 91 [super dealloc]; 92 } 93 94 - (void) setTooltip:(NSString *) tooltip{ 95 [view setToolTip:tooltip]; 96 } 97 98 -(NSStatusItem *) theItem{ 99 return theItem; 100 } 101 102 - (jobject) peer{ 103 return peer; 104 } 105 106 - (void) setImage:(NSImage *) imagePtr sizing:(BOOL)autosize { 107 NSSize imageSize = [imagePtr size]; 108 NSSize scaledSize = ScaledImageSizeForStatusBar(imageSize, autosize); 109 if (imageSize.width != scaledSize.width || 110 imageSize.height != scaledSize.height) { 111 [imagePtr setSize: scaledSize]; 112 } 113 114 CGFloat itemLength = scaledSize.width + 2.0*kImageInset; 115 [theItem setLength:itemLength]; 116 117 [view setImage:imagePtr]; 118 } 119 120 - (NSPoint) getLocationOnScreen { 121 return [[view window] convertBaseToScreen: NSZeroPoint]; 122 } 123 124 -(void) deliverJavaMouseEvent: (NSEvent *) event { 125 [AWTToolkit eventCountPlusPlus]; 126 127 JNIEnv *env = [ThreadUtilities getJNIEnv]; 128 129 NSPoint eventLocation = [event locationInWindow]; 130 NSPoint localPoint = [view convertPoint: eventLocation fromView: nil]; 131 localPoint.y = [view bounds].size.height - localPoint.y; 132 133 NSPoint absP = [NSEvent mouseLocation]; 134 NSEventType type = [event type]; 135 136 absP = ConvertNSScreenPoint(NULL, absP); 137 jint clickCount; 138 139 clickCount = [event clickCount]; 140 141 jdouble deltaX = [event deltaX]; 142 jdouble deltaY = [event deltaY]; 143 if ([AWTToolkit hasPreciseScrollingDeltas: event]) { 144 deltaX = [event scrollingDeltaX] * 0.1; 145 deltaY = [event scrollingDeltaY] * 0.1; 146 } 147 148 DECLARE_CLASS(jc_NSEvent, "sun/lwawt/macosx/NSEvent"); 149 DECLARE_METHOD(jctor_NSEvent, jc_NSEvent, "<init>", "(IIIIIIIIDDI)V"); 150 jobject jEvent = (*env)->NewObject(env, jc_NSEvent, jctor_NSEvent, 151 [event type], 152 [event modifierFlags], 153 clickCount, 154 [event buttonNumber], 155 (jint)localPoint.x, (jint)localPoint.y, 156 (jint)absP.x, (jint)absP.y, 157 deltaY, 158 deltaX, 159 [AWTToolkit scrollStateWithEvent: event]); 160 CHECK_NULL(jEvent); 161 162 DECLARE_CLASS(jc_TrayIcon, "sun/lwawt/macosx/CTrayIcon"); 163 DECLARE_METHOD(jm_handleMouseEvent, jc_TrayIcon, "handleMouseEvent", "(Lsun/lwawt/macosx/NSEvent;)V"); 164 (*env)->CallVoidMethod(env, peer, jm_handleMouseEvent, jEvent); 165 CHECK_EXCEPTION(); 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 DECLARE_CLASS(jc_CTrayIcon, "sun/lwawt/macosx/CTrayIcon"); 272 DECLARE_METHOD(jm_getPopupMenuModel, jc_CTrayIcon, "getPopupMenuModel", "()J"); 273 jlong res = (*env)->CallLongMethod(env, trayIcon.peer, jm_getPopupMenuModel); 274 CHECK_EXCEPTION(); 275 276 if (res != 0) { 277 CPopupMenu *cmenu = jlong_to_ptr(res); 278 NSMenu* menu = [cmenu menu]; 279 [menu setDelegate:self]; 280 [trayIcon.theItem popUpStatusItemMenu:menu]; 281 [self setNeedsDisplay:YES]; 282 } 283 } 284 } 285 286 - (void) mouseUp:(NSEvent *)event { 287 [trayIcon deliverJavaMouseEvent: event]; 288 } 289 290 - (void) mouseDragged:(NSEvent *)event { 291 [trayIcon deliverJavaMouseEvent: event]; 292 } 293 294 - (void) mouseMoved: (NSEvent *)event { 295 [trayIcon deliverJavaMouseEvent: event]; 296 } 297 298 - (void) rightMouseDown:(NSEvent *)event { 299 [trayIcon deliverJavaMouseEvent: event]; 300 } 301 302 - (void) rightMouseUp:(NSEvent *)event { 303 [trayIcon deliverJavaMouseEvent: event]; 304 } 305 306 - (void) rightMouseDragged:(NSEvent *)event { 307 [trayIcon deliverJavaMouseEvent: event]; 308 } 309 310 - (void) otherMouseDown:(NSEvent *)event { 311 [trayIcon deliverJavaMouseEvent: event]; 312 } 313 314 - (void) otherMouseUp:(NSEvent *)event { 315 [trayIcon deliverJavaMouseEvent: event]; 316 } 317 318 - (void) otherMouseDragged:(NSEvent *)event { 319 [trayIcon deliverJavaMouseEvent: event]; 320 } 321 322 323 @end //AWTTrayIconView 324 //================================================ 325 326 /* 327 * Class: sun_lwawt_macosx_CTrayIcon 328 * Method: nativeCreate 329 * Signature: ()J 330 */ 331 JNIEXPORT jlong JNICALL Java_sun_lwawt_macosx_CTrayIcon_nativeCreate 332 (JNIEnv *env, jobject peer) { 333 __block AWTTrayIcon *trayIcon = nil; 334 335 JNI_COCOA_ENTER(env); 336 337 jobject thePeer = (*env)->NewGlobalRef(env, peer); 338 [ThreadUtilities performOnMainThreadWaiting:YES block:^(){ 339 trayIcon = [[AWTTrayIcon alloc] initWithPeer:thePeer]; 340 }]; 341 342 JNI_COCOA_EXIT(env); 343 344 return ptr_to_jlong(trayIcon); 345 } 346 347 348 /* 349 * Class: java_awt_TrayIcon 350 * Method: initIDs 351 * Signature: ()V 352 */ 353 JNIEXPORT void JNICALL Java_java_awt_TrayIcon_initIDs 354 (JNIEnv *env, jclass cls) { 355 //Do nothing. 356 } 357 358 /* 359 * Class: sun_lwawt_macosx_CTrayIcon 360 * Method: nativeSetToolTip 361 * Signature: (JLjava/lang/String;)V 362 */ 363 JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CTrayIcon_nativeSetToolTip 364 (JNIEnv *env, jobject self, jlong model, jstring jtooltip) { 365 JNI_COCOA_ENTER(env); 366 367 AWTTrayIcon *icon = jlong_to_ptr(model); 368 NSString *tooltip = JavaStringToNSString(env, jtooltip); 369 [ThreadUtilities performOnMainThreadWaiting:NO block:^(){ 370 [icon setTooltip:tooltip]; 371 }]; 372 373 JNI_COCOA_EXIT(env); 374 } 375 376 /* 377 * Class: sun_lwawt_macosx_CTrayIcon 378 * Method: setNativeImage 379 * Signature: (JJZ)V 380 */ 381 JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CTrayIcon_setNativeImage 382 (JNIEnv *env, jobject self, jlong model, jlong imagePtr, jboolean autosize) { 383 JNI_COCOA_ENTER(env); 384 385 AWTTrayIcon *icon = jlong_to_ptr(model); 386 [ThreadUtilities performOnMainThreadWaiting:YES block:^(){ 387 [icon setImage:jlong_to_ptr(imagePtr) sizing:autosize]; 388 }]; 389 390 JNI_COCOA_EXIT(env); 391 } 392 393 JNIEXPORT jobject JNICALL 394 Java_sun_lwawt_macosx_CTrayIcon_nativeGetIconLocation 395 (JNIEnv *env, jobject self, jlong model) { 396 jobject jpt = NULL; 397 398 JNI_COCOA_ENTER(env); 399 400 __block NSPoint pt = NSZeroPoint; 401 AWTTrayIcon *icon = jlong_to_ptr(model); 402 [ThreadUtilities performOnMainThreadWaiting:YES block:^(){ 403 NSPoint loc = [icon getLocationOnScreen]; 404 pt = ConvertNSScreenPoint(env, loc); 405 }]; 406 407 jpt = NSToJavaPoint(env, pt); 408 409 JNI_COCOA_EXIT(env); 410 411 return jpt; 412 } 413 414 JNIEXPORT void JNICALL 415 Java_sun_lwawt_macosx_CTrayIcon_nativeShowNotification 416 (JNIEnv *env, jobject self, jlong model, jobject jcaption, jobject jtext, 417 long nsimage) { 418 JNI_COCOA_ENTER(env); 419 420 AWTTrayIcon *icon = jlong_to_ptr(model); 421 NSString *caption = JavaStringToNSString(env, jcaption); 422 NSString *text = JavaStringToNSString(env, jtext); 423 NSImage * contentImage = jlong_to_ptr(nsimage); 424 425 [ThreadUtilities performOnMainThreadWaiting:NO block:^(){ 426 NSUserNotification *notification = [[NSUserNotification alloc] init]; 427 notification.title = caption; 428 notification.informativeText = text; 429 notification.contentImage = contentImage; 430 notification.soundName = NSUserNotificationDefaultSoundName; 431 432 [[NSUserNotificationCenter defaultUserNotificationCenter] 433 deliverNotification:notification]; 434 }]; 435 436 JNI_COCOA_EXIT(env); 437 }