1 /* 2 * Copyright (c) 2011, 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 <JavaNativeFoundation/JavaNativeFoundation.h> 27 28 #import "CMenuItem.h" 29 #import "CMenu.h" 30 #import "AWTEvent.h" 31 #import "ThreadUtilities.h" 32 33 #import "java_awt_Event.h" 34 #import "java_awt_event_KeyEvent.h" 35 #import "sun_lwawt_macosx_CMenuItem.h" 36 37 #define NOT_A_CHECKBOXMENU -2 38 39 40 @implementation CMenuItem 41 42 - (id) initWithPeer:(jobject)peer asSeparator: (NSNumber *) asSeparator{ 43 AWT_ASSERT_APPKIT_THREAD; 44 self = [super initWithPeer:peer]; 45 if (self) { 46 if ([asSeparator boolValue]) { 47 fMenuItem = (NSMenuItem*)[NSMenuItem separatorItem]; 48 [fMenuItem retain]; 49 } else { 50 fMenuItem = [[NSMenuItem alloc] init]; 51 [fMenuItem setAction:@selector(handleAction:)]; 52 [fMenuItem setTarget:self]; 53 } 54 fIsCheckbox = NO; 55 fIsEnabled = YES; 56 } 57 return self; 58 } 59 60 // This is because NSApplication doesn't check the target's window when sending 61 // actions; they only check the target itself. We always return YES, 62 // since we shouldn't even be installed unless our window is active. 63 - (BOOL) worksWhenModal { 64 return YES; 65 } 66 67 // Events 68 - (void)handleAction:(NSMenuItem *)sender { 69 AWT_ASSERT_APPKIT_THREAD; 70 JNIEnv *env = [ThreadUtilities getJNIEnv]; 71 JNF_COCOA_ENTER(env); 72 73 if (fIsCheckbox) { 74 static JNF_CLASS_CACHE(jc_CCheckboxMenuItem, "sun/lwawt/macosx/CCheckboxMenuItem"); 75 static JNF_MEMBER_CACHE(jm_ckHandleAction, jc_CCheckboxMenuItem, "handleAction", "(Z)V"); 76 77 // Send the opposite of what's currently checked -- the action 78 // indicates what state we're going to. 79 NSInteger state = [sender state]; 80 jboolean newState = (state == NSOnState ? JNI_FALSE : JNI_TRUE); 81 JNFCallVoidMethod(env, fPeer, jm_ckHandleAction, newState); 82 } else { 83 static JNF_CLASS_CACHE(jc_CMenuItem, "sun/lwawt/macosx/CMenuItem"); 84 static JNF_MEMBER_CACHE(jm_handleAction, jc_CMenuItem, "handleAction", "(JI)V"); // AWT_THREADING Safe (event) 85 86 NSEvent *currEvent = [[NSApplication sharedApplication] currentEvent]; 87 NSUInteger modifiers = [currEvent modifierFlags]; 88 jint javaModifiers = 0; 89 90 if ((modifiers & NSCommandKeyMask) != 0) javaModifiers |= java_awt_Event_META_MASK; 91 if ((modifiers & NSShiftKeyMask) != 0) javaModifiers |= java_awt_Event_SHIFT_MASK; 92 if ((modifiers & NSControlKeyMask) != 0) javaModifiers |= java_awt_Event_CTRL_MASK; 93 if ((modifiers & NSAlternateKeyMask) != 0) javaModifiers |= java_awt_Event_ALT_MASK; 94 95 JNFCallVoidMethod(env, fPeer, jm_handleAction, UTC(currEvent), javaModifiers); // AWT_THREADING Safe (event) 96 } 97 JNF_COCOA_EXIT(env); 98 } 99 100 - (void) setJavaLabel:(NSString *)theLabel shortcut:(NSString *)theKeyEquivalent modifierMask:(jint)modifiers { 101 AWT_ASSERT_NOT_APPKIT_THREAD; 102 103 NSUInteger modifierMask = 0; 104 105 if (![theKeyEquivalent isEqualToString:@""]) { 106 // Force the key equivalent to lower case if not using the shift key. 107 // Otherwise AppKit will draw a Shift glyph in the menu. 108 if ((modifiers & java_awt_event_KeyEvent_SHIFT_MASK) == 0) { 109 theKeyEquivalent = [theKeyEquivalent lowercaseString]; 110 } 111 112 // Hack for the question mark -- SHIFT and / means use the question mark. 113 if ((modifiers & java_awt_event_KeyEvent_SHIFT_MASK) != 0 && 114 [theKeyEquivalent isEqualToString:@"/"]) 115 { 116 theKeyEquivalent = @"?"; 117 modifiers &= ~java_awt_event_KeyEvent_SHIFT_MASK; 118 } 119 120 if ((modifiers & java_awt_event_KeyEvent_SHIFT_MASK) != 0) modifierMask |= NSShiftKeyMask; 121 if ((modifiers & java_awt_event_KeyEvent_CTRL_MASK) != 0) modifierMask |= NSControlKeyMask; 122 if ((modifiers & java_awt_event_KeyEvent_ALT_MASK) != 0) modifierMask |= NSAlternateKeyMask; 123 if ((modifiers & java_awt_event_KeyEvent_META_MASK) != 0) modifierMask |= NSCommandKeyMask; 124 } 125 126 [JNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){ 127 AWT_ASSERT_APPKIT_THREAD; 128 [fMenuItem setKeyEquivalent:theKeyEquivalent]; 129 [fMenuItem setKeyEquivalentModifierMask:modifierMask]; 130 [fMenuItem setTitle:theLabel]; 131 }]; 132 } 133 134 - (void) setJavaImage:(NSImage *)theImage { 135 AWT_ASSERT_NOT_APPKIT_THREAD; 136 137 [JNFRunLoop performOnMainThreadWaiting:NO withBlock:^(){ 138 AWT_ASSERT_APPKIT_THREAD; 139 140 [fMenuItem setImage:theImage]; 141 }]; 142 } 143 144 - (void) setJavaToolTipText:(NSString *)theText { 145 AWT_ASSERT_NOT_APPKIT_THREAD; 146 147 [JNFRunLoop performOnMainThreadWaiting:NO withBlock:^(){ 148 AWT_ASSERT_APPKIT_THREAD; 149 150 [fMenuItem setToolTip:theText]; 151 }]; 152 } 153 154 155 - (void)setJavaEnabled:(BOOL) enabled { 156 AWT_ASSERT_NOT_APPKIT_THREAD; 157 158 [JNFRunLoop performOnMainThreadWaiting:NO withBlock:^(){ 159 AWT_ASSERT_APPKIT_THREAD; 160 161 @synchronized(self) { 162 fIsEnabled = enabled; 163 164 // Warning: This won't work if the parent menu is disabled. 165 // See [CMenu syncFromJava]. We still need to call it here so 166 // the NSMenuItem itself gets properly updated. 167 [fMenuItem setEnabled:fIsEnabled]; 168 } 169 }]; 170 } 171 172 - (BOOL)isEnabled { 173 // AWT_ASSERT_ANY_THREAD; 174 175 BOOL enabled = NO; 176 @synchronized(self) { 177 enabled = fIsEnabled; 178 } 179 return enabled; 180 } 181 182 183 - (void)setJavaState:(BOOL)newState { 184 AWT_ASSERT_NOT_APPKIT_THREAD; 185 186 [JNFRunLoop performOnMainThreadWaiting:NO withBlock:^(){ 187 AWT_ASSERT_APPKIT_THREAD; 188 189 [fMenuItem setState:(newState ? NSOnState : NSOffState)]; 190 }]; 191 } 192 193 - (void)cleanup { 194 [fMenuItem setAction:NULL]; 195 [fMenuItem setTarget:nil]; 196 } 197 198 - (void)dealloc { 199 [fMenuItem release]; 200 fMenuItem = nil; 201 202 [super dealloc]; 203 } 204 205 - (void)addNSMenuItemToMenu:(NSMenu *)inMenu { 206 [inMenu addItem:fMenuItem]; 207 } 208 209 - (NSMenuItem *)menuItem { 210 return [[fMenuItem retain] autorelease]; 211 } 212 213 - (void)setIsCheckbox { 214 fIsCheckbox = YES; 215 } 216 217 - (void) _createMenuItem_OnAppKitThread: (NSMutableArray *)argValue { 218 jobject cPeerObjGlobal = (jobject)[[argValue objectAtIndex: 0] pointerValue]; 219 NSNumber * asSeparator = (NSNumber *)[argValue objectAtIndex: 1]; 220 CMenuItem *aCMenuItem = [self initWithPeer: cPeerObjGlobal asSeparator: asSeparator]; 221 [argValue removeAllObjects]; 222 [argValue addObject: aCMenuItem]; 223 } 224 225 - (NSString *)description { 226 return [NSString stringWithFormat:@"CMenuItem[ %@ ]", fMenuItem]; 227 } 228 229 @end 230 231 /** Convert a Java keycode for SetMenuItemCmd */ 232 static unichar AWTKeyToMacShortcut(jint awtKey, BOOL doShift) { 233 unichar macKey = 0; 234 235 if ((awtKey >= java_awt_event_KeyEvent_VK_0 && awtKey <= java_awt_event_KeyEvent_VK_9) || 236 (awtKey >= java_awt_event_KeyEvent_VK_A && awtKey <= java_awt_event_KeyEvent_VK_Z)) 237 { 238 // These ranges are the same in ASCII 239 macKey = awtKey; 240 } else if (awtKey >= java_awt_event_KeyEvent_VK_F1 && awtKey <= java_awt_event_KeyEvent_VK_F12) { 241 // Support for F1 - F12 has been around since Java 1.0 and fall into a lower range. 242 macKey = awtKey - java_awt_event_KeyEvent_VK_F1 + NSF1FunctionKey; 243 } else if (awtKey >= java_awt_event_KeyEvent_VK_F13 && awtKey <= java_awt_event_KeyEvent_VK_F24) { 244 // Support for F13-F24 came in Java 1.2 and are at a different range. 245 macKey = awtKey - java_awt_event_KeyEvent_VK_F13 + NSF13FunctionKey; 246 } else { 247 // Special characters 248 switch (awtKey) { 249 case java_awt_event_KeyEvent_VK_BACK_QUOTE : macKey = '`'; break; 250 case java_awt_event_KeyEvent_VK_QUOTE : macKey = '\''; break; 251 252 case java_awt_event_KeyEvent_VK_ESCAPE : macKey = 0x1B; break; 253 // case java_awt_event_KeyEvent_VK_SPACE : macKey = kMenuSpaceGlyph; break; 254 case java_awt_event_KeyEvent_VK_PAGE_UP : macKey = NSPageUpFunctionKey; break; 255 case java_awt_event_KeyEvent_VK_PAGE_DOWN : macKey = NSPageDownFunctionKey; break; 256 case java_awt_event_KeyEvent_VK_END : macKey = NSEndFunctionKey; break; 257 case java_awt_event_KeyEvent_VK_HOME : macKey = NSHomeFunctionKey; break; 258 259 case java_awt_event_KeyEvent_VK_LEFT : macKey = NSLeftArrowFunctionKey; break; 260 case java_awt_event_KeyEvent_VK_UP : macKey = NSUpArrowFunctionKey; break; 261 case java_awt_event_KeyEvent_VK_RIGHT : macKey = NSRightArrowFunctionKey; break; 262 case java_awt_event_KeyEvent_VK_DOWN : macKey = NSDownArrowFunctionKey; break; 263 264 case java_awt_event_KeyEvent_VK_COMMA : macKey = ','; break; 265 266 // Mac OS doesn't distinguish between the two '-' keys... 267 case java_awt_event_KeyEvent_VK_MINUS : 268 case java_awt_event_KeyEvent_VK_SUBTRACT : macKey = '-'; break; 269 270 // or the two '.' keys... 271 case java_awt_event_KeyEvent_VK_DECIMAL : 272 case java_awt_event_KeyEvent_VK_PERIOD : macKey = '.'; break; 273 274 // or the two '/' keys. 275 case java_awt_event_KeyEvent_VK_DIVIDE : 276 case java_awt_event_KeyEvent_VK_SLASH : macKey = '/'; break; 277 278 case java_awt_event_KeyEvent_VK_SEMICOLON : macKey = ';'; break; 279 case java_awt_event_KeyEvent_VK_EQUALS : macKey = '='; break; 280 281 case java_awt_event_KeyEvent_VK_OPEN_BRACKET : macKey = '['; break; 282 case java_awt_event_KeyEvent_VK_BACK_SLASH : macKey = '\\'; break; 283 case java_awt_event_KeyEvent_VK_CLOSE_BRACKET : macKey = ']'; break; 284 285 case java_awt_event_KeyEvent_VK_MULTIPLY : macKey = '*'; break; 286 case java_awt_event_KeyEvent_VK_ADD : macKey = '+'; break; 287 288 case java_awt_event_KeyEvent_VK_HELP : macKey = NSHelpFunctionKey; break; 289 case java_awt_event_KeyEvent_VK_TAB : macKey = NSTabCharacter; break; 290 case java_awt_event_KeyEvent_VK_ENTER : macKey = NSCarriageReturnCharacter; break; 291 case java_awt_event_KeyEvent_VK_BACK_SPACE : macKey = NSBackspaceCharacter; break; 292 case java_awt_event_KeyEvent_VK_DELETE : macKey = NSDeleteCharacter; break; 293 case java_awt_event_KeyEvent_VK_CLEAR : macKey = NSClearDisplayFunctionKey; break; 294 case java_awt_event_KeyEvent_VK_AMPERSAND : macKey = '&'; break; 295 case java_awt_event_KeyEvent_VK_ASTERISK : macKey = '*'; break; 296 case java_awt_event_KeyEvent_VK_QUOTEDBL : macKey = '\"'; break; 297 case java_awt_event_KeyEvent_VK_LESS : macKey = '<'; break; 298 case java_awt_event_KeyEvent_VK_GREATER : macKey = '>'; break; 299 case java_awt_event_KeyEvent_VK_BRACELEFT : macKey = '{'; break; 300 case java_awt_event_KeyEvent_VK_BRACERIGHT : macKey = '}'; break; 301 case java_awt_event_KeyEvent_VK_AT : macKey = '@'; break; 302 case java_awt_event_KeyEvent_VK_COLON : macKey = ':'; break; 303 case java_awt_event_KeyEvent_VK_CIRCUMFLEX : macKey = '^'; break; 304 case java_awt_event_KeyEvent_VK_DOLLAR : macKey = '$'; break; 305 case java_awt_event_KeyEvent_VK_EXCLAMATION_MARK : macKey = '!'; break; 306 case java_awt_event_KeyEvent_VK_LEFT_PARENTHESIS : macKey = '('; break; 307 case java_awt_event_KeyEvent_VK_NUMBER_SIGN : macKey = '#'; break; 308 case java_awt_event_KeyEvent_VK_PLUS : macKey = '+'; break; 309 case java_awt_event_KeyEvent_VK_RIGHT_PARENTHESIS: macKey = ')'; break; 310 case java_awt_event_KeyEvent_VK_UNDERSCORE : macKey = '_'; break; 311 } 312 } 313 return macKey; 314 } 315 316 /* 317 * Class: sun_lwawt_macosx_CMenuItem 318 * Method: nativeSetLabel 319 * Signature: (JLjava/lang/String;CII)V 320 */ 321 JNIEXPORT void JNICALL 322 Java_sun_lwawt_macosx_CMenuItem_nativeSetLabel 323 (JNIEnv *env, jobject peer, 324 jlong menuItemObj, jstring label, 325 jchar shortcutKey, jint shortcutKeyCode, jint mods) 326 { 327 JNF_COCOA_ENTER(env); 328 NSString *theLabel = JNFJavaToNSString(env, label); 329 NSString *theKeyEquivalent = nil; 330 unichar macKey = shortcutKey; 331 332 if (macKey == 0) { 333 macKey = AWTKeyToMacShortcut(shortcutKeyCode, (mods & java_awt_event_KeyEvent_SHIFT_MASK) != 0); 334 } 335 336 if (macKey != 0) { 337 unichar equivalent[1] = {macKey}; 338 theKeyEquivalent = [NSString stringWithCharacters:equivalent length:1]; 339 } else { 340 theKeyEquivalent = @""; 341 } 342 343 [((CMenuItem *)jlong_to_ptr(menuItemObj)) setJavaLabel:theLabel shortcut:theKeyEquivalent modifierMask:mods]; 344 JNF_COCOA_EXIT(env); 345 } 346 347 /* 348 * Class: sun_lwawt_macosx_CMenuItem 349 * Method: nativeSetTooltip 350 * Signature: (JLjava/lang/String;)V 351 */ 352 JNIEXPORT void JNICALL 353 Java_sun_lwawt_macosx_CMenuItem_nativeSetTooltip 354 (JNIEnv *env, jobject peer, jlong menuItemObj, jstring tooltip) 355 { 356 JNF_COCOA_ENTER(env); 357 NSString *theTooltip = JNFJavaToNSString(env, tooltip); 358 [((CMenuItem *)jlong_to_ptr(menuItemObj)) setJavaToolTipText:theTooltip]; 359 JNF_COCOA_EXIT(env); 360 } 361 362 /* 363 * Class: sun_lwawt_macosx_CMenuItem 364 * Method: nativeSetImage 365 * Signature: (JJ)V 366 */ 367 JNIEXPORT void JNICALL 368 Java_sun_lwawt_macosx_CMenuItem_nativeSetImage 369 (JNIEnv *env, jobject peer, jlong menuItemObj, jlong image) 370 { 371 JNF_COCOA_ENTER(env); 372 [((CMenuItem *)jlong_to_ptr(menuItemObj)) setJavaImage:(NSImage*)jlong_to_ptr(image)]; 373 JNF_COCOA_EXIT(env); 374 } 375 376 /* 377 * Class: sun_lwawt_macosx_CMenuItem 378 * Method: nativeCreate 379 * Signature: (JZ)J 380 */ 381 JNIEXPORT jlong JNICALL 382 Java_sun_lwawt_macosx_CMenuItem_nativeCreate 383 (JNIEnv *env, jobject peer, jlong parentCMenuObj, jboolean isSeparator) 384 { 385 386 CMenuItem *aCMenuItem = nil; 387 CMenu *parentCMenu = (CMenu *)jlong_to_ptr(parentCMenuObj); 388 JNF_COCOA_ENTER(env); 389 390 jobject cPeerObjGlobal = (*env)->NewGlobalRef(env, peer); 391 392 NSMutableArray *args = nil; 393 394 // Create a new item.... 395 if (isSeparator == JNI_TRUE) { 396 args = [[NSMutableArray alloc] initWithObjects:[NSValue valueWithBytes:&cPeerObjGlobal objCType:@encode(jobject)], [NSNumber numberWithBool:YES], nil]; 397 } else { 398 args = [[NSMutableArray alloc] initWithObjects:[NSValue valueWithBytes:&cPeerObjGlobal objCType:@encode(jobject)], [NSNumber numberWithBool:NO], nil]; 399 } 400 401 [ThreadUtilities performOnMainThread:@selector(_createMenuItem_OnAppKitThread:) onObject:[CMenuItem alloc] withObject:args waitUntilDone:YES awtMode:YES]; 402 403 aCMenuItem = (CMenuItem *)[args objectAtIndex: 0]; 404 405 if (aCMenuItem == nil) { 406 return 0L; 407 } 408 409 // and add it to the parent item. 410 [parentCMenu addJavaMenuItem: aCMenuItem]; 411 412 // setLabel will be called after creation completes. 413 414 if (aCMenuItem) { 415 CFRetain(aCMenuItem); // GC 416 [aCMenuItem release]; 417 } 418 419 JNF_COCOA_EXIT(env); 420 return ptr_to_jlong(aCMenuItem); 421 } 422 423 /* 424 * Class: sun_lwawt_macosx_CMenuItem 425 * Method: nativeSetEnabled 426 * Signature: (JZ)V 427 */ 428 JNIEXPORT void JNICALL 429 Java_sun_lwawt_macosx_CMenuItem_nativeSetEnabled 430 (JNIEnv *env, jobject peer, jlong menuItemObj, jboolean enable) 431 { 432 JNF_COCOA_ENTER(env); 433 CMenuItem *item = (CMenuItem *)jlong_to_ptr(menuItemObj); 434 [item setJavaEnabled: (enable == JNI_TRUE)]; 435 JNF_COCOA_EXIT(env); 436 } 437 438 /* 439 * Class: sun_lwawt_macosx_CCheckboxMenuItem 440 * Method: nativeSetState 441 * Signature: (IZ)V 442 */ 443 JNIEXPORT void JNICALL 444 Java_sun_lwawt_macosx_CCheckboxMenuItem_nativeSetState 445 (JNIEnv *env, jobject peer, jlong menuItemObj, jboolean state) 446 { 447 JNF_COCOA_ENTER(env); 448 CMenuItem *item = (CMenuItem *)jlong_to_ptr(menuItemObj); 449 [item setJavaState: (state == JNI_TRUE)]; 450 JNF_COCOA_EXIT(env); 451 } 452 453 /* 454 * Class: sun_lwawt_macosx_CCheckboxMenuItem 455 * Method: nativeSetState 456 * Signature: (IZ)V 457 */ 458 JNIEXPORT void JNICALL 459 Java_sun_lwawt_macosx_CCheckboxMenuItem_nativeSetIsCheckbox 460 (JNIEnv *env, jobject peer, jlong menuItemObj) 461 { 462 JNF_COCOA_ENTER(env); 463 CMenuItem *item = (CMenuItem *)jlong_to_ptr(menuItemObj); 464 [item setIsCheckbox]; 465 JNF_COCOA_EXIT(env); 466 }