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