Skip to content

Commit 944005e

Browse files
authored
Merge pull request #1092 from ychin/fix-duplicate-window-menu-items
Fix duplicate menu items in Window menu
2 parents 8c117fc + d412800 commit 944005e

File tree

3 files changed

+81
-35
lines changed

3 files changed

+81
-35
lines changed

src/MacVim/MMAppController.h

+6
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@
2222
NSMutableArray *vimControllers;
2323
NSString *openSelectionString;
2424
NSMutableDictionary *pidArguments;
25+
2526
NSMenu *defaultMainMenu;
27+
NSMenu *currentMainMenu;
28+
BOOL mainMenuDirty;
29+
2630
NSMenuItem *appMenuItemTemplate;
2731
NSMenuItem *recentFilesMenuItem;
2832
NSMutableArray *cachedVimControllers;
@@ -44,6 +48,8 @@
4448
- (void)removeVimController:(id)controller;
4549
- (void)windowControllerWillOpen:(MMWindowController *)windowController;
4650
- (void)setMainMenu:(NSMenu *)mainMenu;
51+
- (void)markMainMenuDirty:(NSMenu *)mainMenu;
52+
- (void)refreshMainMenu;
4753
- (NSArray *)filterOpenFiles:(NSArray *)filenames;
4854
- (BOOL)openFiles:(NSArray *)filenames withArguments:(NSDictionary *)args;
4955

src/MacVim/MMAppController.m

+56-34
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ - (id)init
299299
ASLogCrit(@"Failed to register connection with name '%@'", name);
300300
[connection release]; connection = nil;
301301
}
302-
302+
303303
#if !DISABLE_SPARKLE
304304
// Sparkle is enabled (this is the default). Initialize it. It will
305305
// automatically check for update.
@@ -321,6 +321,7 @@ - (void)dealloc
321321
[openSelectionString release]; openSelectionString = nil;
322322
[recentFilesMenuItem release]; recentFilesMenuItem = nil;
323323
[defaultMainMenu release]; defaultMainMenu = nil;
324+
currentMainMenu = nil;
324325
[appMenuItemTemplate release]; appMenuItemTemplate = nil;
325326
[updater release]; updater = nil;
326327

@@ -329,6 +330,11 @@ - (void)dealloc
329330

330331
- (void)applicationWillFinishLaunching:(NSNotification *)notification
331332
{
333+
// This prevents macOS from injecting "Enter Full Screen" menu item.
334+
// MacVim already has a separate menu item to do that.
335+
// See https://developer.apple.com/library/archive/releasenotes/AppKit/RN-AppKitOlderNotes/index.html#10_11FullScreen
336+
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"NSFullScreenMenuItemEverywhere"];
337+
332338
// Remember the default menu so that it can be restored if the user closes
333339
// all editor windows.
334340
defaultMainMenu = [[NSApp mainMenu] retain];
@@ -862,7 +868,42 @@ - (void)windowControllerWillOpen:(MMWindowController *)windowController
862868

863869
- (void)setMainMenu:(NSMenu *)mainMenu
864870
{
865-
if ([NSApp mainMenu] == mainMenu) return;
871+
if (currentMainMenu == mainMenu) {
872+
return;
873+
}
874+
currentMainMenu = mainMenu;
875+
[self refreshMainMenu];
876+
}
877+
878+
// Refresh the currently active main menu. This call is necessary when any
879+
// modification was made to the menu, because refreshMainMenu makes a copy of
880+
// the main menu, meaning that modifications to the original menu wouldn't be
881+
// reflected until refreshMainMenu is invoked.
882+
- (void)markMainMenuDirty:(NSMenu *)mainMenu
883+
{
884+
if (currentMainMenu != mainMenu) {
885+
// The menu being updated is not the currently set menu, so just ignore,
886+
// as this is likely a background Vim window.
887+
return;
888+
}
889+
if (!mainMenuDirty) {
890+
// Mark the main menu as dirty and queue up a refresh. We don't immediately
891+
// execute the refresh so that multiple calls would get batched up in one go.
892+
mainMenuDirty = YES;
893+
[self performSelectorOnMainThread:@selector(refreshMainMenu) withObject:nil waitUntilDone:NO];
894+
}
895+
}
896+
897+
- (void)refreshMainMenu
898+
{
899+
mainMenuDirty = NO;
900+
901+
// Make a copy of the menu before we pass to AppKit. The main reason is
902+
// that setWindowsMenu: below will inject items like "Tile Window to Left
903+
// of Screen" to the Window menu, and on repeated calls it will keep adding
904+
// the same item over and over again, without resolving for duplicates. Using
905+
// copies help keep the source menu clean.
906+
NSMenu *mainMenu = [currentMainMenu copy];
866907

867908
// If the new menu has a "Recent Files" dummy item, then swap the real item
868909
// for the dummy. We are forced to do this since Cocoa initializes the
@@ -871,7 +912,7 @@ - (void)setMainMenu:(NSMenu *)mainMenu
871912
NSMenu *fileMenu = [mainMenu findFileMenu];
872913
if (recentFilesMenuItem && fileMenu) {
873914
int dummyIdx =
874-
[fileMenu indexOfItemWithAction:@selector(recentFilesDummy:)];
915+
[fileMenu indexOfItemWithAction:@selector(recentFilesDummy:)];
875916
if (dummyIdx >= 0) {
876917
NSMenuItem *dummyItem = [[fileMenu itemAtIndex:dummyIdx] retain];
877918
[fileMenu removeItemAtIndex:dummyIdx];
@@ -889,45 +930,26 @@ - (void)setMainMenu:(NSMenu *)mainMenu
889930
}
890931
}
891932

892-
// Now set the new menu. Notice that we keep one menu for each editor
893-
// window since each editor can have its own set of menus. When swapping
894-
// menus we have to tell Cocoa where the new "MacVim", "Windows", and
895-
// "Services" menu are.
896-
[NSApp setMainMenu:mainMenu];
897-
898-
// Setting the "MacVim" (or "Application") menu ensures that it is typeset
899-
// in boldface. (The setAppleMenu: method used to be public but is now
900-
// private so this will have to be considered a bit of a hack!)
901-
NSMenu *appMenu = [mainMenu findApplicationMenu];
902-
[NSApp performSelector:@selector(setAppleMenu:) withObject:appMenu];
903-
904933
#if DISABLE_SPARKLE
934+
NSMenu *appMenu = [mainMenu findApplicationMenu];
935+
905936
// If Sparkle is disabled, we want to remove the "Check for Updates" menu
906937
// item since it's no longer useful.
907938
NSMenuItem *checkForUpdatesItem = [appMenu itemAtIndex:
908939
[appMenu indexOfItemWithAction:@selector(checkForUpdates:)]];
909940
checkForUpdatesItem.hidden = true;
910941
#endif
911942

943+
// Now set the new menu. Notice that we keep one menu for each editor
944+
// window since each editor can have its own set of menus. When swapping
945+
// menus we have to tell Cocoa where the new "MacVim", "Windows", and
946+
// "Services" menu are.
947+
[NSApp setMainMenu:mainMenu];
948+
912949
NSMenu *servicesMenu = [mainMenu findServicesMenu];
913950
[NSApp setServicesMenu:servicesMenu];
914951

915952
NSMenu *windowsMenu = [mainMenu findWindowsMenu];
916-
if (windowsMenu) {
917-
// Cocoa isn't clever enough to get rid of items it has added to the
918-
// "Windows" menu so we have to do it ourselves otherwise there will be
919-
// multiple menu items for each window in the "Windows" menu.
920-
// This code assumes that the only items Cocoa add are ones which
921-
// send off the action makeKeyAndOrderFront:. (Cocoa will not add
922-
// another separator item if the last item on the "Windows" menu
923-
// already is a separator, so we needen't worry about separators.)
924-
int i, count = [windowsMenu numberOfItems];
925-
for (i = count-1; i >= 0; --i) {
926-
NSMenuItem *item = [windowsMenu itemAtIndex:i];
927-
if ([item action] == @selector(makeKeyAndOrderFront:))
928-
[windowsMenu removeItem:item];
929-
}
930-
}
931953
[NSApp setWindowsMenu:windowsMenu];
932954
}
933955

@@ -1220,7 +1242,7 @@ - (IBAction)showVimHelp:(id)sender
12201242
@"-c", @":h gui_mac", @"-c", @":res", nil]
12211243
workingDirectory:nil];
12221244
}
1223-
1245+
12241246
- (IBAction)checkForUpdates:(id)sender
12251247
{
12261248
#if !DISABLE_SPARKLE
@@ -1816,7 +1838,7 @@ - (void)handleGetURLEvent:(NSAppleEventDescriptor *)event
18161838
// Only opens files that already exist.
18171839
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
18181840
NSArray *filenames = [NSArray arrayWithObject:filePath];
1819-
1841+
18201842
// Look for the line and column options.
18211843
NSDictionary *args = nil;
18221844
NSString *line = [dict objectForKey:@"line"];
@@ -1831,7 +1853,7 @@ - (void)handleGetURLEvent:(NSAppleEventDescriptor *)event
18311853
args = [NSDictionary dictionaryWithObject:line
18321854
forKey:@"cursorLine"];
18331855
}
1834-
1856+
18351857
[self openFiles:filenames withArguments:args];
18361858
} else {
18371859
NSAlert *alert = [[NSAlert alloc] init];
@@ -2198,7 +2220,7 @@ - (void)startWatchingVimDir
21982220

21992221
NSString *path = [@"~/.vim" stringByExpandingTildeInPath];
22002222
NSArray *pathsToWatch = [NSArray arrayWithObject:path];
2201-
2223+
22022224
fsEventStream = FSEventStreamCreate(NULL, &fsEventCallback, NULL,
22032225
(CFArrayRef)pathsToWatch, kFSEventStreamEventIdSinceNow,
22042226
MMEventStreamLatency, kFSEventStreamCreateFlagNone);

src/MacVim/MMVimController.m

+19-1
Original file line numberDiff line numberDiff line change
@@ -1251,7 +1251,8 @@ - (void)addMenuWithDescriptor:(NSArray *)desc atIndex:(int)idx
12511251
[item setSubmenu:menu];
12521252

12531253
NSMenu *parent = [self parentMenuForDescriptor:desc];
1254-
if (!parent && [MMVimController hasPopupPrefix:rootName]) {
1254+
const BOOL isPopup = [MMVimController hasPopupPrefix:rootName];
1255+
if (!parent && isPopup) {
12551256
if ([popupMenuItems count] <= idx) {
12561257
[popupMenuItems addObject:item];
12571258
} else {
@@ -1271,6 +1272,8 @@ - (void)addMenuWithDescriptor:(NSArray *)desc atIndex:(int)idx
12711272

12721273
[item release];
12731274
[menu release];
1275+
if (!isPopup)
1276+
[[MMAppController sharedInstance] markMainMenuDirty:mainMenu];
12741277
}
12751278

12761279
- (void)addMenuItemWithDescriptor:(NSArray *)desc
@@ -1350,6 +1353,9 @@ - (void)addMenuItemWithDescriptor:(NSArray *)desc
13501353
} else {
13511354
[parent insertItem:item atIndex:idx];
13521355
}
1356+
const BOOL isPopup = [MMVimController hasPopupPrefix:rootName];
1357+
if (!isPopup)
1358+
[[MMAppController sharedInstance] markMainMenuDirty:mainMenu];
13531359
}
13541360

13551361
- (void)removeMenuItemWithDescriptor:(NSArray *)desc
@@ -1405,6 +1411,10 @@ - (void)removeMenuItemWithDescriptor:(NSArray *)desc
14051411
[[item menu] removeItem:item];
14061412

14071413
[item release];
1414+
1415+
const BOOL isPopup = [MMVimController hasPopupPrefix:rootName];
1416+
if (!isPopup)
1417+
[[MMAppController sharedInstance] markMainMenuDirty:mainMenu];
14081418
}
14091419

14101420
- (void)enableMenuItemWithDescriptor:(NSArray *)desc state:(BOOL)on
@@ -1439,6 +1449,10 @@ - (void)enableMenuItemWithDescriptor:(NSArray *)desc state:(BOOL)on
14391449
// but at the same time Vim can set if a menu is enabled whenever it
14401450
// wants to.
14411451
[[self menuItemForDescriptor:desc] setTag:on];
1452+
1453+
const BOOL isPopup = [MMVimController hasPopupPrefix:rootName];
1454+
if (!isPopup)
1455+
[[MMAppController sharedInstance] markMainMenuDirty:mainMenu];
14421456
}
14431457

14441458
- (void)updateMenuItemTooltipWithDescriptor:(NSArray *)desc
@@ -1471,6 +1485,10 @@ - (void)updateMenuItemTooltipWithDescriptor:(NSArray *)desc
14711485
}
14721486

14731487
[[self menuItemForDescriptor:desc] setToolTip:tip];
1488+
1489+
const BOOL isPopup = [MMVimController hasPopupPrefix:rootName];
1490+
if (!isPopup)
1491+
[[MMAppController sharedInstance] markMainMenuDirty:mainMenu];
14741492
}
14751493

14761494
- (void)addToolbarItemToDictionaryWithLabel:(NSString *)title

0 commit comments

Comments
 (0)