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