1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
3 * VIM - Vi IMproved by Bram Moolenaar
4 * MacVim GUI port by Bjorn Winckler
6 * Do ":help uganda" in Vim to read copying and usage conditions.
7 * Do ":help credits" in Vim to see a list of people who contributed.
8 * See README.txt for an overview of the Vim source code.
11 #import "MMVimController.h"
12 #import "MMWindowController.h"
13 #import "MMTextView.h"
14 #import "MMAppController.h"
15 #import "MMTextStorage.h"
18 //static NSString *AttentionToolbarItemID = @"Attention";
19 static NSString *DefaultToolbarImageName = @"Attention";
22 @interface MMVimController (Private)
23 - (void)handleMessage:(int)msgid data:(NSData *)data;
24 - (void)performBatchDrawWithData:(NSData *)data;
25 - (void)panelDidEnd:(NSSavePanel *)panel code:(int)code
26 context:(void *)context;
27 - (NSMenuItem *)menuItemForTag:(int)tag;
28 - (NSMenu *)menuForTag:(int)tag;
29 - (NSMenu *)topLevelMenuForTitle:(NSString *)title;
30 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
32 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
33 title:(NSString *)title tip:(NSString *)tip
34 keyEquivalent:(int)key modifiers:(int)mask
35 action:(NSString *)action atIndex:(int)idx;
36 - (void)updateMainMenu;
37 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index;
38 - (IBAction)toolbarAction:(id)sender;
39 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
40 toolTip:(NSString *)tip icon:(NSString *)icon;
41 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label
42 tip:(NSString *)tip icon:(NSString *)icon
44 - (void)connectionDidDie:(NSNotification *)notification;
45 - (BOOL)executeActionWithName:(NSString *)name;
50 // TODO: Move to separate file
51 @interface NSColor (MMProtocol)
52 + (NSColor *)colorWithRgbInt:(int)rgb;
57 static NSMenuItem *findMenuItemWithTagInMenu(NSMenu *root, int tag)
60 NSMenuItem *item = [root itemWithTag:tag];
61 if (item) return item;
63 NSArray *items = [root itemArray];
64 unsigned i, count = [items count];
65 for (i = 0; i < count; ++i) {
66 item = [items objectAtIndex:i];
67 if ([item hasSubmenu]) {
68 item = findMenuItemWithTagInMenu([item submenu], tag);
69 if (item) return item;
79 @implementation MMVimController
81 - (id)initWithBackend:(id)backend
83 if ((self = [super init])) {
85 [[MMWindowController alloc] initWithVimController:self];
86 backendProxy = [backend retain];
87 sendQueue = [NSMutableArray new];
88 mainMenuItems = [[NSMutableArray alloc] init];
89 popupMenuItems = [[NSMutableArray alloc] init];
90 toolbarItemDict = [[NSMutableDictionary alloc] init];
92 NSConnection *connection = [backendProxy connectionForProxy];
93 [[NSNotificationCenter defaultCenter] addObserver:self
94 selector:@selector(connectionDidDie:)
95 name:NSConnectionDidDieNotification object:connection];
103 //NSLog(@"%@ %s", [self className], _cmd);
105 [[NSNotificationCenter defaultCenter] removeObserver:self];
107 [backendProxy release];
110 [toolbarItemDict release];
112 [popupMenuItems release];
113 [mainMenuItems release];
114 [windowController release];
119 - (MMWindowController *)windowController
121 return windowController;
124 - (void)sendMessage:(int)msgid data:(NSData *)data wait:(BOOL)wait
126 if (inProcessCommandQueue) {
127 //NSLog(@"In process command queue; delaying message send.");
128 [sendQueue addObject:[NSNumber numberWithInt:msgid]];
130 [sendQueue addObject:data];
132 [sendQueue addObject:[NSNull null]];
137 [backendProxy processInput:msgid data:data];
139 // Do not wait for the message to be sent, i.e. drop the message if it
140 // can't be delivered immediately.
141 NSConnection *connection = [backendProxy connectionForProxy];
143 NSTimeInterval req = [connection requestTimeout];
144 [connection setRequestTimeout:0];
146 [backendProxy processInput:msgid data:data];
148 @catch (NSException *e) {
149 // Connection timed out, just ignore this.
150 //NSLog(@"WARNING! Connection timed out in %s", _cmd);
153 [connection setRequestTimeout:req];
164 - (oneway void)showSavePanelForDirectory:(in bycopy NSString *)dir
165 title:(in bycopy NSString *)title
168 [windowController setStatusText:title];
171 [[NSSavePanel savePanel] beginSheetForDirectory:dir file:nil
172 modalForWindow:[windowController window]
174 didEndSelector:@selector(panelDidEnd:code:context:)
177 NSOpenPanel *panel = [NSOpenPanel openPanel];
178 [panel setAllowsMultipleSelection:NO];
179 [panel beginSheetForDirectory:dir file:nil types:nil
180 modalForWindow:[windowController window]
182 didEndSelector:@selector(panelDidEnd:code:context:)
187 - (oneway void)processCommandQueue:(in NSArray *)queue
189 unsigned i, count = [queue count];
191 NSLog(@"WARNING: Uneven number of components (%d) in flush queue "
192 "message; ignoring this message.", count);
196 inProcessCommandQueue = YES;
198 //NSLog(@"======== %s BEGIN ========", _cmd);
199 for (i = 0; i < count; i += 2) {
200 NSData *value = [queue objectAtIndex:i];
201 NSData *data = [queue objectAtIndex:i+1];
203 int msgid = *((int*)[value bytes]);
205 if (msgid != EnableMenuItemMsgID && msgid != AddMenuItemMsgID
206 && msgid != AddMenuMsgID) {
207 NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
211 [self handleMessage:msgid data:data];
213 //NSLog(@"======== %s END ========", _cmd);
215 if (shouldUpdateMainMenu) {
216 [self updateMainMenu];
219 [windowController processCommandQueueDidFinish];
221 inProcessCommandQueue = NO;
223 count = [sendQueue count];
225 if (count % 2 == 0) {
226 //NSLog(@"%s Sending %d queued messages", _cmd, count/2);
228 for (i = 0; i < count; i += 2) {
229 int msgid = [[sendQueue objectAtIndex:i] intValue];
230 id data = [sendQueue objectAtIndex:i+1];
231 if ([data isEqual:[NSNull null]])
234 [backendProxy processInput:msgid data:data];
238 [sendQueue removeAllObjects];
242 - (void)windowWillClose:(NSNotification *)notification
244 // NOTE! This causes the call to removeVimController: to be delayed.
246 performSelectorOnMainThread:@selector(removeVimController:)
247 withObject:self waitUntilDone:NO];
250 - (void)windowDidBecomeMain:(NSNotification *)notification
252 [self updateMainMenu];
255 - (NSToolbarItem *)toolbar:(NSToolbar *)theToolbar
256 itemForItemIdentifier:(NSString *)itemId
257 willBeInsertedIntoToolbar:(BOOL)flag
259 //NSLog(@"%s", _cmd);
261 NSToolbarItem *item = [toolbarItemDict objectForKey:itemId];
263 NSLog(@"WARNING: No toolbar item with id '%@'", itemId);
269 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)theToolbar
271 //NSLog(@"%s", _cmd);
275 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)theToolbar
277 //NSLog(@"%s", _cmd);
281 @end // MMVimController
285 @implementation MMVimController (Private)
287 - (void)handleMessage:(int)msgid data:(NSData *)data
289 //NSLog(@"%@ %s", [self className], _cmd);
291 if (OpenVimWindowMsgID == msgid) {
292 [windowController openWindow];
293 } else if (BatchDrawMsgID == msgid) {
294 //NSLog(@"Received batch draw message from VimTask.");
296 [self performBatchDrawWithData:data];
297 } else if (SelectTabMsgID == msgid) {
298 #if 0 // NOTE: Tab selection is done inside updateTabsWithData:.
299 const void *bytes = [data bytes];
300 int idx = *((int*)bytes);
301 //NSLog(@"Selecting tab with index %d", idx);
302 [windowController selectTabWithIndex:idx];
304 } else if (UpdateTabBarMsgID == msgid) {
305 //NSLog(@"Updating tabs");
306 [windowController updateTabsWithData:data];
307 } else if (ShowTabBarMsgID == msgid) {
308 //NSLog(@"Showing tab bar");
309 [windowController showTabBar:YES];
310 } else if (HideTabBarMsgID == msgid) {
311 //NSLog(@"Hiding tab bar");
312 [windowController showTabBar:NO];
313 } else if (SetTextDimensionsMsgID == msgid) {
314 const void *bytes = [data bytes];
315 int rows = *((int*)bytes); bytes += sizeof(int);
316 int cols = *((int*)bytes); bytes += sizeof(int);
318 [windowController setTextDimensionsWithRows:rows columns:cols];
319 } else if (SetVimWindowTitleMsgID == msgid) {
320 const void *bytes = [data bytes];
321 int len = *((int*)bytes); bytes += sizeof(int);
323 NSString *string = [[NSString alloc] initWithBytes:(void*)bytes
324 length:len encoding:NSUTF8StringEncoding];
326 [[windowController window] setTitle:string];
329 } else if (BrowseForFileMsgID == msgid) {
330 const void *bytes = [data bytes];
331 int save = *((int*)bytes); bytes += sizeof(int);
333 int len = *((int*)bytes); bytes += sizeof(int);
336 dir = [[NSString alloc] initWithBytes:(void*)bytes
338 encoding:NSUTF8StringEncoding];
342 len = *((int*)bytes); bytes += sizeof(int);
344 NSString *title = [[NSString alloc]
345 initWithBytes:(void*)bytes length:len
346 encoding:NSUTF8StringEncoding];
349 [windowController setStatusText:title];
354 [[NSSavePanel savePanel] beginSheetForDirectory:dir file:nil
355 modalForWindow:[windowController window]
357 didEndSelector:@selector(panelDidEnd:code:context:)
360 NSOpenPanel *panel = [NSOpenPanel openPanel];
361 [panel setAllowsMultipleSelection:NO];
362 [panel beginSheetForDirectory:dir file:nil types:nil
363 modalForWindow:[windowController window]
365 didEndSelector:@selector(panelDidEnd:code:context:)
370 } else if (UpdateInsertionPointMsgID == msgid) {
371 const void *bytes = [data bytes];
372 int color = *((int*)bytes); bytes += sizeof(int);
373 int row = *((int*)bytes); bytes += sizeof(int);
374 int col = *((int*)bytes); bytes += sizeof(int);
375 int state = *((int*)bytes); bytes += sizeof(int);
377 // TODO! Move to window controller.
378 MMTextView *textView = [windowController textView];
380 MMTextStorage *textStorage = (MMTextStorage*)[textView textStorage];
381 unsigned off = [textStorage offsetFromRow:row column:col];
383 [textView setInsertionPointColor:[NSColor colorWithRgbInt:color]];
384 [textView setSelectedRange:NSMakeRange(off, 0)];
385 [textView setShouldDrawInsertionPoint:state];
387 } else if (AddMenuMsgID == msgid) {
388 NSString *title = nil;
389 const void *bytes = [data bytes];
390 int tag = *((int*)bytes); bytes += sizeof(int);
391 int parentTag = *((int*)bytes); bytes += sizeof(int);
392 int len = *((int*)bytes); bytes += sizeof(int);
394 title = [[NSString alloc] initWithBytes:(void*)bytes length:len
395 encoding:NSUTF8StringEncoding];
398 int idx = *((int*)bytes); bytes += sizeof(int);
400 if (MenuToolbarType == parentTag) {
402 NSString *ident = [NSString stringWithFormat:@"%d.%d",
404 //NSLog(@"Creating toolbar with identifier %@", ident);
405 toolbar = [[NSToolbar alloc] initWithIdentifier:ident];
407 [toolbar setShowsBaselineSeparator:NO];
408 [toolbar setDelegate:self];
409 [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
410 [toolbar setSizeMode:NSToolbarSizeModeSmall];
412 NSWindow *win = [windowController window];
413 [win setToolbar:toolbar];
415 // HACK! Redirect the pill button so that we can ask Vim to
417 NSButton *pillButton = [win
418 standardWindowButton:NSWindowToolbarButton];
420 [pillButton setAction:@selector(toggleToolbar:)];
421 [pillButton setTarget:windowController];
425 [self addMenuWithTag:tag parent:parentTag title:title atIndex:idx];
429 } else if (AddMenuItemMsgID == msgid) {
430 NSString *title = nil, *tip = nil, *icon = nil, *action = nil;
431 const void *bytes = [data bytes];
432 int tag = *((int*)bytes); bytes += sizeof(int);
433 int parentTag = *((int*)bytes); bytes += sizeof(int);
434 int namelen = *((int*)bytes); bytes += sizeof(int);
436 title = [[NSString alloc] initWithBytes:(void*)bytes length:namelen
437 encoding:NSUTF8StringEncoding];
440 int tiplen = *((int*)bytes); bytes += sizeof(int);
442 tip = [[NSString alloc] initWithBytes:(void*)bytes length:tiplen
443 encoding:NSUTF8StringEncoding];
446 int iconlen = *((int*)bytes); bytes += sizeof(int);
448 icon = [[NSString alloc] initWithBytes:(void*)bytes length:iconlen
449 encoding:NSUTF8StringEncoding];
452 int actionlen = *((int*)bytes); bytes += sizeof(int);
454 action = [[NSString alloc] initWithBytes:(void*)bytes
456 encoding:NSUTF8StringEncoding];
459 int idx = *((int*)bytes); bytes += sizeof(int);
460 if (idx < 0) idx = 0;
461 int key = *((int*)bytes); bytes += sizeof(int);
462 int mask = *((int*)bytes); bytes += sizeof(int);
464 NSString *ident = [NSString stringWithFormat:@"%d.%d",
465 (int)self, parentTag];
466 if (toolbar && [[toolbar identifier] isEqual:ident]) {
467 [self addToolbarItemWithTag:tag label:title tip:tip icon:icon
470 NSMenu *parent = [self menuForTag:parentTag];
471 [self addMenuItemWithTag:tag parent:parent title:title tip:tip
472 keyEquivalent:key modifiers:mask action:action
480 } else if (RemoveMenuItemMsgID == msgid) {
481 const void *bytes = [data bytes];
482 int tag = *((int*)bytes); bytes += sizeof(int);
486 if ((item = [self toolbarItemForTag:tag index:&idx])) {
487 [toolbar removeItemAtIndex:idx];
488 } else if ((item = [self menuItemForTag:tag])) {
491 if ([item menu] == [NSApp mainMenu] || ![item menu]) {
492 //NSLog(@"Removing menu: %@", item);
493 // NOTE: To be on the safe side we try to remove the item from
494 // both arrays (it is ok to call removeObject: even if an array
495 // does not contain the object to remove).
496 [mainMenuItems removeObject:item];
497 [popupMenuItems removeObject:item];
501 [[item menu] removeItem:item];
505 } else if (EnableMenuItemMsgID == msgid) {
506 const void *bytes = [data bytes];
507 int tag = *((int*)bytes); bytes += sizeof(int);
508 int state = *((int*)bytes); bytes += sizeof(int);
510 id item = [self toolbarItemForTag:tag index:NULL];
512 item = [self menuItemForTag:tag];
514 [item setEnabled:state];
515 } else if (ShowToolbarMsgID == msgid) {
516 const void *bytes = [data bytes];
517 int enable = *((int*)bytes); bytes += sizeof(int);
518 int flags = *((int*)bytes); bytes += sizeof(int);
520 int mode = NSToolbarDisplayModeDefault;
521 if (flags & ToolbarLabelFlag) {
522 mode = flags & ToolbarIconFlag ? NSToolbarDisplayModeIconAndLabel
523 : NSToolbarDisplayModeLabelOnly;
524 } else if (flags & ToolbarIconFlag) {
525 mode = NSToolbarDisplayModeIconOnly;
528 int size = flags & ToolbarSizeRegularFlag ? NSToolbarSizeModeRegular
529 : NSToolbarSizeModeSmall;
531 [windowController showToolbar:enable size:size mode:mode];
532 } else if (CreateScrollbarMsgID == msgid) {
533 const void *bytes = [data bytes];
534 long ident = *((long*)bytes); bytes += sizeof(long);
535 int type = *((int*)bytes); bytes += sizeof(int);
537 [windowController createScrollbarWithIdentifier:ident type:type];
538 } else if (DestroyScrollbarMsgID == msgid) {
539 const void *bytes = [data bytes];
540 long ident = *((long*)bytes); bytes += sizeof(long);
542 [windowController destroyScrollbarWithIdentifier:ident];
543 } else if (ShowScrollbarMsgID == msgid) {
544 const void *bytes = [data bytes];
545 long ident = *((long*)bytes); bytes += sizeof(long);
546 int visible = *((int*)bytes); bytes += sizeof(int);
548 [windowController showScrollbarWithIdentifier:ident state:visible];
549 } else if (SetScrollbarPositionMsgID == msgid) {
550 const void *bytes = [data bytes];
551 long ident = *((long*)bytes); bytes += sizeof(long);
552 int pos = *((int*)bytes); bytes += sizeof(int);
553 int len = *((int*)bytes); bytes += sizeof(int);
555 [windowController setScrollbarPosition:pos length:len
557 } else if (SetScrollbarThumbMsgID == msgid) {
558 const void *bytes = [data bytes];
559 long ident = *((long*)bytes); bytes += sizeof(long);
560 float val = *((float*)bytes); bytes += sizeof(float);
561 float prop = *((float*)bytes); bytes += sizeof(float);
563 [windowController setScrollbarThumbValue:val proportion:prop
565 } else if (SetFontMsgID == msgid) {
566 const void *bytes = [data bytes];
567 float size = *((float*)bytes); bytes += sizeof(float);
568 int len = *((int*)bytes); bytes += sizeof(int);
569 NSString *name = [[NSString alloc]
570 initWithBytes:(void*)bytes length:len
571 encoding:NSUTF8StringEncoding];
572 NSFont *font = [NSFont fontWithName:name size:size];
575 [windowController setFont:font];
578 } else if (SetDefaultColorsMsgID == msgid) {
579 const void *bytes = [data bytes];
580 int bg = *((int*)bytes); bytes += sizeof(int);
581 int fg = *((int*)bytes); bytes += sizeof(int);
582 NSColor *back = [NSColor colorWithRgbInt:bg];
583 NSColor *fore = [NSColor colorWithRgbInt:fg];
585 [windowController setDefaultColorsBackground:back foreground:fore];
586 } else if (ExecuteActionMsgID == msgid) {
587 const void *bytes = [data bytes];
588 int len = *((int*)bytes); bytes += sizeof(int);
589 NSString *actionName = [[NSString alloc]
590 initWithBytesNoCopy:(void*)bytes
592 encoding:NSUTF8StringEncoding
595 SEL sel = NSSelectorFromString(actionName);
596 [NSApp sendAction:sel to:nil from:self];
598 [actionName release];
599 } else if (ShowPopupMenuMsgID == msgid) {
600 const void *bytes = [data bytes];
601 int row = *((int*)bytes); bytes += sizeof(int);
602 int col = *((int*)bytes); bytes += sizeof(int);
603 int len = *((int*)bytes); bytes += sizeof(int);
604 NSString *title = [[NSString alloc]
605 initWithBytesNoCopy:(void*)bytes
607 encoding:NSUTF8StringEncoding
610 NSMenu *menu = [self topLevelMenuForTitle:title];
612 [windowController popupMenu:menu atRow:row column:col];
614 NSLog(@"WARNING: Cannot popup menu with title %@; no such menu.",
620 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
624 - (void)performBatchDrawWithData:(NSData *)data
626 // TODO! Move to window controller.
627 MMTextStorage *textStorage = [windowController textStorage];
631 const void *bytes = [data bytes];
632 const void *end = bytes + [data length];
634 [textStorage beginEditing];
637 // 1. Sanity check input
638 // 2. Cache rgb -> NSColor lookups?
640 while (bytes < end) {
641 int type = *((int*)bytes); bytes += sizeof(int);
643 if (ClearAllDrawType == type) {
644 int color = *((int*)bytes); bytes += sizeof(int);
646 [textStorage clearAllWithColor:[NSColor colorWithRgbInt:color]];
647 } else if (ClearBlockDrawType == type) {
648 int color = *((int*)bytes); bytes += sizeof(int);
649 int row1 = *((int*)bytes); bytes += sizeof(int);
650 int col1 = *((int*)bytes); bytes += sizeof(int);
651 int row2 = *((int*)bytes); bytes += sizeof(int);
652 int col2 = *((int*)bytes); bytes += sizeof(int);
654 [textStorage clearBlockFromRow:row1 column:col1
655 toRow:row2 column:col2
656 color:[NSColor colorWithRgbInt:color]];
657 } else if (DeleteLinesDrawType == type) {
658 int color = *((int*)bytes); bytes += sizeof(int);
659 int row = *((int*)bytes); bytes += sizeof(int);
660 int count = *((int*)bytes); bytes += sizeof(int);
661 int bot = *((int*)bytes); bytes += sizeof(int);
662 int left = *((int*)bytes); bytes += sizeof(int);
663 int right = *((int*)bytes); bytes += sizeof(int);
665 [textStorage deleteLinesFromRow:row lineCount:count
666 scrollBottom:bot left:left right:right
667 color:[NSColor colorWithRgbInt:color]];
668 } else if (ReplaceStringDrawType == type) {
669 int bg = *((int*)bytes); bytes += sizeof(int);
670 int fg = *((int*)bytes); bytes += sizeof(int);
671 int row = *((int*)bytes); bytes += sizeof(int);
672 int col = *((int*)bytes); bytes += sizeof(int);
673 int flags = *((int*)bytes); bytes += sizeof(int);
674 int len = *((int*)bytes); bytes += sizeof(int);
675 NSString *string = [[NSString alloc]
676 initWithBytesNoCopy:(void*)bytes
678 encoding:NSUTF8StringEncoding
682 [textStorage replaceString:string
685 foregroundColor:[NSColor colorWithRgbInt:fg]
686 backgroundColor:[NSColor colorWithRgbInt:bg]];
689 } else if (InsertLinesDrawType == type) {
690 int color = *((int*)bytes); bytes += sizeof(int);
691 int row = *((int*)bytes); bytes += sizeof(int);
692 int count = *((int*)bytes); bytes += sizeof(int);
693 int bot = *((int*)bytes); bytes += sizeof(int);
694 int left = *((int*)bytes); bytes += sizeof(int);
695 int right = *((int*)bytes); bytes += sizeof(int);
697 [textStorage insertLinesAtRow:row lineCount:count
698 scrollBottom:bot left:left right:right
699 color:[NSColor colorWithRgbInt:color]];
701 NSLog(@"WARNING: Unknown draw type (type=%d)", type);
705 [textStorage endEditing];
708 - (void)panelDidEnd:(NSSavePanel *)panel code:(int)code context:(void *)context
710 [windowController setStatusText:@""];
712 NSString *string = (code == NSOKButton) ? [panel filename] : nil;
713 [backendProxy setBrowseForFileString:string];
716 - (NSMenuItem *)menuItemForTag:(int)tag
718 // Search the main menu.
719 int i, count = [mainMenuItems count];
720 for (i = 0; i < count; ++i) {
721 NSMenuItem *item = [mainMenuItems objectAtIndex:i];
722 if ([item tag] == tag) return item;
723 item = findMenuItemWithTagInMenu([item submenu], tag);
724 if (item) return item;
727 // Search the popup menus.
728 count = [popupMenuItems count];
729 for (i = 0; i < count; ++i) {
730 NSMenuItem *item = [popupMenuItems objectAtIndex:i];
731 if ([item tag] == tag) return item;
732 item = findMenuItemWithTagInMenu([item submenu], tag);
733 if (item) return item;
739 - (NSMenu *)menuForTag:(int)tag
741 return [[self menuItemForTag:tag] submenu];
744 - (NSMenu *)topLevelMenuForTitle:(NSString *)title
746 // Search only the top-level menus.
748 unsigned i, count = [popupMenuItems count];
749 for (i = 0; i < count; ++i) {
750 NSMenuItem *item = [popupMenuItems objectAtIndex:i];
751 if ([title isEqual:[item title]])
752 return [item submenu];
755 count = [mainMenuItems count];
756 for (i = 0; i < count; ++i) {
757 NSMenuItem *item = [mainMenuItems objectAtIndex:i];
758 if ([title isEqual:[item title]])
759 return [item submenu];
765 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
768 NSMenu *parent = [self menuForTag:parentTag];
769 NSMenuItem *item = [[NSMenuItem alloc] init];
770 NSMenu *menu = [[NSMenu alloc] initWithTitle:title];
772 [menu setAutoenablesItems:NO];
774 [item setTitle:title];
775 [item setSubmenu:menu];
778 if ([parent numberOfItems] <= idx) {
779 [parent addItem:item];
781 [parent insertItem:item atIndex:idx];
784 NSMutableArray *items = (MenuPopupType == parentTag)
785 ? popupMenuItems : mainMenuItems;
786 if ([items count] <= idx) {
787 [items addObject:item];
789 [items insertObject:item atIndex:idx];
792 shouldUpdateMainMenu = (MenuPopupType != parentTag);
799 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
800 title:(NSString *)title tip:(NSString *)tip
801 keyEquivalent:(int)key modifiers:(int)mask
802 action:(NSString *)action atIndex:(int)idx
805 NSMenuItem *item = nil;
806 if (!title || ([title hasPrefix:@"-"] && [title hasSuffix:@"-"])) {
807 item = [NSMenuItem separatorItem];
809 item = [[[NSMenuItem alloc] init] autorelease];
810 [item setTitle:title];
811 // TODO: Check that 'action' is a valid action (nothing will happen
812 // if it isn't, but it would be nice with a warning).
813 if (action) [item setAction:NSSelectorFromString(action)];
814 else [item setAction:@selector(vimMenuItemAction:)];
815 if (tip) [item setToolTip:tip];
818 NSString *keyString =
819 [NSString stringWithFormat:@"%C", key];
820 [item setKeyEquivalent:keyString];
821 [item setKeyEquivalentModifierMask:mask];
825 // NOTE! The tag is used to idenfity which menu items were
826 // added by Vim (tag != 0) and which were added by the AppKit
830 if ([parent numberOfItems] <= idx) {
831 [parent addItem:item];
833 [parent insertItem:item atIndex:idx];
836 NSLog(@"WARNING: Menu item '%@' (tag=%d) has no parent.", title, tag);
840 - (void)updateMainMenu
842 NSMenu *mainMenu = [NSApp mainMenu];
844 // Stop NSApp from updating the Window menu.
845 [NSApp setWindowsMenu:nil];
847 // Remove all menus from main menu (except the MacVim menu).
848 int i, count = [mainMenu numberOfItems];
849 for (i = count-1; i > 0; --i) {
850 [mainMenu removeItemAtIndex:i];
853 // Add menus from 'mainMenuItems' to main menu.
854 count = [mainMenuItems count];
855 for (i = 0; i < count; ++i) {
856 [mainMenu addItem:[mainMenuItems objectAtIndex:i]];
859 // Set the new Window menu.
860 // TODO! Need to look for 'Window' in all localized languages.
861 NSMenu *windowMenu = [[mainMenu itemWithTitle:@"Window"] submenu];
863 // Remove all AppKit owned menu items (tag == 0); they will be added
864 // again when setWindowsMenu: is called.
865 count = [windowMenu numberOfItems];
866 for (i = count-1; i >= 0; --i) {
867 NSMenuItem *item = [windowMenu itemAtIndex:i];
869 [windowMenu removeItem:item];
873 [NSApp setWindowsMenu:windowMenu];
876 shouldUpdateMainMenu = NO;
879 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index
881 if (!toolbar) return nil;
883 NSArray *items = [toolbar items];
884 int i, count = [items count];
885 for (i = 0; i < count; ++i) {
886 NSToolbarItem *item = [items objectAtIndex:i];
887 if ([item tag] == tag) {
888 if (index) *index = i;
896 - (IBAction)toolbarAction:(id)sender
898 NSLog(@"%s%@", _cmd, sender);
901 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
902 toolTip:(NSString *)tip icon:(NSString *)icon
904 // If the item corresponds to a separator then do nothing, since it is
905 // already defined by Cocoa.
906 if (!title || [title isEqual:NSToolbarSeparatorItemIdentifier]
907 || [title isEqual:NSToolbarSpaceItemIdentifier]
908 || [title isEqual:NSToolbarFlexibleSpaceItemIdentifier])
911 NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:title];
913 [item setLabel:title];
914 [item setToolTip:tip];
915 [item setAction:@selector(vimMenuItemAction:)];
916 [item setAutovalidates:NO];
918 NSImage *img = [NSImage imageNamed:icon];
920 NSLog(@"WARNING: Could not find image with name '%@' to use as toolbar"
921 " image for identifier '%@';"
922 " using default toolbar icon '%@' instead.",
923 icon, title, DefaultToolbarImageName);
925 img = [NSImage imageNamed:DefaultToolbarImageName];
930 [toolbarItemDict setObject:item forKey:title];
935 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label tip:(NSString
936 *)tip icon:(NSString *)icon atIndex:(int)idx
938 if (!toolbar) return;
940 // Check for separator items.
942 label = NSToolbarSeparatorItemIdentifier;
943 } else if ([label length] >= 2 && [label hasPrefix:@"-"]
944 && [label hasSuffix:@"-"]) {
945 // The label begins and ends with '-'; decided which kind of separator
946 // item it is by looking at the prefix.
947 if ([label hasPrefix:@"-space"]) {
948 label = NSToolbarSpaceItemIdentifier;
949 } else if ([label hasPrefix:@"-flexspace"]) {
950 label = NSToolbarFlexibleSpaceItemIdentifier;
952 label = NSToolbarSeparatorItemIdentifier;
956 [self addToolbarItemToDictionaryWithTag:tag label:label toolTip:tip
959 int maxIdx = [[toolbar items] count];
960 if (maxIdx < idx) idx = maxIdx;
962 [toolbar insertItemWithItemIdentifier:label atIndex:idx];
965 - (void)connectionDidDie:(NSNotification *)notification
967 //NSLog(@"A MMVimController lost its connection to the backend; "
968 // "closing the controller.");
969 [windowController close];
972 - (BOOL)executeActionWithName:(NSString *)name
975 static NSDictionary *actionDict = nil;
978 NSBundle *mainBundle = [NSBundle mainBundle];
979 NSString *path = [mainBundle pathForResource:@"Actions"
982 actionDict = [[NSDictionary alloc] initWithContentsOfFile:path];
983 NSLog(@"Actions = %@", actionDict);
985 NSLog(@"WARNING: Failed to load dictionary of actions "
991 if ([actionDict objectForKey:name]) {
992 NSLog(@"Executing action %@", name);
993 SEL sel = NSSelectorFromString(name);
995 if ([NSApp sendAction:sel to:nil from:self])
998 NSLog(@"WARNING: Failed to send action");
1000 NSLog(@"WARNING: Action with name '%@' cannot be executed.", name);
1007 @end // MMVimController (Private)
1011 @implementation NSColor (MMProtocol)
1013 + (NSColor *)colorWithRgbInt:(int)rgb
1015 float r = ((rgb>>16) & 0xff)/255.0f;
1016 float g = ((rgb>>8) & 0xff)/255.0f;
1017 float b = (rgb & 0xff)/255.0f;
1019 return [NSColor colorWithCalibratedRed:r green:g blue:b alpha:1.0f];
1022 @end // NSColor (MMProtocol)