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 }