Use default menu when no window open, fix 'Recent Files' menu

This commit is contained in:
Bjorn Winckler
2008-06-03 23:15:18 +02:00
parent 44d7e60b06
commit f6ab5cbc03
8 changed files with 245 additions and 186 deletions
+10
View File
@@ -15,6 +15,8 @@
<string>id</string>
<key>newWindow</key>
<string>id</string>
<key>openWebsite</key>
<string>id</string>
<key>orderFrontPreferencePanel</key>
<string>id</string>
<key>selectNextWindow</key>
@@ -29,6 +31,14 @@
<key>SUPERCLASS</key>
<string>NSObject</string>
</dict>
<dict>
<key>CLASS</key>
<string>NSMenu</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>SUPERCLASS</key>
<string>NSObject</string>
</dict>
<dict>
<key>ACTIONS</key>
<dict>
+2 -2
View File
@@ -10,10 +10,10 @@
<integer>5</integer>
<key>IBOpenObjects</key>
<array>
<integer>57</integer>
<integer>218</integer>
</array>
<key>IBSystem Version</key>
<string>9B18</string>
<string>9D34</string>
<key>targetFramework</key>
<string>IBCocoaFramework</string>
</dict>
Binary file not shown.
+4 -1
View File
@@ -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;
+189 -27
View File
@@ -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;
}
+2 -5
View File
@@ -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
+36 -149
View File
@@ -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)
+2 -2
View File
@@ -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]) {