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