/* * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ #import #import "CMenuItem.h" #import "CMenu.h" #import "AWTEvent.h" #import "ThreadUtilities.h" #import "java_awt_Event.h" #import "java_awt_event_KeyEvent.h" #import "sun_lwawt_macosx_CMenuItem.h" #define NOT_A_CHECKBOXMENU -2 @implementation CMenuItem - (id) initWithPeer:(jobject)peer asSeparator: (NSNumber *) asSeparator{ AWT_ASSERT_APPKIT_THREAD; self = [super initWithPeer:peer]; if (self) { if ([asSeparator boolValue]) { fMenuItem = (NSMenuItem*)[NSMenuItem separatorItem]; [fMenuItem retain]; } else { fMenuItem = [[NSMenuItem alloc] init]; [fMenuItem setAction:@selector(handleAction:)]; [fMenuItem setTarget:self]; } fIsCheckbox = NO; fIsEnabled = YES; } return self; } // This is because NSApplication doesn't check the target's window when sending // actions; they only check the target itself. We always return YES, // since we shouldn't even be installed unless our window is active. - (BOOL) worksWhenModal { return YES; } // Events - (void)handleAction:(NSMenuItem *)sender { AWT_ASSERT_APPKIT_THREAD; JNIEnv *env = [ThreadUtilities getJNIEnv]; JNF_COCOA_ENTER(env); // If we are called as a result of user pressing a shorcut, do nothing, // because AVTView has already sent corresponding key event to the Java // layer from performKeyEquivalent NSEvent *currEvent = [[NSApplication sharedApplication] currentEvent]; if ([currEvent type] == NSKeyDown) { NSString *menuKey = [sender keyEquivalent]; NSString *eventKey = [currEvent characters]; if ([menuKey isEqualToString:eventKey]) { return; } } if (fIsCheckbox) { static JNF_CLASS_CACHE(jc_CCheckboxMenuItem, "sun/lwawt/macosx/CCheckboxMenuItem"); static JNF_MEMBER_CACHE(jm_ckHandleAction, jc_CCheckboxMenuItem, "handleAction", "(Z)V"); // Send the opposite of what's currently checked -- the action // indicates what state we're going to. NSInteger state = [sender state]; jboolean newState = (state == NSOnState ? JNI_FALSE : JNI_TRUE); JNFCallVoidMethod(env, fPeer, jm_ckHandleAction, newState); } else { static JNF_CLASS_CACHE(jc_CMenuItem, "sun/lwawt/macosx/CMenuItem"); static JNF_MEMBER_CACHE(jm_handleAction, jc_CMenuItem, "handleAction", "(JI)V"); // AWT_THREADING Safe (event) NSUInteger modifiers = [currEvent modifierFlags]; jint javaModifiers = NsKeyModifiersToJavaModifiers(modifiers, NO); JNFCallVoidMethod(env, fPeer, jm_handleAction, UTC(currEvent), javaModifiers); // AWT_THREADING Safe (event) } JNF_COCOA_EXIT(env); } - (void) setJavaLabel:(NSString *)theLabel shortcut:(NSString *)theKeyEquivalent modifierMask:(jint)modifiers { AWT_ASSERT_NOT_APPKIT_THREAD; NSUInteger modifierMask = 0; if (![theKeyEquivalent isEqualToString:@""]) { // Force the key equivalent to lower case if not using the shift key. // Otherwise AppKit will draw a Shift glyph in the menu. if ((modifiers & java_awt_event_KeyEvent_SHIFT_MASK) == 0) { theKeyEquivalent = [theKeyEquivalent lowercaseString]; } // Hack for the question mark -- SHIFT and / means use the question mark. if ((modifiers & java_awt_event_KeyEvent_SHIFT_MASK) != 0 && [theKeyEquivalent isEqualToString:@"/"]) { theKeyEquivalent = @"?"; modifiers &= ~java_awt_event_KeyEvent_SHIFT_MASK; } modifierMask = JavaModifiersToNsKeyModifiers(modifiers, NO); } [JNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){ AWT_ASSERT_APPKIT_THREAD; [fMenuItem setKeyEquivalent:theKeyEquivalent]; [fMenuItem setKeyEquivalentModifierMask:modifierMask]; [fMenuItem setTitle:theLabel]; }]; } - (void) setJavaImage:(NSImage *)theImage { AWT_ASSERT_NOT_APPKIT_THREAD; [JNFRunLoop performOnMainThreadWaiting:NO withBlock:^(){ AWT_ASSERT_APPKIT_THREAD; [fMenuItem setImage:theImage]; }]; } - (void) setJavaToolTipText:(NSString *)theText { AWT_ASSERT_NOT_APPKIT_THREAD; [JNFRunLoop performOnMainThreadWaiting:NO withBlock:^(){ AWT_ASSERT_APPKIT_THREAD; [fMenuItem setToolTip:theText]; }]; } - (void)setJavaEnabled:(BOOL) enabled { AWT_ASSERT_NOT_APPKIT_THREAD; [JNFRunLoop performOnMainThreadWaiting:NO withBlock:^(){ AWT_ASSERT_APPKIT_THREAD; @synchronized(self) { fIsEnabled = enabled; // Warning: This won't work if the parent menu is disabled. // See [CMenu syncFromJava]. We still need to call it here so // the NSMenuItem itself gets properly updated. [fMenuItem setEnabled:fIsEnabled]; } }]; } - (BOOL)isEnabled { // AWT_ASSERT_ANY_THREAD; BOOL enabled = NO; @synchronized(self) { enabled = fIsEnabled; } return enabled; } - (void)setJavaState:(BOOL)newState { AWT_ASSERT_NOT_APPKIT_THREAD; [JNFRunLoop performOnMainThreadWaiting:NO withBlock:^(){ AWT_ASSERT_APPKIT_THREAD; [fMenuItem setState:(newState ? NSOnState : NSOffState)]; }]; } - (void)cleanup { [fMenuItem setAction:NULL]; [fMenuItem setTarget:nil]; } - (void)dealloc { [fMenuItem release]; fMenuItem = nil; [super dealloc]; } - (void)addNSMenuItemToMenu:(NSMenu *)inMenu { [inMenu addItem:fMenuItem]; } - (NSMenuItem *)menuItem { return [[fMenuItem retain] autorelease]; } - (void)setIsCheckbox { fIsCheckbox = YES; } - (void) _createMenuItem_OnAppKitThread: (NSMutableArray *)argValue { jobject cPeerObjGlobal = (jobject)[[argValue objectAtIndex: 0] pointerValue]; NSNumber * asSeparator = (NSNumber *)[argValue objectAtIndex: 1]; CMenuItem *aCMenuItem = [self initWithPeer: cPeerObjGlobal asSeparator: asSeparator]; [argValue removeAllObjects]; [argValue addObject: aCMenuItem]; } - (NSString *)description { return [NSString stringWithFormat:@"CMenuItem[ %@ ]", fMenuItem]; } @end /** Convert a Java keycode for SetMenuItemCmd */ static unichar AWTKeyToMacShortcut(jint awtKey, BOOL doShift) { unichar macKey = 0; if ((awtKey >= java_awt_event_KeyEvent_VK_0 && awtKey <= java_awt_event_KeyEvent_VK_9) || (awtKey >= java_awt_event_KeyEvent_VK_A && awtKey <= java_awt_event_KeyEvent_VK_Z)) { // These ranges are the same in ASCII macKey = awtKey; } else if (awtKey >= java_awt_event_KeyEvent_VK_F1 && awtKey <= java_awt_event_KeyEvent_VK_F12) { // Support for F1 - F12 has been around since Java 1.0 and fall into a lower range. macKey = awtKey - java_awt_event_KeyEvent_VK_F1 + NSF1FunctionKey; } else if (awtKey >= java_awt_event_KeyEvent_VK_F13 && awtKey <= java_awt_event_KeyEvent_VK_F24) { // Support for F13-F24 came in Java 1.2 and are at a different range. macKey = awtKey - java_awt_event_KeyEvent_VK_F13 + NSF13FunctionKey; } else { // Special characters switch (awtKey) { case java_awt_event_KeyEvent_VK_BACK_QUOTE : macKey = '`'; break; case java_awt_event_KeyEvent_VK_QUOTE : macKey = '\''; break; case java_awt_event_KeyEvent_VK_ESCAPE : macKey = 0x1B; break; // case java_awt_event_KeyEvent_VK_SPACE : macKey = kMenuSpaceGlyph; break; case java_awt_event_KeyEvent_VK_PAGE_UP : macKey = NSPageUpFunctionKey; break; case java_awt_event_KeyEvent_VK_PAGE_DOWN : macKey = NSPageDownFunctionKey; break; case java_awt_event_KeyEvent_VK_END : macKey = NSEndFunctionKey; break; case java_awt_event_KeyEvent_VK_HOME : macKey = NSHomeFunctionKey; break; case java_awt_event_KeyEvent_VK_LEFT : macKey = NSLeftArrowFunctionKey; break; case java_awt_event_KeyEvent_VK_UP : macKey = NSUpArrowFunctionKey; break; case java_awt_event_KeyEvent_VK_RIGHT : macKey = NSRightArrowFunctionKey; break; case java_awt_event_KeyEvent_VK_DOWN : macKey = NSDownArrowFunctionKey; break; case java_awt_event_KeyEvent_VK_COMMA : macKey = ','; break; // Mac OS doesn't distinguish between the two '-' keys... case java_awt_event_KeyEvent_VK_MINUS : case java_awt_event_KeyEvent_VK_SUBTRACT : macKey = '-'; break; // or the two '.' keys... case java_awt_event_KeyEvent_VK_DECIMAL : case java_awt_event_KeyEvent_VK_PERIOD : macKey = '.'; break; // or the two '/' keys. case java_awt_event_KeyEvent_VK_DIVIDE : case java_awt_event_KeyEvent_VK_SLASH : macKey = '/'; break; case java_awt_event_KeyEvent_VK_SEMICOLON : macKey = ';'; break; case java_awt_event_KeyEvent_VK_EQUALS : macKey = '='; break; case java_awt_event_KeyEvent_VK_OPEN_BRACKET : macKey = '['; break; case java_awt_event_KeyEvent_VK_BACK_SLASH : macKey = '\\'; break; case java_awt_event_KeyEvent_VK_CLOSE_BRACKET : macKey = ']'; break; case java_awt_event_KeyEvent_VK_MULTIPLY : macKey = '*'; break; case java_awt_event_KeyEvent_VK_ADD : macKey = '+'; break; case java_awt_event_KeyEvent_VK_HELP : macKey = NSHelpFunctionKey; break; case java_awt_event_KeyEvent_VK_TAB : macKey = NSTabCharacter; break; case java_awt_event_KeyEvent_VK_ENTER : macKey = NSCarriageReturnCharacter; break; case java_awt_event_KeyEvent_VK_BACK_SPACE : macKey = NSBackspaceCharacter; break; case java_awt_event_KeyEvent_VK_DELETE : macKey = NSDeleteCharacter; break; case java_awt_event_KeyEvent_VK_CLEAR : macKey = NSClearDisplayFunctionKey; break; case java_awt_event_KeyEvent_VK_AMPERSAND : macKey = '&'; break; case java_awt_event_KeyEvent_VK_ASTERISK : macKey = '*'; break; case java_awt_event_KeyEvent_VK_QUOTEDBL : macKey = '\"'; break; case java_awt_event_KeyEvent_VK_LESS : macKey = '<'; break; case java_awt_event_KeyEvent_VK_GREATER : macKey = '>'; break; case java_awt_event_KeyEvent_VK_BRACELEFT : macKey = '{'; break; case java_awt_event_KeyEvent_VK_BRACERIGHT : macKey = '}'; break; case java_awt_event_KeyEvent_VK_AT : macKey = '@'; break; case java_awt_event_KeyEvent_VK_COLON : macKey = ':'; break; case java_awt_event_KeyEvent_VK_CIRCUMFLEX : macKey = '^'; break; case java_awt_event_KeyEvent_VK_DOLLAR : macKey = '$'; break; case java_awt_event_KeyEvent_VK_EXCLAMATION_MARK : macKey = '!'; break; case java_awt_event_KeyEvent_VK_LEFT_PARENTHESIS : macKey = '('; break; case java_awt_event_KeyEvent_VK_NUMBER_SIGN : macKey = '#'; break; case java_awt_event_KeyEvent_VK_PLUS : macKey = '+'; break; case java_awt_event_KeyEvent_VK_RIGHT_PARENTHESIS: macKey = ')'; break; case java_awt_event_KeyEvent_VK_UNDERSCORE : macKey = '_'; break; } } return macKey; } /* * Class: sun_lwawt_macosx_CMenuItem * Method: nativeSetLabel * Signature: (JLjava/lang/String;CII)V */ JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CMenuItem_nativeSetLabel (JNIEnv *env, jobject peer, jlong menuItemObj, jstring label, jchar shortcutKey, jint shortcutKeyCode, jint mods) { JNF_COCOA_ENTER(env); NSString *theLabel = JNFJavaToNSString(env, label); NSString *theKeyEquivalent = nil; unichar macKey = shortcutKey; if (macKey == 0) { macKey = AWTKeyToMacShortcut(shortcutKeyCode, (mods & java_awt_event_KeyEvent_SHIFT_MASK) != 0); } if (macKey != 0) { unichar equivalent[1] = {macKey}; theKeyEquivalent = [NSString stringWithCharacters:equivalent length:1]; } else { theKeyEquivalent = @""; } [((CMenuItem *)jlong_to_ptr(menuItemObj)) setJavaLabel:theLabel shortcut:theKeyEquivalent modifierMask:mods]; JNF_COCOA_EXIT(env); } /* * Class: sun_lwawt_macosx_CMenuItem * Method: nativeSetTooltip * Signature: (JLjava/lang/String;)V */ JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CMenuItem_nativeSetTooltip (JNIEnv *env, jobject peer, jlong menuItemObj, jstring tooltip) { JNF_COCOA_ENTER(env); NSString *theTooltip = JNFJavaToNSString(env, tooltip); [((CMenuItem *)jlong_to_ptr(menuItemObj)) setJavaToolTipText:theTooltip]; JNF_COCOA_EXIT(env); } /* * Class: sun_lwawt_macosx_CMenuItem * Method: nativeSetImage * Signature: (JJ)V */ JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CMenuItem_nativeSetImage (JNIEnv *env, jobject peer, jlong menuItemObj, jlong image) { JNF_COCOA_ENTER(env); [((CMenuItem *)jlong_to_ptr(menuItemObj)) setJavaImage:(NSImage*)jlong_to_ptr(image)]; JNF_COCOA_EXIT(env); } /* * Class: sun_lwawt_macosx_CMenuItem * Method: nativeCreate * Signature: (JZ)J */ JNIEXPORT jlong JNICALL Java_sun_lwawt_macosx_CMenuItem_nativeCreate (JNIEnv *env, jobject peer, jlong parentCMenuObj, jboolean isSeparator) { CMenuItem *aCMenuItem = nil; CMenu *parentCMenu = (CMenu *)jlong_to_ptr(parentCMenuObj); JNF_COCOA_ENTER(env); jobject cPeerObjGlobal = (*env)->NewGlobalRef(env, peer); NSMutableArray *args = nil; // Create a new item.... if (isSeparator == JNI_TRUE) { args = [[NSMutableArray alloc] initWithObjects:[NSValue valueWithBytes:&cPeerObjGlobal objCType:@encode(jobject)], [NSNumber numberWithBool:YES], nil]; } else { args = [[NSMutableArray alloc] initWithObjects:[NSValue valueWithBytes:&cPeerObjGlobal objCType:@encode(jobject)], [NSNumber numberWithBool:NO], nil]; } [ThreadUtilities performOnMainThread:@selector(_createMenuItem_OnAppKitThread:) onObject:[CMenuItem alloc] withObject:args waitUntilDone:YES awtMode:YES]; aCMenuItem = (CMenuItem *)[args objectAtIndex: 0]; if (aCMenuItem == nil) { return 0L; } // and add it to the parent item. [parentCMenu addJavaMenuItem: aCMenuItem]; // setLabel will be called after creation completes. if (aCMenuItem) { CFRetain(aCMenuItem); // GC [aCMenuItem release]; } JNF_COCOA_EXIT(env); return ptr_to_jlong(aCMenuItem); } /* * Class: sun_lwawt_macosx_CMenuItem * Method: nativeSetEnabled * Signature: (JZ)V */ JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CMenuItem_nativeSetEnabled (JNIEnv *env, jobject peer, jlong menuItemObj, jboolean enable) { JNF_COCOA_ENTER(env); CMenuItem *item = (CMenuItem *)jlong_to_ptr(menuItemObj); [item setJavaEnabled: (enable == JNI_TRUE)]; JNF_COCOA_EXIT(env); } /* * Class: sun_lwawt_macosx_CCheckboxMenuItem * Method: nativeSetState * Signature: (IZ)V */ JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CCheckboxMenuItem_nativeSetState (JNIEnv *env, jobject peer, jlong menuItemObj, jboolean state) { JNF_COCOA_ENTER(env); CMenuItem *item = (CMenuItem *)jlong_to_ptr(menuItemObj); [item setJavaState: (state == JNI_TRUE)]; JNF_COCOA_EXIT(env); } /* * Class: sun_lwawt_macosx_CCheckboxMenuItem * Method: nativeSetState * Signature: (IZ)V */ JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CCheckboxMenuItem_nativeSetIsCheckbox (JNIEnv *env, jobject peer, jlong menuItemObj) { JNF_COCOA_ENTER(env); CMenuItem *item = (CMenuItem *)jlong_to_ptr(menuItemObj); [item setIsCheckbox]; JNF_COCOA_EXIT(env); }