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 }
--- EOF ---