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 }