- Moved user default keys to MacVim.[h|m]
[MacVim/jjgod.git] / MMVimController.m
blob41fddbf8f7c2a2a4f4082db5e64a703896c14f02
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
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.
9  */
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
31                atIndex:(int)idx;
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
43                       atIndex:(int)idx;
44 - (void)connectionDidDie:(NSNotification *)notification;
45 - (BOOL)executeActionWithName:(NSString *)name;
46 @end
50 // TODO: Move to separate file
51 @interface NSColor (MMProtocol)
52 + (NSColor *)colorWithRgbInt:(int)rgb;
53 @end
57 static NSMenuItem *findMenuItemWithTagInMenu(NSMenu *root, int tag)
59     if (root) {
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;
70             }
71         }
72     }
74     return nil;
79 @implementation MMVimController
81 - (id)initWithBackend:(id)backend
83     if ((self = [super init])) {
84         windowController =
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];
96     }
98     return self;
101 - (void)dealloc
103     //NSLog(@"%@ %s", [self className], _cmd);
105     [[NSNotificationCenter defaultCenter] removeObserver:self];
107     [backendProxy release];
108     [sendQueue release];
110     [toolbarItemDict release];
111     [toolbar release];
112     [popupMenuItems release];
113     [mainMenuItems release];
114     [windowController release];
116     [super dealloc];
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]];
129         if (data)
130             [sendQueue addObject:data];
131         else
132             [sendQueue addObject:[NSNull null]];
133         return;
134     }
136     if (wait) {
137         [backendProxy processInput:msgid data:data];
138     } else {
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];
142         if (connection) {
143             NSTimeInterval req = [connection requestTimeout];
144             [connection setRequestTimeout:0];
145             @try {
146                 [backendProxy processInput:msgid data:data];
147             }
148             @catch (NSException *e) {
149                 // Connection timed out, just ignore this.
150                 //NSLog(@"WARNING! Connection timed out in %s", _cmd);
151             }
152             @finally {
153                 [connection setRequestTimeout:req];
154             }
155         }
156     }
159 - (id)backendProxy
161     return backendProxy;
164 - (oneway void)showSavePanelForDirectory:(in bycopy NSString *)dir
165                                    title:(in bycopy NSString *)title
166                                   saving:(int)saving
168     [windowController setStatusText:title];
170     if (saving) {
171         [[NSSavePanel savePanel] beginSheetForDirectory:dir file:nil
172                 modalForWindow:[windowController window]
173                  modalDelegate:self
174                 didEndSelector:@selector(panelDidEnd:code:context:)
175                    contextInfo:NULL];
176     } else {
177         NSOpenPanel *panel = [NSOpenPanel openPanel];
178         [panel setAllowsMultipleSelection:NO];
179         [panel beginSheetForDirectory:dir file:nil types:nil
180                 modalForWindow:[windowController window]
181                  modalDelegate:self
182                 didEndSelector:@selector(panelDidEnd:code:context:)
183                    contextInfo:NULL];
184     }
187 - (oneway void)processCommandQueue:(in NSArray *)queue
189     unsigned i, count = [queue count];
190     if (count % 2) {
191         NSLog(@"WARNING: Uneven number of components (%d) in flush queue "
192                 "message; ignoring this message.", count);
193         return;
194     }
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]);
204 #if 0
205         if (msgid != EnableMenuItemMsgID && msgid != AddMenuItemMsgID
206                 && msgid != AddMenuMsgID) {
207             NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
208         }
209 #endif
211         [self handleMessage:msgid data:data];
212     }
213     //NSLog(@"======== %s  END  ========", _cmd);
215     if (shouldUpdateMainMenu) {
216         [self updateMainMenu];
217     }
219     [windowController processCommandQueueDidFinish];
221     inProcessCommandQueue = NO;
223     count = [sendQueue count];
224     if (count > 0) {
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]])
232                     data = nil;
234                 [backendProxy processInput:msgid data:data];
235             }
236         }
238         [sendQueue removeAllObjects];
239     }
242 - (void)windowWillClose:(NSNotification *)notification
244     // NOTE!  This causes the call to removeVimController: to be delayed.
245     [[NSApp delegate]
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];
262     if (!item) {
263         NSLog(@"WARNING:  No toolbar item with id '%@'", itemId);
264     }
266     return item;
269 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)theToolbar
271     //NSLog(@"%s", _cmd);
272     return nil;
275 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)theToolbar
277     //NSLog(@"%s", _cmd);
278     return nil;
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];
303 #endif
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:self];
310     } else if (HideTabBarMsgID == msgid) {
311         //NSLog(@"Hiding tab bar");
312         [windowController hideTabBar:self];
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];
328         [string release];
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);
334         NSString *dir = nil;
335         if (len > 0) {
336             dir = [[NSString alloc] initWithBytes:(void*)bytes
337                                            length:len
338                                          encoding:NSUTF8StringEncoding];
339             bytes += len;
340         }
342         len = *((int*)bytes);  bytes += sizeof(int);
343         if (len > 0) {
344             NSString *title = [[NSString alloc]
345                     initWithBytes:(void*)bytes length:len
346                          encoding:NSUTF8StringEncoding];
347             bytes += len;
349             [windowController setStatusText:title];
350             [title release];
351         }
353         if (save) {
354             [[NSSavePanel savePanel] beginSheetForDirectory:dir file:nil
355                 modalForWindow:[windowController window]
356                  modalDelegate:self
357                 didEndSelector:@selector(panelDidEnd:code:context:)
358                    contextInfo:NULL];
359         } else {
360             NSOpenPanel *panel = [NSOpenPanel openPanel];
361             [panel setAllowsMultipleSelection:NO];
362             [panel beginSheetForDirectory:dir file:nil types:nil
363                     modalForWindow:[windowController window]
364                      modalDelegate:self
365                     didEndSelector:@selector(panelDidEnd:code:context:)
366                        contextInfo:NULL];
367         }
369         [dir release];
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];
379         if (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];
386         }
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);
393         if (len > 0) {
394             title = [[NSString alloc] initWithBytes:(void*)bytes length:len
395                                            encoding:NSUTF8StringEncoding];
396             bytes += len;
397         }
398         int idx = *((int*)bytes);  bytes += sizeof(int);
400         if (MenuToolbarType == parentTag) {
401             if (!toolbar) {
402                 NSString *ident = [NSString stringWithFormat:@"%d.%d",
403                          (int)self, tag];
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                 [[windowController window] setToolbar:toolbar];
413             }
414         } else if (title) {
415             [self addMenuWithTag:tag parent:parentTag title:title atIndex:idx];
416         }
418         [title release];
419     } else if (AddMenuItemMsgID == msgid) {
420         NSString *title = nil, *tip = nil, *icon = nil, *action = nil;
421         const void *bytes = [data bytes];
422         int tag = *((int*)bytes);  bytes += sizeof(int);
423         int parentTag = *((int*)bytes);  bytes += sizeof(int);
424         int namelen = *((int*)bytes);  bytes += sizeof(int);
425         if (namelen > 0) {
426             title = [[NSString alloc] initWithBytes:(void*)bytes length:namelen
427                                            encoding:NSUTF8StringEncoding];
428             bytes += namelen;
429         }
430         int tiplen = *((int*)bytes);  bytes += sizeof(int);
431         if (tiplen > 0) {
432             tip = [[NSString alloc] initWithBytes:(void*)bytes length:tiplen
433                                            encoding:NSUTF8StringEncoding];
434             bytes += tiplen;
435         }
436         int iconlen = *((int*)bytes);  bytes += sizeof(int);
437         if (iconlen > 0) {
438             icon = [[NSString alloc] initWithBytes:(void*)bytes length:iconlen
439                                            encoding:NSUTF8StringEncoding];
440             bytes += iconlen;
441         }
442         int actionlen = *((int*)bytes);  bytes += sizeof(int);
443         if (actionlen > 0) {
444             action = [[NSString alloc] initWithBytes:(void*)bytes
445                                               length:actionlen
446                                             encoding:NSUTF8StringEncoding];
447             bytes += actionlen;
448         }
449         int idx = *((int*)bytes);  bytes += sizeof(int);
450         if (idx < 0) idx = 0;
451         int key = *((int*)bytes);  bytes += sizeof(int);
452         int mask = *((int*)bytes);  bytes += sizeof(int);
454         NSString *ident = [NSString stringWithFormat:@"%d.%d",
455                 (int)self, parentTag];
456         if (toolbar && [[toolbar identifier] isEqual:ident]) {
457             [self addToolbarItemWithTag:tag label:title tip:tip icon:icon
458                                 atIndex:idx];
459         } else {
460             NSMenu *parent = [self menuForTag:parentTag];
461             [self addMenuItemWithTag:tag parent:parent title:title tip:tip
462                        keyEquivalent:key modifiers:mask action:action
463                              atIndex:idx];
464         }
466         [title release];
467         [tip release];
468         [icon release];
469         [action release];
470     } else if (RemoveMenuItemMsgID == msgid) {
471         const void *bytes = [data bytes];
472         int tag = *((int*)bytes);  bytes += sizeof(int);
474         id item;
475         int idx;
476         if ((item = [self toolbarItemForTag:tag index:&idx])) {
477             [toolbar removeItemAtIndex:idx];
478         } else if ((item = [self menuItemForTag:tag])) {
479             [item retain];
481             if ([item menu] == [NSApp mainMenu] || ![item menu]) {
482                 //NSLog(@"Removing menu: %@", item);
483                 // NOTE: To be on the safe side we try to remove the item from
484                 // both arrays (it is ok to call removeObject: even if an array
485                 // does not contain the object to remove).
486                 [mainMenuItems removeObject:item];
487                 [popupMenuItems removeObject:item];
488             }
490             if ([item menu])
491                 [[item menu] removeItem:item];
493             [item release];
494         }
495     } else if (EnableMenuItemMsgID == msgid) {
496         const void *bytes = [data bytes];
497         int tag = *((int*)bytes);  bytes += sizeof(int);
498         int state = *((int*)bytes);  bytes += sizeof(int);
500         id item = [self toolbarItemForTag:tag index:NULL];
501         if (!item)
502             item = [self menuItemForTag:tag];
504         [item setEnabled:state];
505     } else if (ShowToolbarMsgID == msgid) {
506         const void *bytes = [data bytes];
507         int enable = *((int*)bytes);  bytes += sizeof(int);
508         int flags = *((int*)bytes);  bytes += sizeof(int);
510         int mode = NSToolbarDisplayModeDefault;
511         if (flags & ToolbarLabelFlag) {
512             mode = flags & ToolbarIconFlag ? NSToolbarDisplayModeIconAndLabel
513                     : NSToolbarDisplayModeLabelOnly;
514         } else if (flags & ToolbarIconFlag) {
515             mode = NSToolbarDisplayModeIconOnly;
516         }
518         int size = flags & ToolbarSizeRegularFlag ? NSToolbarSizeModeRegular
519                 : NSToolbarSizeModeSmall;
521         [toolbar setSizeMode:size];
522         [toolbar setDisplayMode:mode];
523         [toolbar setVisible:enable];
524     } else if (CreateScrollbarMsgID == msgid) {
525         const void *bytes = [data bytes];
526         long ident = *((long*)bytes);  bytes += sizeof(long);
527         int type = *((int*)bytes);  bytes += sizeof(int);
529         [windowController createScrollbarWithIdentifier:ident type:type];
530     } else if (DestroyScrollbarMsgID == msgid) {
531         const void *bytes = [data bytes];
532         long ident = *((long*)bytes);  bytes += sizeof(long);
534         [windowController destroyScrollbarWithIdentifier:ident];
535     } else if (ShowScrollbarMsgID == msgid) {
536         const void *bytes = [data bytes];
537         long ident = *((long*)bytes);  bytes += sizeof(long);
538         int visible = *((int*)bytes);  bytes += sizeof(int);
540         [windowController showScrollbarWithIdentifier:ident state:visible];
541     } else if (SetScrollbarPositionMsgID == msgid) {
542         const void *bytes = [data bytes];
543         long ident = *((long*)bytes);  bytes += sizeof(long);
544         int pos = *((int*)bytes);  bytes += sizeof(int);
545         int len = *((int*)bytes);  bytes += sizeof(int);
547         [windowController setScrollbarPosition:pos length:len
548                                     identifier:ident];
549     } else if (SetScrollbarThumbMsgID == msgid) {
550         const void *bytes = [data bytes];
551         long ident = *((long*)bytes);  bytes += sizeof(long);
552         float val = *((float*)bytes);  bytes += sizeof(float);
553         float prop = *((float*)bytes);  bytes += sizeof(float);
555         [windowController setScrollbarThumbValue:val proportion:prop
556                                       identifier:ident];
557     } else if (SetFontMsgID == msgid) {
558         const void *bytes = [data bytes];
559         float size = *((float*)bytes);  bytes += sizeof(float);
560         int len = *((int*)bytes);  bytes += sizeof(int);
561         NSString *name = [[NSString alloc]
562                 initWithBytes:(void*)bytes length:len
563                      encoding:NSUTF8StringEncoding];
564         NSFont *font = [NSFont fontWithName:name size:size];
566         if (font)
567             [windowController setFont:font];
569         [name release];
570     } else if (SetDefaultColorsMsgID == msgid) {
571         const void *bytes = [data bytes];
572         int bg = *((int*)bytes);  bytes += sizeof(int);
573         int fg = *((int*)bytes);  bytes += sizeof(int);
574         NSColor *back = [NSColor colorWithRgbInt:bg];
575         NSColor *fore = [NSColor colorWithRgbInt:fg];
577         [windowController setDefaultColorsBackground:back foreground:fore];
578     } else if (ExecuteActionMsgID == msgid) {
579         const void *bytes = [data bytes];
580         int len = *((int*)bytes);  bytes += sizeof(int);
581         NSString *actionName = [[NSString alloc]
582                 initWithBytesNoCopy:(void*)bytes
583                              length:len
584                            encoding:NSUTF8StringEncoding
585                        freeWhenDone:NO];
587         SEL sel = NSSelectorFromString(actionName);
588         [NSApp sendAction:sel to:nil from:self];
590         [actionName release];
591     } else if (ShowPopupMenuMsgID == msgid) {
592         const void *bytes = [data bytes];
593         int row = *((int*)bytes);  bytes += sizeof(int);
594         int col = *((int*)bytes);  bytes += sizeof(int);
595         int len = *((int*)bytes);  bytes += sizeof(int);
596         NSString *title = [[NSString alloc]
597                 initWithBytesNoCopy:(void*)bytes
598                              length:len
599                            encoding:NSUTF8StringEncoding
600                        freeWhenDone:NO];
602         NSMenu *menu = [self topLevelMenuForTitle:title];
603         if (menu) {
604             [windowController popupMenu:menu atRow:row column:col];
605         } else {
606             NSLog(@"WARNING: Cannot popup menu with title %@; no such menu.",
607                     title);
608         }
610         [title release];
611     } else {
612         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
613     }
616 - (void)performBatchDrawWithData:(NSData *)data
618     // TODO!  Move to window controller.
619     MMTextStorage *textStorage = [windowController textStorage];
620     if (!textStorage)
621         return;
623     const void *bytes = [data bytes];
624     const void *end = bytes + [data length];
626     [textStorage beginEditing];
628     // TODO:
629     // 1. Sanity check input
630     // 2. Cache rgb -> NSColor lookups?
632     while (bytes < end) {
633         int type = *((int*)bytes);  bytes += sizeof(int);
635         if (ClearAllDrawType == type) {
636             int color = *((int*)bytes);  bytes += sizeof(int);
638             [textStorage clearAllWithColor:[NSColor colorWithRgbInt:color]];
639         } else if (ClearBlockDrawType == type) {
640             int color = *((int*)bytes);  bytes += sizeof(int);
641             int row1 = *((int*)bytes);  bytes += sizeof(int);
642             int col1 = *((int*)bytes);  bytes += sizeof(int);
643             int row2 = *((int*)bytes);  bytes += sizeof(int);
644             int col2 = *((int*)bytes);  bytes += sizeof(int);
646             [textStorage clearBlockFromRow:row1 column:col1
647                     toRow:row2 column:col2
648                     color:[NSColor colorWithRgbInt:color]];
649         } else if (DeleteLinesDrawType == type) {
650             int color = *((int*)bytes);  bytes += sizeof(int);
651             int row = *((int*)bytes);  bytes += sizeof(int);
652             int count = *((int*)bytes);  bytes += sizeof(int);
653             int bot = *((int*)bytes);  bytes += sizeof(int);
654             int left = *((int*)bytes);  bytes += sizeof(int);
655             int right = *((int*)bytes);  bytes += sizeof(int);
657             [textStorage deleteLinesFromRow:row lineCount:count
658                     scrollBottom:bot left:left right:right
659                            color:[NSColor colorWithRgbInt:color]];
660         } else if (ReplaceStringDrawType == type) {
661             int bg = *((int*)bytes);  bytes += sizeof(int);
662             int fg = *((int*)bytes);  bytes += sizeof(int);
663             int row = *((int*)bytes);  bytes += sizeof(int);
664             int col = *((int*)bytes);  bytes += sizeof(int);
665             int flags = *((int*)bytes);  bytes += sizeof(int);
666             int len = *((int*)bytes);  bytes += sizeof(int);
667             NSString *string = [[NSString alloc]
668                     initWithBytesNoCopy:(void*)bytes
669                                  length:len
670                                encoding:NSUTF8StringEncoding
671                            freeWhenDone:NO];
672             bytes += len;
674             [textStorage replaceString:string
675                                  atRow:row column:col
676                              withFlags:flags
677                        foregroundColor:[NSColor colorWithRgbInt:fg]
678                        backgroundColor:[NSColor colorWithRgbInt:bg]];
680             [string release];
681         } else if (InsertLinesDrawType == type) {
682             int color = *((int*)bytes);  bytes += sizeof(int);
683             int row = *((int*)bytes);  bytes += sizeof(int);
684             int count = *((int*)bytes);  bytes += sizeof(int);
685             int bot = *((int*)bytes);  bytes += sizeof(int);
686             int left = *((int*)bytes);  bytes += sizeof(int);
687             int right = *((int*)bytes);  bytes += sizeof(int);
689             [textStorage insertLinesAtRow:row lineCount:count
690                              scrollBottom:bot left:left right:right
691                                     color:[NSColor colorWithRgbInt:color]];
692         } else {
693             NSLog(@"WARNING: Unknown draw type (type=%d)", type);
694         }
695     }
697     [textStorage endEditing];
700 - (void)panelDidEnd:(NSSavePanel *)panel code:(int)code context:(void *)context
702     [windowController setStatusText:@""];
704     NSString *string = (code == NSOKButton) ? [panel filename] : nil;
705     [backendProxy setBrowseForFileString:string];
708 - (NSMenuItem *)menuItemForTag:(int)tag
710     // Search the main menu.
711     int i, count = [mainMenuItems count];
712     for (i = 0; i < count; ++i) {
713         NSMenuItem *item = [mainMenuItems objectAtIndex:i];
714         if ([item tag] == tag) return item;
715         item = findMenuItemWithTagInMenu([item submenu], tag);
716         if (item) return item;
717     }
719     // Search the popup menus.
720     count = [popupMenuItems count];
721     for (i = 0; i < count; ++i) {
722         NSMenuItem *item = [popupMenuItems objectAtIndex:i];
723         if ([item tag] == tag) return item;
724         item = findMenuItemWithTagInMenu([item submenu], tag);
725         if (item) return item;
726     }
728     return nil;
731 - (NSMenu *)menuForTag:(int)tag
733     return [[self menuItemForTag:tag] submenu];
736 - (NSMenu *)topLevelMenuForTitle:(NSString *)title
738     // Search only the top-level menus.
740     unsigned i, count = [popupMenuItems count];
741     for (i = 0; i < count; ++i) {
742         NSMenuItem *item = [popupMenuItems objectAtIndex:i];
743         if ([title isEqual:[item title]])
744             return [item submenu];
745     }
747     count = [mainMenuItems count];
748     for (i = 0; i < count; ++i) {
749         NSMenuItem *item = [mainMenuItems objectAtIndex:i];
750         if ([title isEqual:[item title]])
751             return [item submenu];
752     }
754     return nil;
757 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
758                atIndex:(int)idx
760     NSMenu *parent = [self menuForTag:parentTag];
761     NSMenuItem *item = [[NSMenuItem alloc] init];
762     NSMenu *menu = [[NSMenu alloc] initWithTitle:title];
764     [menu setAutoenablesItems:NO];
765     [item setTag:tag];
766     [item setTitle:title];
767     [item setSubmenu:menu];
769     if (parent) {
770         if ([parent numberOfItems] <= idx) {
771             [parent addItem:item];
772         } else {
773             [parent insertItem:item atIndex:idx];
774         }
775     } else {
776         NSMutableArray *items = (MenuPopupType == parentTag)
777             ? popupMenuItems : mainMenuItems;
778         if ([items count] <= idx) {
779             [items addObject:item];
780         } else {
781             [items insertObject:item atIndex:idx];
782         }
784         shouldUpdateMainMenu = (MenuPopupType != parentTag);
785     }
787     [item release];
788     [menu release];
791 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
792                      title:(NSString *)title tip:(NSString *)tip
793              keyEquivalent:(int)key modifiers:(int)mask
794                     action:(NSString *)action atIndex:(int)idx
796     if (parent) {
797         NSMenuItem *item = nil;
798         if (title) {
799             item = [[[NSMenuItem alloc] init] autorelease];
800             [item setTitle:title];
801             // TODO: Check that 'action' is a valid action (nothing will happen
802             // if it isn't, but it would be nice with a warning).
803             if (action) [item setAction:NSSelectorFromString(action)];
804             else        [item setAction:@selector(vimMenuItemAction:)];
805             if (tip) [item setToolTip:tip];
807             if (key != 0) {
808                 NSString *keyString =
809                     [NSString stringWithFormat:@"%C", key];
810                 [item setKeyEquivalent:keyString];
811                 [item setKeyEquivalentModifierMask:mask];
812             }
813         } else {
814             item = [NSMenuItem separatorItem];
815         }
817         // NOTE!  The tag is used to idenfity which menu items were
818         // added by Vim (tag != 0) and which were added by the AppKit
819         // (tag == 0).
820         [item setTag:tag];
822         if ([parent numberOfItems] <= idx) {
823             [parent addItem:item];
824         } else {
825             [parent insertItem:item atIndex:idx];
826         }
827     } else {
828         NSLog(@"WARNING: Menu item '%@' (tag=%d) has no parent.", title, tag);
829     }
832 - (void)updateMainMenu
834     NSMenu *mainMenu = [NSApp mainMenu];
836     // Stop NSApp from updating the Window menu.
837     [NSApp setWindowsMenu:nil];
839     // Remove all menus from main menu (except the MacVim menu).
840     int i, count = [mainMenu numberOfItems];
841     for (i = count-1; i > 0; --i) {
842         [mainMenu removeItemAtIndex:i];
843     }
845     // Add menus from 'mainMenuItems' to main menu.
846     count = [mainMenuItems count];
847     for (i = 0; i < count; ++i) {
848         [mainMenu addItem:[mainMenuItems objectAtIndex:i]];
849     }
851     // Set the new Window menu.
852     // TODO!  Need to look for 'Window' in all localized languages.
853     NSMenu *windowMenu = [[mainMenu itemWithTitle:@"Window"] submenu];
854     if (windowMenu) {
855         // Remove all AppKit owned menu items (tag == 0); they will be added
856         // again when setWindowsMenu: is called.
857         count = [windowMenu numberOfItems];
858         for (i = count-1; i >= 0; --i) {
859             NSMenuItem *item = [windowMenu itemAtIndex:i];
860             if (![item tag]) {
861                 [windowMenu removeItem:item];
862             }
863         }
865         [NSApp setWindowsMenu:windowMenu];
866     }
868     shouldUpdateMainMenu = NO;
871 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index
873     if (!toolbar) return nil;
875     NSArray *items = [toolbar items];
876     int i, count = [items count];
877     for (i = 0; i < count; ++i) {
878         NSToolbarItem *item = [items objectAtIndex:i];
879         if ([item tag] == tag) {
880             if (index) *index = i;
881             return item;
882         }
883     }
885     return nil;
888 - (IBAction)toolbarAction:(id)sender
890     NSLog(@"%s%@", _cmd, sender);
893 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
894         toolTip:(NSString *)tip icon:(NSString *)icon
896     // NOTE!  'title' is nul for separator item.  Since this is already defined
897     // by Coca, we don't need to do anything here.
898     if (!title) return;
900     NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:title];
901     [item setTag:tag];
902     [item setLabel:title];
903     [item setToolTip:tip];
904     [item setAction:@selector(vimMenuItemAction:)];
905     [item setAutovalidates:NO];
907     NSImage *img = [NSImage imageNamed:icon];
908     if (!img) {
909         NSLog(@"WARNING: Could not find image with name '%@' to use as toolbar"
910                " image for identifier '%@';"
911                " using default toolbar icon '%@' instead.",
912                icon, title, DefaultToolbarImageName);
914         img = [NSImage imageNamed:DefaultToolbarImageName];
915     }
917     [item setImage:img];
919     [toolbarItemDict setObject:item forKey:title];
921     [item release];
924 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label tip:(NSString
925                    *)tip icon:(NSString *)icon atIndex:(int)idx
927     if (!toolbar) return;
929     [self addToolbarItemToDictionaryWithTag:tag label:label toolTip:tip
930                                        icon:icon];
932     int maxIdx = [[toolbar items] count];
933     if (maxIdx < idx) idx = maxIdx;
935     // If 'label' is nul, insert a separator.
936     if (!label) label = NSToolbarSeparatorItemIdentifier;
937     [toolbar insertItemWithItemIdentifier:label atIndex:idx];
940 - (void)connectionDidDie:(NSNotification *)notification
942     //NSLog(@"A MMVimController lost its connection to the backend; "
943     //       "closing the controller.");
944     [windowController close];
947 - (BOOL)executeActionWithName:(NSString *)name
949 #if 0
950     static NSDictionary *actionDict = nil;
952     if (!actionDict) {
953         NSBundle *mainBundle = [NSBundle mainBundle];
954         NSString *path = [mainBundle pathForResource:@"Actions"
955                                               ofType:@"plist"];
956         if (path) {
957             actionDict = [[NSDictionary alloc] initWithContentsOfFile:path];
958             NSLog(@"Actions = %@", actionDict);
959         } else {
960             NSLog(@"WARNING: Failed to load dictionary of actions "
961                     "(Actions.plist).");
962             return NO;
963         }
964     }
966     if ([actionDict objectForKey:name]) {
967         NSLog(@"Executing action %@", name);
968         SEL sel = NSSelectorFromString(name);
970         if ([NSApp sendAction:sel to:nil from:self])
971             return YES;
973         NSLog(@"WARNING: Failed to send action");
974     } else {
975         NSLog(@"WARNING: Action with name '%@' cannot be executed.", name);
976     }
978 #endif
979     return NO;
982 @end // MMVimController (Private)
986 @implementation NSColor (MMProtocol)
988 + (NSColor *)colorWithRgbInt:(int)rgb
990     float r = ((rgb>>16) & 0xff)/255.0f;
991     float g = ((rgb>>8) & 0xff)/255.0f;
992     float b = (rgb & 0xff)/255.0f;
994     return [NSColor colorWithCalibratedRed:r green:g blue:b alpha:1.0f];
997 @end // NSColor (MMProtocol)