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