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 <JavaNativeFoundation/JavaNativeFoundation.h> 27 #include <Carbon/Carbon.h> 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 we are called as a result of user pressing a shortcut, do nothing, 74 // because AVTView has already sent corresponding key event to the Java 75 // layer from performKeyEquivalent. 76 // There is an exception from the rule above, though: if a window with 77 // a menu gets minimized by user and there are no other windows to take 78 // focus, the window's menu won't be removed from the global menu bar. 79 // However, the Java layer won't handle invocation by a shortcut coming 80 // from this "frameless" menu, because there are no active windows. This 81 // means we have to handle it here. 82 NSEvent *currEvent = [[NSApplication sharedApplication] currentEvent]; 83 if (fIsCheckbox) { 84 static JNF_CLASS_CACHE(jc_CCheckboxMenuItem, "sun/lwawt/macosx/CCheckboxMenuItem"); 85 static JNF_MEMBER_CACHE(jm_ckHandleAction, jc_CCheckboxMenuItem, "handleAction", "(Z)V"); 86 87 // Send the opposite of what's currently checked -- the action 88 // indicates what state we're going to. 89 NSInteger state = [sender state]; 90 jboolean newState = (state == NSOnState ? JNI_FALSE : JNI_TRUE); 91 JNFCallVoidMethod(env, fPeer, jm_ckHandleAction, newState); 92 } 93 else { 94 if ([currEvent type] == NSKeyDown) { 95 // Event available through sender variable hence NSApplication 96 // not needed for checking the keyboard input sans the modifier keys 97 // Also, the method used to fetch eventKey earlier would be locale dependent 98 // With earlier implementation, if MenuKey: e EventKey: ा ; if input method 99 // is not U.S. (Devanagari in this case) 100 // With current implementation, EventKey = MenuKey = e irrespective of 101 // input method 102 NSString *eventKey = [sender keyEquivalent]; 103 // Apple uses characters from private Unicode range for some of the 104 // keys, so we need to do the same translation here that we do 105 // for the regular key down events 106 if ([eventKey length] == 1) { 107 unichar origChar = [eventKey characterAtIndex:0]; 108 unichar newChar = NsCharToJavaChar(origChar, 0); 109 if (newChar == java_awt_event_KeyEvent_CHAR_UNDEFINED) { 110 newChar = origChar; 111 } 112 eventKey = [NSString stringWithCharacters: &newChar length: 1]; 113 } 114 NSWindow *keyWindow = [NSApp keyWindow]; 115 if (keyWindow != nil) { 116 return; 117 } 118 } 119 120 static JNF_CLASS_CACHE(jc_CMenuItem, "sun/lwawt/macosx/CMenuItem"); 121 static JNF_MEMBER_CACHE(jm_handleAction, jc_CMenuItem, "handleAction", "(JI)V"); // AWT_THREADING Safe (event) 122 123 NSUInteger modifiers = [currEvent modifierFlags]; 124 jint javaModifiers = NsKeyModifiersToJavaModifiers(modifiers, NO); 125 126 JNFCallVoidMethod(env, fPeer, jm_handleAction, UTC(currEvent), javaModifiers); // AWT_THREADING Safe (event) 127 } 128 JNF_COCOA_EXIT(env); 129 130 } 131 132 - (void) setJavaLabel:(NSString *)theLabel shortcut:(NSString *)theKeyEquivalent modifierMask:(jint)modifiers { 133 134 NSUInteger modifierMask = 0; 135 136 if (![theKeyEquivalent isEqualToString:@""]) { 137 // Force the key equivalent to lower case if not using the shift key. 138 // Otherwise AppKit will draw a Shift glyph in the menu. 139 if ((modifiers & java_awt_event_KeyEvent_SHIFT_MASK) == 0) { 140 theKeyEquivalent = [theKeyEquivalent lowercaseString]; 141 } 142 143 // Hack for the question mark -- SHIFT and / means use the question mark. 144 if ((modifiers & java_awt_event_KeyEvent_SHIFT_MASK) != 0 && 145 [theKeyEquivalent isEqualToString:@"/"]) 146 { 147 theKeyEquivalent = @"?"; 148 modifiers &= ~java_awt_event_KeyEvent_SHIFT_MASK; 149 } 150 151 modifierMask = JavaModifiersToNsKeyModifiers(modifiers, NO); 152 } 153 154 [ThreadUtilities performOnMainThreadWaiting:YES block:^(){ 155 [fMenuItem setKeyEquivalent:theKeyEquivalent]; 156 [fMenuItem setKeyEquivalentModifierMask:modifierMask]; 157 [fMenuItem setTitle:theLabel]; 158 }]; 159 } 160 161 - (void) setJavaImage:(NSImage *)theImage { 162 163 [ThreadUtilities performOnMainThreadWaiting:NO block:^(){ 164 [fMenuItem setImage:theImage]; 165 }]; 166 } 167 168 - (void) setJavaToolTipText:(NSString *)theText { 169 170 [ThreadUtilities performOnMainThreadWaiting:NO block:^(){ 171 [fMenuItem setToolTip:theText]; 172 }]; 173 } 174 175 176 - (void)setJavaEnabled:(BOOL) enabled { 177 178 [ThreadUtilities performOnMainThreadWaiting:NO block:^(){ 179 @synchronized(self) { 180 fIsEnabled = enabled; 181 182 // Warning: This won't work if the parent menu is disabled. 183 // See [CMenu syncFromJava]. We still need to call it here so 184 // the NSMenuItem itself gets properly updated. 185 [fMenuItem setEnabled:fIsEnabled]; 186 } 187 }]; 188 } 189 190 - (BOOL)isEnabled { 191 192 BOOL enabled = NO; 193 @synchronized(self) { 194 enabled = fIsEnabled; 195 } 196 return enabled; 197 } 198 199 200 - (void)setJavaState:(BOOL)newState { 201 202 [ThreadUtilities performOnMainThreadWaiting:NO block:^(){ 203 [fMenuItem setState:(newState ? NSOnState : NSOffState)]; 204 }]; 205 } 206 207 - (void)cleanup { 208 [fMenuItem setAction:NULL]; 209 [fMenuItem setTarget:nil]; 210 } 211 212 - (void)dealloc { 213 [fMenuItem release]; 214 fMenuItem = nil; 215 216 [super dealloc]; 217 } 218 219 - (void)addNSMenuItemToMenu:(NSMenu *)inMenu { 220 [inMenu addItem:fMenuItem]; 221 } 222 223 - (NSMenuItem *)menuItem { 224 return [[fMenuItem retain] autorelease]; 225 } 226 227 - (void)setIsCheckbox { 228 fIsCheckbox = YES; 229 } 230 231 - (void) _createMenuItem_OnAppKitThread: (NSMutableArray *)argValue { 232 jobject cPeerObjGlobal = (jobject)[[argValue objectAtIndex: 0] pointerValue]; 233 NSNumber * asSeparator = (NSNumber *)[argValue objectAtIndex: 1]; 234 CMenuItem *aCMenuItem = [self initWithPeer: cPeerObjGlobal asSeparator: asSeparator]; 235 [argValue removeAllObjects]; 236 [argValue addObject: aCMenuItem]; 237 } 238 239 - (NSString *)description { 240 return [NSString stringWithFormat:@"CMenuItem[ %@ ]", fMenuItem]; 241 } 242 243 @end 244 245 /** Convert a Java keycode for SetMenuItemCmd */ 246 static unichar AWTKeyToMacShortcut(jint awtKey, BOOL doShift) { 247 unichar macKey = 0; 248 249 if ((awtKey >= java_awt_event_KeyEvent_VK_0 && awtKey <= java_awt_event_KeyEvent_VK_9) || 250 (awtKey >= java_awt_event_KeyEvent_VK_A && awtKey <= java_awt_event_KeyEvent_VK_Z)) 251 { 252 // These ranges are the same in ASCII 253 macKey = awtKey; 254 } else if (awtKey >= java_awt_event_KeyEvent_VK_F1 && awtKey <= java_awt_event_KeyEvent_VK_F12) { 255 // Support for F1 - F12 has been around since Java 1.0 and fall into a lower range. 256 macKey = awtKey - java_awt_event_KeyEvent_VK_F1 + NSF1FunctionKey; 257 } else if (awtKey >= java_awt_event_KeyEvent_VK_F13 && awtKey <= java_awt_event_KeyEvent_VK_F24) { 258 // Support for F13-F24 came in Java 1.2 and are at a different range. 259 macKey = awtKey - java_awt_event_KeyEvent_VK_F13 + NSF13FunctionKey; 260 } else { 261 // Special characters 262 switch (awtKey) { 263 case java_awt_event_KeyEvent_VK_BACK_QUOTE : macKey = '`'; break; 264 case java_awt_event_KeyEvent_VK_QUOTE : macKey = '\''; break; 265 266 case java_awt_event_KeyEvent_VK_ESCAPE : macKey = 0x1B; break; 267 case java_awt_event_KeyEvent_VK_SPACE : macKey = ' '; break; 268 case java_awt_event_KeyEvent_VK_PAGE_UP : macKey = NSPageUpFunctionKey; break; 269 case java_awt_event_KeyEvent_VK_PAGE_DOWN : macKey = NSPageDownFunctionKey; break; 270 case java_awt_event_KeyEvent_VK_END : macKey = NSEndFunctionKey; break; 271 case java_awt_event_KeyEvent_VK_HOME : macKey = NSHomeFunctionKey; break; 272 273 case java_awt_event_KeyEvent_VK_LEFT : macKey = NSLeftArrowFunctionKey; break; 274 case java_awt_event_KeyEvent_VK_UP : macKey = NSUpArrowFunctionKey; break; 275 case java_awt_event_KeyEvent_VK_RIGHT : macKey = NSRightArrowFunctionKey; break; 276 case java_awt_event_KeyEvent_VK_DOWN : macKey = NSDownArrowFunctionKey; break; 277 278 case java_awt_event_KeyEvent_VK_COMMA : macKey = ','; break; 279 280 // Mac OS doesn't distinguish between the two '-' keys... 281 case java_awt_event_KeyEvent_VK_MINUS : 282 case java_awt_event_KeyEvent_VK_SUBTRACT : macKey = '-'; break; 283 284 // or the two '.' keys... 285 case java_awt_event_KeyEvent_VK_DECIMAL : 286 case java_awt_event_KeyEvent_VK_PERIOD : macKey = '.'; break; 287 288 // or the two '/' keys. 289 case java_awt_event_KeyEvent_VK_DIVIDE : 290 case java_awt_event_KeyEvent_VK_SLASH : macKey = '/'; break; 291 292 case java_awt_event_KeyEvent_VK_SEMICOLON : macKey = ';'; break; 293 case java_awt_event_KeyEvent_VK_EQUALS : macKey = '='; break; 294 295 case java_awt_event_KeyEvent_VK_OPEN_BRACKET : macKey = '['; break; 296 case java_awt_event_KeyEvent_VK_BACK_SLASH : macKey = '\\'; break; 297 case java_awt_event_KeyEvent_VK_CLOSE_BRACKET : macKey = ']'; break; 298 299 case java_awt_event_KeyEvent_VK_MULTIPLY : macKey = '*'; break; 300 case java_awt_event_KeyEvent_VK_ADD : macKey = '+'; break; 301 302 case java_awt_event_KeyEvent_VK_HELP : macKey = NSHelpFunctionKey; break; 303 case java_awt_event_KeyEvent_VK_TAB : macKey = NSTabCharacter; break; 304 case java_awt_event_KeyEvent_VK_ENTER : macKey = NSNewlineCharacter; break; 305 case java_awt_event_KeyEvent_VK_BACK_SPACE : macKey = NSBackspaceCharacter; break; 306 case java_awt_event_KeyEvent_VK_DELETE : macKey = NSDeleteCharacter; break; 307 case java_awt_event_KeyEvent_VK_CLEAR : macKey = NSClearDisplayFunctionKey; break; 308 case java_awt_event_KeyEvent_VK_AMPERSAND : macKey = '&'; break; 309 case java_awt_event_KeyEvent_VK_ASTERISK : macKey = '*'; break; 310 case java_awt_event_KeyEvent_VK_QUOTEDBL : macKey = '\"'; break; 311 case java_awt_event_KeyEvent_VK_LESS : macKey = '<'; break; 312 case java_awt_event_KeyEvent_VK_GREATER : macKey = '>'; break; 313 case java_awt_event_KeyEvent_VK_BRACELEFT : macKey = '{'; break; 314 case java_awt_event_KeyEvent_VK_BRACERIGHT : macKey = '}'; break; 315 case java_awt_event_KeyEvent_VK_AT : macKey = '@'; break; 316 case java_awt_event_KeyEvent_VK_COLON : macKey = ':'; break; 317 case java_awt_event_KeyEvent_VK_CIRCUMFLEX : macKey = '^'; break; 318 case java_awt_event_KeyEvent_VK_DOLLAR : macKey = '$'; break; 319 case java_awt_event_KeyEvent_VK_EXCLAMATION_MARK : macKey = '!'; break; 320 case java_awt_event_KeyEvent_VK_LEFT_PARENTHESIS : macKey = '('; break; 321 case java_awt_event_KeyEvent_VK_NUMBER_SIGN : macKey = '#'; break; 322 case java_awt_event_KeyEvent_VK_PLUS : macKey = '+'; break; 323 case java_awt_event_KeyEvent_VK_RIGHT_PARENTHESIS: macKey = ')'; break; 324 case java_awt_event_KeyEvent_VK_UNDERSCORE : macKey = '_'; break; 325 } 326 } 327 return macKey; 328 } 329 330 /* 331 * Class: sun_lwawt_macosx_CMenuItem 332 * Method: nativeSetLabel 333 * Signature: (JLjava/lang/String;CII)V 334 */ 335 JNIEXPORT void JNICALL 336 Java_sun_lwawt_macosx_CMenuItem_nativeSetLabel 337 (JNIEnv *env, jobject peer, 338 jlong menuItemObj, jstring label, 339 jchar shortcutKey, jint shortcutKeyCode, jint mods) 340 { 341 JNF_COCOA_ENTER(env); 342 NSString *theLabel = JNFJavaToNSString(env, label); 343 NSString *theKeyEquivalent = nil; 344 unichar macKey = shortcutKey; 345 346 if (macKey == 0) { 347 macKey = AWTKeyToMacShortcut(shortcutKeyCode, (mods & java_awt_event_KeyEvent_SHIFT_MASK) != 0); 348 } 349 350 if (macKey != 0) { 351 unichar equivalent[1] = {macKey}; 352 theKeyEquivalent = [NSString stringWithCharacters:equivalent length:1]; 353 } else { 354 theKeyEquivalent = @""; 355 } 356 357 [((CMenuItem *)jlong_to_ptr(menuItemObj)) setJavaLabel:theLabel shortcut:theKeyEquivalent modifierMask:mods]; 358 JNF_COCOA_EXIT(env); 359 } 360 361 /* 362 * Class: sun_lwawt_macosx_CMenuItem 363 * Method: nativeSetTooltip 364 * Signature: (JLjava/lang/String;)V 365 */ 366 JNIEXPORT void JNICALL 367 Java_sun_lwawt_macosx_CMenuItem_nativeSetTooltip 368 (JNIEnv *env, jobject peer, jlong menuItemObj, jstring tooltip) 369 { 370 JNF_COCOA_ENTER(env); 371 NSString *theTooltip = JNFJavaToNSString(env, tooltip); 372 [((CMenuItem *)jlong_to_ptr(menuItemObj)) setJavaToolTipText:theTooltip]; 373 JNF_COCOA_EXIT(env); 374 } 375 376 /* 377 * Class: sun_lwawt_macosx_CMenuItem 378 * Method: nativeSetImage 379 * Signature: (JJ)V 380 */ 381 JNIEXPORT void JNICALL 382 Java_sun_lwawt_macosx_CMenuItem_nativeSetImage 383 (JNIEnv *env, jobject peer, jlong menuItemObj, jlong image) 384 { 385 JNF_COCOA_ENTER(env); 386 [((CMenuItem *)jlong_to_ptr(menuItemObj)) setJavaImage:(NSImage*)jlong_to_ptr(image)]; 387 JNF_COCOA_EXIT(env); 388 } 389 390 /* 391 * Class: sun_lwawt_macosx_CMenuItem 392 * Method: nativeCreate 393 * Signature: (JZ)J 394 */ 395 JNIEXPORT jlong JNICALL 396 Java_sun_lwawt_macosx_CMenuItem_nativeCreate 397 (JNIEnv *env, jobject peer, jlong parentCMenuObj, jboolean isSeparator) 398 { 399 400 CMenuItem *aCMenuItem = nil; 401 CMenu *parentCMenu = (CMenu *)jlong_to_ptr(parentCMenuObj); 402 JNF_COCOA_ENTER(env); 403 404 jobject cPeerObjGlobal = (*env)->NewGlobalRef(env, peer); 405 406 NSMutableArray *args = nil; 407 408 // Create a new item.... 409 if (isSeparator == JNI_TRUE) { 410 args = [[NSMutableArray alloc] initWithObjects:[NSValue valueWithBytes:&cPeerObjGlobal objCType:@encode(jobject)], [NSNumber numberWithBool:YES], nil]; 411 } else { 412 args = [[NSMutableArray alloc] initWithObjects:[NSValue valueWithBytes:&cPeerObjGlobal objCType:@encode(jobject)], [NSNumber numberWithBool:NO], nil]; 413 } 414 415 [ThreadUtilities performOnMainThread:@selector(_createMenuItem_OnAppKitThread:) on:[CMenuItem alloc] withObject:args waitUntilDone:YES]; 416 417 aCMenuItem = (CMenuItem *)[args objectAtIndex: 0]; 418 419 if (aCMenuItem == nil) { 420 return 0L; 421 } 422 423 // and add it to the parent item. 424 [parentCMenu addJavaMenuItem: aCMenuItem]; 425 426 // setLabel will be called after creation completes. 427 428 JNF_COCOA_EXIT(env); 429 return ptr_to_jlong(aCMenuItem); 430 } 431 432 /* 433 * Class: sun_lwawt_macosx_CMenuItem 434 * Method: nativeSetEnabled 435 * Signature: (JZ)V 436 */ 437 JNIEXPORT void JNICALL 438 Java_sun_lwawt_macosx_CMenuItem_nativeSetEnabled 439 (JNIEnv *env, jobject peer, jlong menuItemObj, jboolean enable) 440 { 441 JNF_COCOA_ENTER(env); 442 CMenuItem *item = (CMenuItem *)jlong_to_ptr(menuItemObj); 443 [item setJavaEnabled: (enable == JNI_TRUE)]; 444 JNF_COCOA_EXIT(env); 445 } 446 447 /* 448 * Class: sun_lwawt_macosx_CCheckboxMenuItem 449 * Method: nativeSetState 450 * Signature: (IZ)V 451 */ 452 JNIEXPORT void JNICALL 453 Java_sun_lwawt_macosx_CCheckboxMenuItem_nativeSetState 454 (JNIEnv *env, jobject peer, jlong menuItemObj, jboolean state) 455 { 456 JNF_COCOA_ENTER(env); 457 CMenuItem *item = (CMenuItem *)jlong_to_ptr(menuItemObj); 458 [item setJavaState: (state == JNI_TRUE)]; 459 JNF_COCOA_EXIT(env); 460 } 461 462 /* 463 * Class: sun_lwawt_macosx_CCheckboxMenuItem 464 * Method: nativeSetState 465 * Signature: (IZ)V 466 */ 467 JNIEXPORT void JNICALL 468 Java_sun_lwawt_macosx_CCheckboxMenuItem_nativeSetIsCheckbox 469 (JNIEnv *env, jobject peer, jlong menuItemObj) 470 { 471 JNF_COCOA_ENTER(env); 472 CMenuItem *item = (CMenuItem *)jlong_to_ptr(menuItemObj); 473 [item setIsCheckbox]; 474 JNF_COCOA_EXIT(env); 475 }