diff --git a/src/MacVim/English.lproj/MainMenu.nib/classes.nib b/src/MacVim/English.lproj/MainMenu.nib/classes.nib index 1dbd06362c..4a0573b97d 100644 --- a/src/MacVim/English.lproj/MainMenu.nib/classes.nib +++ b/src/MacVim/English.lproj/MainMenu.nib/classes.nib @@ -15,6 +15,8 @@ id newWindow id + openWebsite + id orderFrontPreferencePanel id selectNextWindow @@ -29,6 +31,14 @@ SUPERCLASS NSObject + + CLASS + NSMenu + LANGUAGE + ObjC + SUPERCLASS + NSObject + ACTIONS diff --git a/src/MacVim/English.lproj/MainMenu.nib/info.nib b/src/MacVim/English.lproj/MainMenu.nib/info.nib index 0fe119084e..fb4d19ea6e 100644 --- a/src/MacVim/English.lproj/MainMenu.nib/info.nib +++ b/src/MacVim/English.lproj/MainMenu.nib/info.nib @@ -10,10 +10,10 @@ 5 IBOpenObjects - 57 + 218 IBSystem Version - 9B18 + 9D34 targetFramework IBCocoaFramework diff --git a/src/MacVim/English.lproj/MainMenu.nib/keyedobjects.nib b/src/MacVim/English.lproj/MainMenu.nib/keyedobjects.nib index f58db777c0..241ca9cbc2 100644 Binary files a/src/MacVim/English.lproj/MainMenu.nib/keyedobjects.nib and b/src/MacVim/English.lproj/MainMenu.nib/keyedobjects.nib differ diff --git a/src/MacVim/MMAppController.h b/src/MacVim/MMAppController.h index db3d61af48..0236eaaed9 100644 --- a/src/MacVim/MMAppController.h +++ b/src/MacVim/MMAppController.h @@ -21,12 +21,15 @@ NSString *openSelectionString; ATSFontContainerRef fontContainerRef; NSMutableDictionary *pidArguments; - + NSMenu *defaultMainMenu; NSMenuItem *recentFilesMenuItem; } ++ (MMAppController *)sharedInstance; +- (NSMenu *)defaultMainMenu; - (void)removeVimController:(id)controller; - (void)windowControllerWillOpen:(MMWindowController *)windowController; +- (void)setMainMenu:(NSMenu *)mainMenu; - (IBAction)newWindow:(id)sender; - (IBAction)fileOpen:(id)sender; - (IBAction)selectNextWindow:(id)sender; diff --git a/src/MacVim/MMAppController.m b/src/MacVim/MMAppController.m index 3ad8e2e493..0a978d6d53 100644 --- a/src/MacVim/MMAppController.m +++ b/src/MacVim/MMAppController.m @@ -92,6 +92,18 @@ static int executeInLoginShell(NSString *path, NSArray *args); @end +@interface NSMenu (MMExtras) +- (int)indexOfItemWithAction:(SEL)action; +- (NSMenuItem *)itemWithAction:(SEL)action; +- (NSMenu *)findMenuContainingItemWithAction:(SEL)action; +- (NSMenu *)findWindowsMenu; +- (NSMenu *)findApplicationMenu; +- (NSMenu *)findServicesMenu; +- (NSMenu *)findFileMenu; +@end + + + @implementation MMAppController @@ -177,39 +189,42 @@ static int executeInLoginShell(NSString *path, NSArray *args); [vimControllers release]; vimControllers = nil; [openSelectionString release]; openSelectionString = nil; [recentFilesMenuItem release]; recentFilesMenuItem = nil; + [defaultMainMenu release]; defaultMainMenu = nil; [super dealloc]; } - (void)applicationWillFinishLaunching:(NSNotification *)notification { - // Create the "Open Recent" menu. See - // http://lapcatsoftware.com/blog/2007/07/10/working-without-a-nib-part-5-open-recent-menu/ - // and http://www.cocoabuilder.com/archive/message/cocoa/2007/8/15/187793 + // Remember the default menu so that it can be restored if the user closes + // all editor windows. + defaultMainMenu = [[NSApp mainMenu] retain]; + + // Set up the "Open Recent" menu. See + // http://lapcatsoftware.com/blog/2007/07/10/ + // working-without-a-nib-part-5-open-recent-menu/ + // and + // http://www.cocoabuilder.com/archive/message/cocoa/2007/8/15/187793 // for more information. - // - // The menu needs to be created and be added to a toplevel menu in - // applicationWillFinishLaunching at the latest, otherwise it doesn't work. + // + // The menu itself is created in MainMenu.nib but we still seem to have to + // hack around a bit to get it to work. (This has to be done in + // applicationWillFinishLaunching at the latest, otherwise it doesn't + // work.) + NSMenu *fileMenu = [defaultMainMenu findFileMenu]; + if (fileMenu) { + int idx = [fileMenu indexOfItemWithAction:@selector(fileOpen:)]; + if (idx >= 0 && idx+1 < [fileMenu numberOfItems]) - recentFilesMenuItem = [[NSMenuItem alloc] - initWithTitle:NSLocalizedString(@"Open Recent", @"Open Recent menu") - action:nil keyEquivalent:@""]; + recentFilesMenuItem = [fileMenu itemWithTitle:@"Open Recent"]; + [[recentFilesMenuItem submenu] performSelector:@selector(_setMenuName:) + withObject:@"NSRecentDocumentsMenu"]; - NSMenu *recentFilesMenu = [[NSMenu alloc] - initWithTitle:NSLocalizedString(@"Open Recent", @"Open Recent menu")]; - [recentFilesMenu performSelector:@selector(_setMenuName:) - withObject:@"NSRecentDocumentsMenu"]; - - [recentFilesMenu addItemWithTitle:NSLocalizedString(@"Clear Menu", - @"Open Recent menu") - action:@selector(clearRecentDocuments:) - keyEquivalent:@""]; - [recentFilesMenuItem setSubmenu:recentFilesMenu]; - [recentFilesMenu release]; // the menu is retained by recentFilesMenuItem - [recentFilesMenuItem setTag:-1]; // must not be 0 - - // TODO: this will not work in a localized MacVim - [[[[NSApp mainMenu] itemWithTitle:@"File"] submenu] addItem:recentFilesMenuItem]; + // Note: The "Recent Files" menu must be moved around since there is no + // -[NSApp setRecentFilesMenu:] method. We keep a reference to it to + // facilitate this move (see setMainMenu: below). + [recentFilesMenuItem retain]; + } #if MM_HANDLE_XCODE_MOD_EVENT [[NSAppleEventManager sharedAppleEventManager] @@ -504,6 +519,19 @@ static int executeInLoginShell(NSString *path, NSArray *args); [NSApp setDelegate:nil]; } ++ (MMAppController *)sharedInstance +{ + // Note: The app controller is a singleton which is instantiated in + // MainMenu.nib where it is also connected as the delegate of NSApp. + id delegate = [NSApp delegate]; + return [delegate isKindOfClass:self] ? (MMAppController*)delegate : nil; +} + +- (NSMenu *)defaultMainMenu +{ + return defaultMainMenu; +} + - (void)removeVimController:(id)controller { //NSLog(@"%s%@", _cmd, controller); @@ -513,7 +541,9 @@ static int executeInLoginShell(NSString *path, NSArray *args); [vimControllers removeObject:controller]; if (![vimControllers count]) { - // TODO: change menu to default state + // The last editor window just closed so restore the main menu back to + // its default state (which is defined in MainMenu.nib). + [self setMainMenu:defaultMainMenu]; } } @@ -556,6 +586,68 @@ static int executeInLoginShell(NSString *path, NSArray *args); } } +- (void)setMainMenu:(NSMenu *)mainMenu +{ + if ([NSApp mainMenu] == mainMenu) return; + + // If the new menu has a "Recent Files" dummy item, then swap the real item + // for the dummy. We are forced to do this since Cocoa initializes the + // "Recent Files" menu and there is no way to simply point Cocoa to a new + // item each time the menus are swapped. + NSMenu *fileMenu = [mainMenu findFileMenu]; + int dummyIdx = + [fileMenu indexOfItemWithAction:@selector(recentFilesDummy:)]; + if (dummyIdx >= 0 && recentFilesMenuItem) { + NSMenuItem *dummyItem = [[fileMenu itemAtIndex:dummyIdx] retain]; + [fileMenu removeItemAtIndex:dummyIdx]; + + NSMenu *recentFilesParentMenu = [recentFilesMenuItem menu]; + int idx = [recentFilesParentMenu indexOfItem:recentFilesMenuItem]; + if (idx >= 0) { + [[recentFilesMenuItem retain] autorelease]; + [recentFilesParentMenu removeItemAtIndex:idx]; + [recentFilesParentMenu insertItem:dummyItem atIndex:idx]; + } + + [fileMenu insertItem:recentFilesMenuItem atIndex:dummyIdx]; + [dummyItem release]; + } + + // Now set the new menu. Notice that we keep one menu for each editor + // window since each editor can have its own set of menus. When swapping + // menus we have to tell Cocoa where the new "MacVim", "Windows", and + // "Services" menu are. + [NSApp setMainMenu:mainMenu]; + + // Setting the "MacVim" (or "Application") menu ensures that it is typeset + // in boldface. (The setAppleMenu: method used to be public but is now + // private so this will have to be considered a bit of a hack!) + NSMenu *appMenu = [mainMenu findApplicationMenu]; + [NSApp performSelector:@selector(setAppleMenu:) withObject:appMenu]; + + NSMenu *servicesMenu = [mainMenu findServicesMenu]; + [NSApp setServicesMenu:servicesMenu]; + + NSMenu *windowsMenu = [mainMenu findWindowsMenu]; + if (windowsMenu) { + // Cocoa isn't clever enough to get rid of items it has added to the + // "Windows" menu so we have to do it ourselves otherwise there will be + // multiple menu items for each window in the "Windows" menu. + // This code assumes that the only items Cocoa add are ones which + // send off the action makeKeyAndOrderFront:. (Cocoa will not add + // another separator item if the last item on the "Windows" menu + // already is a separator, so we needen't worry about separators.) + int i, count = [windowsMenu numberOfItems]; + for (i = count-1; i >= 0; --i) { + NSMenuItem *item = [windowsMenu itemAtIndex:i]; + if ([item action] == @selector(makeKeyAndOrderFront:)) + [windowsMenu removeItem:item]; + } + + [NSApp setWindowsMenu:windowsMenu]; + } +} + - (IBAction)newWindow:(id)sender { [self launchVimProcessWithArguments:nil]; @@ -657,7 +749,7 @@ static int executeInLoginShell(NSString *path, NSArray *args); setProtocolForProxy:@protocol(MMBackendProtocol)]; vc = [[[MMVimController alloc] - initWithBackend:backend pid:pid recentFiles:recentFilesMenuItem] + initWithBackend:backend pid:pid] autorelease]; if (![vimControllers count]) { @@ -1144,6 +1236,77 @@ static int executeInLoginShell(NSString *path, NSArray *args); +@implementation NSMenu (MMExtras) + +- (int)indexOfItemWithAction:(SEL)action +{ + int i, count = [self numberOfItems]; + for (i = 0; i < count; ++i) { + NSMenuItem *item = [self itemAtIndex:i]; + if ([item action] == action) + return i; + } + + return -1; +} + +- (NSMenuItem *)itemWithAction:(SEL)action +{ + int idx = [self indexOfItemWithAction:action]; + return idx >= 0 ? [self itemAtIndex:idx] : nil; +} + +- (NSMenu *)findMenuContainingItemWithAction:(SEL)action +{ + // NOTE: We only look for the action in the submenus of 'self' + int i, count = [self numberOfItems]; + for (i = 0; i < count; ++i) { + NSMenu *menu = [[self itemAtIndex:i] submenu]; + NSMenuItem *item = [menu itemWithAction:action]; + if (item) return menu; + } + + return nil; +} + +- (NSMenu *)findWindowsMenu +{ + return [self findMenuContainingItemWithAction: + @selector(performMiniaturize:)]; +} + +- (NSMenu *)findApplicationMenu +{ + // TODO: Just return [self itemAtIndex:0]? + return [self findMenuContainingItemWithAction:@selector(terminate:)]; +} + +- (NSMenu *)findServicesMenu +{ + // NOTE! Our heuristic for finding the "Services" menu is to look for the + // second item before the "Hide MacVim" menu item on the "MacVim" menu. + // (The item before "Hide MacVim" should be a separator, but this is not + // important as long as the item before that is the "Services" menu.) + + NSMenu *appMenu = [self findApplicationMenu]; + if (!appMenu) return nil; + + int idx = [appMenu indexOfItemWithAction: @selector(hide:)]; + if (idx-2 < 0) return nil; // idx == -1, if selector not found + + return [[appMenu itemAtIndex:idx-2] submenu]; +} + +- (NSMenu *)findFileMenu +{ + return [self findMenuContainingItemWithAction:@selector(performClose:)]; +} + +@end // NSMenu (MMExtras) + + + + static int executeInLoginShell(NSString *path, NSArray *args) { @@ -1237,4 +1400,3 @@ executeInLoginShell(NSString *path, NSArray *args) return pid; } - diff --git a/src/MacVim/MMVimController.h b/src/MacVim/MMVimController.h index 56fb6d16b9..a48ae7429a 100644 --- a/src/MacVim/MMVimController.h +++ b/src/MacVim/MMVimController.h @@ -38,19 +38,17 @@ int resendMsgid; NSData *resendData; #endif - NSMenuItem *recentFilesMenuItem; - NSMenuItem *recentFilesDummy; NSDictionary *vimState; } -- (id)initWithBackend:(id)backend pid:(int)processIdentifier - recentFiles:(NSMenuItem*)menu; +- (id)initWithBackend:(id)backend pid:(int)processIdentifier; - (id)backendProxy; - (int)pid; - (void)setServerName:(NSString *)name; - (NSString *)serverName; - (MMWindowController *)windowController; - (NSDictionary *)vimState; +- (NSMenu *)mainMenu; - (void)cleanup; - (void)dropFiles:(NSArray *)filenames forceOpen:(BOOL)force; - (void)dropString:(NSString *)string; @@ -61,5 +59,4 @@ timeout:(NSTimeInterval)timeout; - (void)addVimInput:(NSString *)string; - (NSString *)evaluateVimExpression:(NSString *)expr; -- (void)updateMainMenu; @end diff --git a/src/MacVim/MMVimController.m b/src/MacVim/MMVimController.m index be74f1dcd0..53d8daf371 100644 --- a/src/MacVim/MMVimController.m +++ b/src/MacVim/MMVimController.m @@ -84,11 +84,6 @@ static NSTimeInterval MMResendInterval = 0.5; #if MM_RESEND_LAST_FAILURE - (void)resendTimerFired:(NSTimer *)timer; #endif -- (void)replaceMenuItem:(NSMenuItem*)old with:(NSMenuItem*)new; -- (NSMenu *)findMenuContainingItemWithAction:(SEL)action; -- (NSMenu *)findWindowsMenu; -- (NSMenu *)findApplicationMenu; -- (NSMenu *)findServicesMenu; @end @@ -99,22 +94,13 @@ static NSTimeInterval MMResendInterval = 0.5; @end -@interface NSMenu (MMExtras) -- (int)indexOfItemWithAction:(SEL)action; -- (NSMenuItem *)itemWithAction:(SEL)action; -@end - - @implementation MMVimController - (id)initWithBackend:(id)backend pid:(int)processIdentifier - recentFiles:(NSMenuItem*)menu; { if ((self = [super init])) { - recentFilesMenuItem = [menu retain]; - windowController = [[MMWindowController alloc] initWithVimController:self]; backendProxy = [backend retain]; @@ -133,15 +119,21 @@ static NSTimeInterval MMResendInterval = 0.5; selector:@selector(connectionDidDie:) name:NSConnectionDidDieNotification object:connection]; - // Copy the "MacVim menu" from the current main menu. + // Copy the "MacVim menu" from the default main menu (we assume that it + // is the first item on the default main menu). mainMenu = [[NSMenu alloc] initWithTitle:@"MainMenu"]; - NSMenuItem *appMenuItem = [[NSApp mainMenu] itemAtIndex:0]; + NSMenuItem *appMenuItem = [[[MMAppController sharedInstance] + defaultMainMenu] itemAtIndex:0]; appMenuItem = [[appMenuItem copy] autorelease]; - // Note: If the title of the application menu is anything but "MacVim", - // then the application menu will not be typeset in boldface for some - // reason. - [appMenuItem setTitle:@"MacVim"]; + // Note: If the title of the application menu is anything but what + // CFBundleName says then the application menu will not be typeset in + // boldface for some reason. (It should already be set when we copy + // from the default main menu, but this is not the case for some + // reason.) + NSString *appName = [[NSBundle mainBundle] + objectForInfoDictionaryKey:@"CFBundleName"]; + [appMenuItem setTitle:appName]; [mainMenu addItem:appMenuItem]; @@ -169,9 +161,6 @@ static NSTimeInterval MMResendInterval = 0.5; [popupMenuItems release]; popupMenuItems = nil; [windowController release]; windowController = nil; - [recentFilesMenuItem release]; recentFilesMenuItem = nil; - [recentFilesDummy release]; recentFilesDummy = nil; - [vimState release]; vimState = nil; [mainMenu release]; mainMenu = nil; @@ -188,6 +177,11 @@ static NSTimeInterval MMResendInterval = 0.5; return vimState; } +- (NSMenu *)mainMenu +{ + return mainMenu; +} + - (void)setServerName:(NSString *)name { if (name != serverName) { @@ -588,42 +582,6 @@ static NSTimeInterval MMResendInterval = 0.5; return nil; } -- (void)updateMainMenu -{ - if ([NSApp mainMenu] == mainMenu) return; - - [NSApp setMainMenu:mainMenu]; - - NSMenu *appMenu = [self findApplicationMenu]; - [NSApp performSelector:@selector(setAppleMenu:) withObject:appMenu]; - - NSMenu *servicesMenu = [self findServicesMenu]; - [NSApp setServicesMenu:servicesMenu]; - - NSMenu *windowsMenu = [self findWindowsMenu]; - if (windowsMenu) { - // Remove all items that are added when setWindowsMenu: is called. - int i, count = [windowsMenu numberOfItems]; - for (i = count-1; i >= 0; --i) { - NSMenuItem *item = [windowsMenu itemAtIndex:i]; - if ([item action] == @selector(makeKeyAndOrderFront:)) - [windowsMenu removeItem:item]; - } - - [NSApp setWindowsMenu:windowsMenu]; - } - -#if 0 - // Replace real Recent Files menu in the old menu with the dummy, then - // remove dummy from new menu and put Recent Files menu there - NSMenuItem *oldItem = (NSMenuItem*)[recentFilesMenuItem representedObject]; - if (oldItem) - [self replaceMenuItem:recentFilesMenuItem with:oldItem]; - [recentFilesMenuItem setRepresentedObject:recentFilesDummy]; - [self replaceMenuItem:recentFilesDummy with:recentFilesMenuItem]; -#endif -} - @end // MMVimController @@ -1064,28 +1022,25 @@ static NSTimeInterval MMResendInterval = 0.5; item = [[[NSMenuItem alloc] init] autorelease]; [item setTitle:title]; - if ([action isEqualToString:@"recentFilesDummy:"]) { - // Remove the recent files menu item from its current menu - // and put it in the current file menu. See -[MMAppController - // applicationWillFinishLaunching for more information. - //[[recentFilesMenuItem menu] removeItem:recentFilesMenuItem]; - //item = recentFilesMenuItem; - recentFilesDummy = [item retain]; - } else { - // TODO: Check that 'action' is a valid action (nothing will - // happen if it isn't, but it would be nice with a warning). - if ([action length] > 0) - [item setAction:NSSelectorFromString(action)]; - else - [item setAction:@selector(vimMenuItemAction:)]; - if ([tip length] > 0) [item setToolTip:tip]; - if ([keyEquivalent length] > 0) { - [item setKeyEquivalent:keyEquivalent]; - [item setKeyEquivalentModifierMask:modifierMask]; - } - [item setAlternate:isAlternate]; - [item setTag:1]; // 'tag != 0' means item is enabled + // Note: It is possible to set the action to a message that "doesn't + // exist" without problems. We take advantage of this when adding + // "dummy items" e.g. when dealing with the "Recent Files" menu (in + // which case a recentFilesDummy: action is set, although it is never + // used). + if ([action length] > 0) + [item setAction:NSSelectorFromString(action)]; + else + [item setAction:@selector(vimMenuItemAction:)]; + if ([tip length] > 0) [item setToolTip:tip]; + if ([keyEquivalent length] > 0) { + [item setKeyEquivalent:keyEquivalent]; + [item setKeyEquivalentModifierMask:modifierMask]; } + [item setAlternate:isAlternate]; + + // The tag is used to indicate whether Vim thinks a menu item should be + // enabled or disabled. By default Vim thinks menu items are enabled. + [item setTag:1]; } if ([parent numberOfItems] <= idx) { @@ -1228,7 +1183,7 @@ static NSTimeInterval MMResendInterval = 0.5; [self cleanup]; // NOTE! This causes the call to removeVimController: to be delayed. - [[NSApp delegate] + [[MMAppController sharedInstance] performSelectorOnMainThread:@selector(removeVimController:) withObject:self waitUntilDone:NO]; } @@ -1258,53 +1213,6 @@ static NSTimeInterval MMResendInterval = 0.5; } #endif -- (void)replaceMenuItem:(NSMenuItem*)old with:(NSMenuItem*)new -{ - NSMenu *menu = [old menu]; - int index = [menu indexOfItem:old]; - [menu removeItemAtIndex:index]; - [menu insertItem:new atIndex:index]; -} - -- (NSMenu *)findMenuContainingItemWithAction:(SEL)action -{ - int i, count = [mainMenu numberOfItems]; - for (i = 0; i < count; ++i) { - NSMenu *menu = [[mainMenu itemAtIndex:i] submenu]; - NSMenuItem *item = [menu itemWithAction:action]; - if (item) return menu; - } - - return nil; -} - -- (NSMenu *)findWindowsMenu -{ - return [self findMenuContainingItemWithAction: - @selector(performMiniaturize:)]; -} - -- (NSMenu *)findApplicationMenu -{ - return [self findMenuContainingItemWithAction:@selector(terminate:)]; -} - -- (NSMenu *)findServicesMenu -{ - // NOTE! Our heuristic for finding the "Services" menu is to look for the - // second item before the "Hide MacVim" menu item on the "MacVim" menu. - // (The item before "Hide MacVim" should be a separator, but this is not - // important as long as the item before that is the "Services" menu.) - - NSMenu *appMenu = [self findApplicationMenu]; - if (!appMenu) return nil; - - int idx = [appMenu indexOfItemWithAction: @selector(hide:)]; - if (idx-2 < 0) return nil; // idx == -1, if selector not found - - return [[appMenu itemAtIndex:idx-2] submenu]; -} - @end // MMVimController (Private) @@ -1342,27 +1250,6 @@ static NSTimeInterval MMResendInterval = 0.5; @end // NSToolbar (MMExtras) -@implementation NSMenu (MMExtras) - -- (int)indexOfItemWithAction:(SEL)action -{ - int i, count = [self numberOfItems]; - for (i = 0; i < count; ++i) { - NSMenuItem *item = [self itemAtIndex:i]; - if ([item action] == action) - return i; - } - - return -1; -} - -- (NSMenuItem *)itemWithAction:(SEL)action -{ - int idx = [self indexOfItemWithAction:action]; - return idx >= 0 ? [self itemAtIndex:idx] : nil; -} - -@end // NSMenu (MMExtras) diff --git a/src/MacVim/MMWindowController.m b/src/MacVim/MMWindowController.m index ede1a753a6..2a78cae3d4 100644 --- a/src/MacVim/MMWindowController.m +++ b/src/MacVim/MMWindowController.m @@ -263,7 +263,7 @@ - (void)openWindow { - [[NSApp delegate] windowControllerWillOpen:self]; + [[MMAppController sharedInstance] windowControllerWillOpen:self]; [self addNewTabViewItem]; @@ -649,7 +649,7 @@ - (void)windowDidBecomeMain:(NSNotification *)notification { - [vimController updateMainMenu]; + [[MMAppController sharedInstance] setMainMenu:[vimController mainMenu]]; [vimController sendMessage:GotFocusMsgID data:nil]; if ([vimView textView]) {