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