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 }