1/* 2 * Copyright (c) 2011, 2016, 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 */ 42static 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 NSRect screenRect = [[NSScreen mainScreen] frame]; 138 absP.y = screenRect.size.height - absP.y; 139 jint clickCount; 140 141 clickCount = [event clickCount]; 142 143 jdouble deltaX = [event deltaX]; 144 jdouble deltaY = [event deltaY]; 145 if ([AWTToolkit hasPreciseScrollingDeltas: event]) { 146 deltaX = [event scrollingDeltaX] * 0.1; 147 deltaY = [event scrollingDeltaY] * 0.1; 148 } 149 150 static JNF_CLASS_CACHE(jc_NSEvent, "sun/lwawt/macosx/NSEvent"); 151 static JNF_CTOR_CACHE(jctor_NSEvent, jc_NSEvent, "(IIIIIIIIDDI)V"); 152 jobject jEvent = JNFNewObject(env, jctor_NSEvent, 153 [event type], 154 [event modifierFlags], 155 clickCount, 156 [event buttonNumber], 157 (jint)localPoint.x, (jint)localPoint.y, 158 (jint)absP.x, (jint)absP.y, 159 deltaY, 160 deltaX, 161 [AWTToolkit scrollStateWithEvent: event]); 162 CHECK_NULL(jEvent); 163 164 static JNF_CLASS_CACHE(jc_TrayIcon, "sun/lwawt/macosx/CTrayIcon"); 165 static JNF_MEMBER_CACHE(jm_handleMouseEvent, jc_TrayIcon, "handleMouseEvent", "(Lsun/lwawt/macosx/NSEvent;)V"); 166 JNFCallVoidMethod(env, peer, jm_handleMouseEvent, jEvent); 167 (*env)->DeleteLocalRef(env, jEvent); 168} 169 170@end //AWTTrayIcon 171//================================================ 172 173@implementation AWTTrayIconView 174 175-(id)initWithTrayIcon:(AWTTrayIcon *)theTrayIcon { 176 self = [super initWithFrame:NSMakeRect(0, 0, 1, 1)]; 177 178 [self setTrayIcon: theTrayIcon]; 179 isHighlighted = NO; 180 image = nil; 181 trackingArea = nil; 182 183 [self addTrackingArea]; 184 185 return self; 186} 187 188- (void)addTrackingArea { 189 NSTrackingAreaOptions options = NSTrackingMouseMoved | 190 NSTrackingInVisibleRect | 191 NSTrackingActiveAlways; 192 trackingArea = [[NSTrackingArea alloc] initWithRect: CGRectZero 193 options: options 194 owner: self 195 userInfo: nil]; 196 [self addTrackingArea:trackingArea]; 197} 198 199-(void) dealloc { 200 [image release]; 201 [trackingArea release]; 202 [super dealloc]; 203} 204 205- (void)setHighlighted:(BOOL)aFlag 206{ 207 if (isHighlighted != aFlag) { 208 isHighlighted = aFlag; 209 [self setNeedsDisplay:YES]; 210 } 211} 212 213- (void)setImage:(NSImage*)anImage { 214 [anImage retain]; 215 [image release]; 216 image = anImage; 217 218 if (image != nil) { 219 [self setNeedsDisplay:YES]; 220 } 221} 222 223-(void)setTrayIcon:(AWTTrayIcon*)theTrayIcon { 224 trayIcon = theTrayIcon; 225} 226 227- (void)menuWillOpen:(NSMenu *)menu 228{ 229 [self setHighlighted:YES]; 230} 231 232- (void)menuDidClose:(NSMenu *)menu 233{ 234 [menu setDelegate:nil]; 235 [self setHighlighted:NO]; 236} 237 238- (void)drawRect:(NSRect)dirtyRect 239{ 240 if (image == nil) { 241 return; 242 } 243 244 NSRect bounds = [self bounds]; 245 NSSize imageSize = [image size]; 246 247 NSRect drawRect = {{ (bounds.size.width - imageSize.width) / 2.0, 248 (bounds.size.height - imageSize.height) / 2.0 }, imageSize}; 249 250 // don't cover bottom pixels of the status bar with the image 251 if (drawRect.origin.y < 1.0) { 252 drawRect.origin.y = 1.0; 253 } 254 drawRect = NSIntegralRect(drawRect); 255 256 [trayIcon.theItem drawStatusBarBackgroundInRect:bounds 257 withHighlight:isHighlighted]; 258 [image drawInRect:drawRect 259 fromRect:NSZeroRect 260 operation:NSCompositeSourceOver 261 fraction:1.0 262 ]; 263} 264 265- (void)mouseDown:(NSEvent *)event { 266 [trayIcon deliverJavaMouseEvent: event]; 267 268 // don't show the menu on ctrl+click: it triggers ACTION event, like right click 269 if (([event modifierFlags] & NSControlKeyMask) == 0) { 270 //find CTrayIcon.getPopupMenuModel method and call it to get popup menu ptr. 271 JNIEnv *env = [ThreadUtilities getJNIEnv]; 272 static JNF_CLASS_CACHE(jc_CTrayIcon, "sun/lwawt/macosx/CTrayIcon"); 273 static JNF_MEMBER_CACHE(jm_getPopupMenuModel, jc_CTrayIcon, "getPopupMenuModel", "()J"); 274 jlong res = JNFCallLongMethod(env, trayIcon.peer, jm_getPopupMenuModel); 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 */ 331JNIEXPORT jlong JNICALL Java_sun_lwawt_macosx_CTrayIcon_nativeCreate 332(JNIEnv *env, jobject peer) { 333 __block AWTTrayIcon *trayIcon = nil; 334 335JNF_COCOA_ENTER(env); 336 337 jobject thePeer = JNFNewGlobalRef(env, peer); 338 [ThreadUtilities performOnMainThreadWaiting:YES block:^(){ 339 trayIcon = [[AWTTrayIcon alloc] initWithPeer:thePeer]; 340 }]; 341 342JNF_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 */ 353JNIEXPORT 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 */ 363JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CTrayIcon_nativeSetToolTip 364(JNIEnv *env, jobject self, jlong model, jstring jtooltip) { 365JNF_COCOA_ENTER(env); 366 367 AWTTrayIcon *icon = jlong_to_ptr(model); 368 NSString *tooltip = JNFJavaToNSString(env, jtooltip); 369 [ThreadUtilities performOnMainThreadWaiting:NO block:^(){ 370 [icon setTooltip:tooltip]; 371 }]; 372 373JNF_COCOA_EXIT(env); 374} 375 376/* 377 * Class: sun_lwawt_macosx_CTrayIcon 378 * Method: setNativeImage 379 * Signature: (JJZ)V 380 */ 381JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CTrayIcon_setNativeImage 382(JNIEnv *env, jobject self, jlong model, jlong imagePtr, jboolean autosize) { 383JNF_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 390JNF_COCOA_EXIT(env); 391} 392 393JNIEXPORT jobject JNICALL 394Java_sun_lwawt_macosx_CTrayIcon_nativeGetIconLocation 395(JNIEnv *env, jobject self, jlong model) { 396 jobject jpt = NULL; 397 398JNF_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 409JNF_COCOA_EXIT(env); 410 411 return jpt; 412} 413