1 /* 2 * Copyright (c) 2011, 2018, 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 "JNIUtilities.h" 27 28 #import <AppKit/AppKit.h> 29 #import <JavaRuntimeSupport/JavaRuntimeSupport.h> 30 31 32 #import "CMenuBar.h" 33 #import "CMenu.h" 34 #import "ThreadUtilities.h" 35 #import "ApplicationDelegate.h" 36 37 #import "sun_lwawt_macosx_CMenuBar.h" 38 39 __attribute__((visibility("default"))) 40 NSString *CMenuBarDidReuseItemNotification = 41 @"CMenuBarDidReuseItemNotification"; 42 43 static CMenuBar *sActiveMenuBar = nil; 44 static NSMenu *sDefaultHelpMenu = nil; 45 static BOOL sSetupHelpMenu = NO; 46 47 @interface CMenuBar (CMenuBar_Private) 48 + (void) addDefaultHelpMenu; 49 @end 50 51 @implementation CMenuBar 52 53 + (void)clearMenuBarExcludingAppleMenu_OnAppKitThread:(BOOL) excludingAppleMenu { 54 AWT_ASSERT_APPKIT_THREAD; 55 // Remove all Java menus from the main bar. 56 NSMenu *theMainMenu = [NSApp mainMenu]; 57 NSUInteger i, menuCount = [theMainMenu numberOfItems]; 58 59 for (i = menuCount; i > 1; i--) { 60 NSUInteger index = i-1; 61 62 NSMenuItem *currItem = [theMainMenu itemAtIndex:index]; 63 NSMenu *currMenu = [currItem submenu]; 64 65 if (excludingAppleMenu && ![currMenu isJavaMenu]) { 66 continue; 67 } 68 [currItem setSubmenu:nil]; 69 [theMainMenu removeItemAtIndex:index]; 70 } 71 72 [CMenuBar addDefaultHelpMenu]; 73 } 74 75 + (BOOL) isActiveMenuBar:(CMenuBar *)inMenuBar { 76 return (sActiveMenuBar == inMenuBar); 77 } 78 79 - (id) initWithPeer:(jobject)peer { 80 AWT_ASSERT_APPKIT_THREAD; 81 self = [super initWithPeer: peer]; 82 if (self) { 83 fMenuList = [[NSMutableArray alloc] init]; 84 } 85 return self; 86 } 87 88 -(void) dealloc { 89 [fMenuList release]; 90 fMenuList = nil; 91 92 [fHelpMenu release]; 93 fHelpMenu = nil; 94 95 [super dealloc]; 96 } 97 98 + (void) activate:(CMenuBar *)menubar modallyDisabled:(BOOL)modallyDisabled { 99 AWT_ASSERT_APPKIT_THREAD; 100 101 if (!menubar) { 102 [CMenuBar clearMenuBarExcludingAppleMenu_OnAppKitThread:YES]; 103 return; 104 } 105 106 #ifdef DEBUG 107 NSLog(@"activating menu bar: %@", menubar); 108 #endif 109 110 @synchronized([CMenuBar class]) { 111 sActiveMenuBar = menubar; 112 } 113 114 @synchronized(menubar) { 115 menubar->fModallyDisabled = modallyDisabled; 116 } 117 118 NSUInteger i = 0, newMenuListSize = [menubar->fMenuList count]; 119 120 NSMenu *theMainMenu = [NSApp mainMenu]; 121 NSUInteger menuIndex, menuCount = [theMainMenu numberOfItems]; 122 123 NSUInteger cmenuIndex = 0, cmenuCount = newMenuListSize; 124 NSMutableArray *removedMenuArray = [NSMutableArray array]; 125 126 for (menuIndex = 0; menuIndex < menuCount; menuIndex++) { 127 NSMenuItem *currItem = [theMainMenu itemAtIndex:menuIndex]; 128 NSMenu *currMenu = [currItem submenu]; 129 130 if ([currMenu isJavaMenu]) { 131 // Ready to replace, find next candidate 132 CMenu *newMenu = nil; 133 if (cmenuIndex < cmenuCount) { 134 newMenu = (CMenu *)[menubar->fMenuList objectAtIndex:cmenuIndex]; 135 if (newMenu == menubar->fHelpMenu) { 136 cmenuIndex++; 137 if (cmenuIndex < cmenuCount) { 138 newMenu = (CMenu *)[menubar->fMenuList objectAtIndex:cmenuIndex]; 139 } 140 } 141 } 142 if (newMenu) { 143 NSMenu *menuToAdd = [newMenu menu]; 144 if ([theMainMenu indexOfItemWithSubmenu:menuToAdd] == -1) { 145 [[NSNotificationCenter defaultCenter] postNotificationName:CMenuBarDidReuseItemNotification object:theMainMenu]; 146 147 [currItem setSubmenu:menuToAdd]; 148 [currItem setTitle:[menuToAdd title]]; 149 cmenuIndex++; 150 } 151 152 BOOL newEnabledState = [newMenu isEnabled] && !menubar->fModallyDisabled; 153 [currItem setEnabled:newEnabledState]; 154 } else { 155 [removedMenuArray addObject:[NSNumber numberWithInteger:menuIndex]]; 156 } 157 } 158 } 159 160 // Clean up extra items 161 NSUInteger removedIndex, removedCount = [removedMenuArray count]; 162 for (removedIndex=removedCount; removedIndex > 0; removedIndex--) { 163 NSUInteger index = [[removedMenuArray objectAtIndex:(removedIndex-1)] integerValue]; 164 NSMenuItem *currItem = [theMainMenu itemAtIndex:index]; 165 [currItem setSubmenu:nil]; 166 [theMainMenu removeItemAtIndex:index]; 167 } 168 169 i = cmenuIndex; 170 171 // Add all of the menus in the menu list. 172 for (; i < newMenuListSize; i++) { 173 CMenu *newMenu = (CMenu *)[menubar->fMenuList objectAtIndex:i]; 174 175 if (newMenu != menubar->fHelpMenu) { 176 NSArray *args = [NSArray arrayWithObjects:newMenu, [NSNumber numberWithInt:-1], nil]; 177 [menubar nativeAddMenuAtIndex_OnAppKitThread:args]; 178 } 179 } 180 181 // Add the help menu last. 182 if (menubar->fHelpMenu) { 183 NSArray *args = [NSArray arrayWithObjects:menubar->fHelpMenu, [NSNumber numberWithInt:-1], nil]; 184 [menubar nativeAddMenuAtIndex_OnAppKitThread:args]; 185 } else { 186 [CMenuBar addDefaultHelpMenu]; 187 } 188 } 189 190 -(void) deactivate { 191 AWT_ASSERT_APPKIT_THREAD; 192 193 BOOL isDeactivated = NO; 194 @synchronized([CMenuBar class]) { 195 if (sActiveMenuBar == self) { 196 sActiveMenuBar = nil; 197 isDeactivated = YES; 198 } 199 } 200 201 if (isDeactivated) { 202 #ifdef DEBUG 203 NSLog(@"deactivating menu bar: %@", self); 204 #endif 205 206 @synchronized(self) { 207 fModallyDisabled = NO; 208 } 209 210 // In theory, this might cause flickering if the window gaining focus 211 // has its own menu. However, I couldn't reproduce it on practice, so 212 // perhaps this is a non issue. 213 CMenuBar* defaultMenu = [[ApplicationDelegate sharedDelegate] defaultMenuBar]; 214 if (defaultMenu != nil) { 215 [CMenuBar activate:defaultMenu modallyDisabled:NO]; 216 } 217 } 218 } 219 220 -(void) javaAddMenu: (CMenu *)theMenu { 221 @synchronized(self) { 222 [fMenuList addObject: theMenu]; 223 } 224 225 if (self == sActiveMenuBar) { 226 NSArray *args = [[NSArray alloc] initWithObjects:theMenu, [NSNumber numberWithInt:-1], nil]; 227 [ThreadUtilities performOnMainThread:@selector(nativeAddMenuAtIndex_OnAppKitThread:) on:self withObject:args waitUntilDone:YES]; 228 [args release]; 229 } 230 } 231 232 // This method is a special case for use by the screen menu bar. 233 // See ScreenMenuBar.java -- used to implement setVisible(boolean) by 234 // removing or adding the menu from the current menu bar's list. 235 -(void) javaAddMenu: (CMenu *)theMenu atIndex:(jint)index { 236 @synchronized(self) { 237 if (index == -1){ 238 [fMenuList addObject:theMenu]; 239 }else{ 240 [fMenuList insertObject:theMenu atIndex:index]; 241 } 242 } 243 244 if (self == sActiveMenuBar) { 245 NSArray *args = [[NSArray alloc] initWithObjects:theMenu, [NSNumber numberWithInt:index], nil]; 246 [ThreadUtilities performOnMainThread:@selector(nativeAddMenuAtIndex_OnAppKitThread:) on:self withObject:args waitUntilDone:YES]; 247 [args release]; 248 } 249 } 250 251 - (NSInteger) javaIndexToNSMenuIndex_OnAppKitThread:(jint)javaIndex { 252 AWT_ASSERT_APPKIT_THREAD; 253 NSInteger returnValue = -1; 254 NSMenu *theMainMenu = [NSApp mainMenu]; 255 256 if (javaIndex == -1) { 257 if (fHelpMenu) { 258 returnValue = [theMainMenu indexOfItemWithSubmenu:[fHelpMenu menu]]; 259 } 260 } else { 261 CMenu *requestedMenu = [fMenuList objectAtIndex:javaIndex]; 262 263 if (requestedMenu == fHelpMenu) { 264 returnValue = [theMainMenu indexOfItemWithSubmenu:[fHelpMenu menu]]; 265 } else { 266 NSUInteger i, menuCount = [theMainMenu numberOfItems]; 267 jint currJavaMenuIndex = 0; 268 for (i = 0; i < menuCount; i++) { 269 NSMenuItem *currItem = [theMainMenu itemAtIndex:i]; 270 NSMenu *currMenu = [currItem submenu]; 271 272 if ([currMenu isJavaMenu]) { 273 if (javaIndex == currJavaMenuIndex) { 274 returnValue = i; 275 break; 276 } 277 278 currJavaMenuIndex++; 279 } 280 } 281 } 282 } 283 284 return returnValue; 285 } 286 287 - (void) nativeAddMenuAtIndex_OnAppKitThread:(NSArray *)args { 288 AWT_ASSERT_APPKIT_THREAD; 289 CMenu *theNewMenu = (CMenu*)[args objectAtIndex:0]; 290 jint index = [(NSNumber*)[args objectAtIndex:1] intValue]; 291 NSApplication *theApp = [NSApplication sharedApplication]; 292 NSMenu *theMainMenu = [theApp mainMenu]; 293 NSMenu *menuToAdd = [theNewMenu menu]; 294 295 if ([theMainMenu indexOfItemWithSubmenu:menuToAdd] == -1) { 296 NSMenuItem *newItem = [[NSMenuItem alloc] init]; 297 [newItem setSubmenu:[theNewMenu menu]]; 298 [newItem setTitle:[[theNewMenu menu] title]]; 299 300 NSInteger nsMenuIndex = [self javaIndexToNSMenuIndex_OnAppKitThread:index]; 301 302 if (nsMenuIndex == -1) { 303 [theMainMenu addItem:newItem]; 304 } else { 305 [theMainMenu insertItem:newItem atIndex:nsMenuIndex]; 306 } 307 308 BOOL newEnabledState = [theNewMenu isEnabled] && !fModallyDisabled; 309 [newItem setEnabled:newEnabledState]; 310 [newItem release]; 311 } 312 } 313 314 - (void) javaDeleteMenu: (jint)index { 315 if (self == sActiveMenuBar) { 316 [ThreadUtilities performOnMainThread:@selector(nativeDeleteMenu_OnAppKitThread:) on:self withObject:[NSNumber numberWithInt:index] waitUntilDone:YES]; 317 } 318 319 @synchronized(self) { 320 CMenu *menuToRemove = [fMenuList objectAtIndex:index]; 321 322 if (menuToRemove == fHelpMenu) { 323 [fHelpMenu release]; 324 fHelpMenu = nil; 325 } 326 327 [fMenuList removeObjectAtIndex:index]; 328 } 329 } 330 331 - (void) nativeDeleteMenu_OnAppKitThread:(id)indexObj { 332 AWT_ASSERT_APPKIT_THREAD; 333 NSApplication *theApp = [NSApplication sharedApplication]; 334 NSMenu *theMainMenu = [theApp mainMenu]; 335 jint menuToRemove = [(NSNumber *)indexObj intValue]; 336 NSInteger nsMenuToRemove = [self javaIndexToNSMenuIndex_OnAppKitThread:menuToRemove]; 337 338 if (nsMenuToRemove != -1) { 339 [theMainMenu removeItemAtIndex:nsMenuToRemove]; 340 } 341 } 342 343 - (void) javaSetHelpMenu:(CMenu *)theMenu { 344 @synchronized(self) { 345 [theMenu retain]; 346 [fHelpMenu release]; 347 fHelpMenu = theMenu; 348 } 349 } 350 351 + (void) addDefaultHelpMenu { 352 AWT_ASSERT_APPKIT_THREAD; 353 354 // Look for a help book tag. If it's there, add the help menu. 355 @synchronized ([CMenuBar class]) { 356 if (!sSetupHelpMenu) { 357 if (sDefaultHelpMenu == nil) { 358 // If we are embedded, don't make a help menu. 359 // TODO(cpc): we don't have NSApplicationAWT yet... 360 //if (![NSApp isKindOfClass:[NSApplicationAWT class]]) { 361 // sSetupHelpMenu = YES; 362 // return; 363 //} 364 365 // If the developer specified a NIB, don't make a help menu. 366 // TODO(cpc): usingDefaultNib only defined on NSApplicationAWT 367 //if (![NSApp usingDefaultNib]) { 368 // sSetupHelpMenu = YES; 369 // return; 370 //} 371 372 // TODO: not implemented 373 } 374 375 sSetupHelpMenu = YES; 376 } 377 } 378 379 if (sDefaultHelpMenu) { 380 NSMenu *theMainMenu = [NSApp mainMenu]; 381 382 if ([theMainMenu indexOfItemWithSubmenu:sDefaultHelpMenu] == -1) { 383 // Since we're re-using this NSMenu, we need to clear its parent before 384 // adding it to a new menu item, or else AppKit will complain. 385 [sDefaultHelpMenu setSupermenu:nil]; 386 387 // Add the help menu to the main menu. 388 NSMenuItem *newItem = [[NSMenuItem alloc] init]; 389 [newItem setSubmenu:sDefaultHelpMenu]; 390 [newItem setTitle:[sDefaultHelpMenu title]]; 391 [theMainMenu addItem:newItem]; 392 393 // Release it so the main menu owns it. 394 [newItem release]; 395 } 396 } 397 } 398 399 @end 400 401 /* 402 * Class: sun_lwawt_macosx_CMenuBar 403 * Method: nativeCreateMenuBar 404 * Signature: ()J 405 */ 406 JNIEXPORT jlong JNICALL 407 Java_sun_lwawt_macosx_CMenuBar_nativeCreateMenuBar 408 (JNIEnv *env, jobject peer) 409 { 410 __block CMenuBar *aCMenuBar = nil; 411 JNI_COCOA_ENTER(env); 412 413 jobject cPeerObjGlobal = (*env)->NewGlobalRef(env, peer); 414 415 [ThreadUtilities performOnMainThreadWaiting:YES block:^(){ 416 417 aCMenuBar = [[CMenuBar alloc] initWithPeer:cPeerObjGlobal]; 418 // the aCMenuBar is released in CMenuComponent.dispose() 419 }]; 420 if (aCMenuBar == nil) { 421 return 0L; 422 } 423 424 JNI_COCOA_EXIT(env); 425 return ptr_to_jlong(aCMenuBar); 426 } 427 428 /* 429 * Class: sun_lwawt_macosx_CMenuBar 430 * Method: nativeAddAtIndex 431 * Signature: (JJI)V 432 */ 433 JNIEXPORT void JNICALL 434 Java_sun_lwawt_macosx_CMenuBar_nativeAddAtIndex 435 (JNIEnv *env, jobject peer, 436 jlong menuBarObject, jlong menuObject, jint index) 437 { 438 JNI_COCOA_ENTER(env); 439 // Remove the specified item. 440 [((CMenuBar *) jlong_to_ptr(menuBarObject)) javaAddMenu:(CMenu *) jlong_to_ptr(menuObject) atIndex:index]; 441 JNI_COCOA_EXIT(env); 442 } 443 444 /* 445 * Class: sun_lwawt_macosx_CMenuBar 446 * Method: nativeDelMenu 447 * Signature: (JI)V 448 */ 449 JNIEXPORT void JNICALL 450 Java_sun_lwawt_macosx_CMenuBar_nativeDelMenu 451 (JNIEnv *env, jobject peer, jlong menuBarObject, jint index) 452 { 453 JNI_COCOA_ENTER(env); 454 // Remove the specified item. 455 [((CMenuBar *) jlong_to_ptr(menuBarObject)) javaDeleteMenu: index]; 456 JNI_COCOA_EXIT(env); 457 } 458 459 /* 460 * Class: sun_lwawt_macosx_CMenuBar 461 * Method: nativeSetHelpMenu 462 * Signature: (JJ)V 463 */ 464 JNIEXPORT void JNICALL 465 Java_sun_lwawt_macosx_CMenuBar_nativeSetHelpMenu 466 (JNIEnv *env, jobject peer, jlong menuBarObject, jlong menuObject) 467 { 468 JNI_COCOA_ENTER(env); 469 // Remove the specified item. 470 [((CMenuBar *) jlong_to_ptr(menuBarObject)) javaSetHelpMenu: ((CMenu *)jlong_to_ptr(menuObject))]; 471 JNI_COCOA_EXIT(env); 472 }