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 }