git-svn-id: http://macvim.googlecode.com/svn/trunk@139 96c4425d-ca35-0410-94e5-3396d5...
[MacVim/jjgod.git] / MMVimController.m
blob99e48fda5e11c83e2f76a3ca2a16da8b6467280a
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: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];
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                 NSWindow *win = [windowController window];
413                 [win setToolbar:toolbar];
415                 // HACK! Redirect the pill button so that we can ask Vim to
416                 // hide the toolbar.
417                 NSButton *pillButton = [win
418                     standardWindowButton:NSWindowToolbarButton];
419                 if (pillButton) {
420                     [pillButton setAction:@selector(toggleToolbar:)];
421                     [pillButton setTarget:windowController];
422                 }
423             }
424         } else if (title) {
425             [self addMenuWithTag:tag parent:parentTag title:title atIndex:idx];
426         }
428         [title release];
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);
435         if (namelen > 0) {
436             title = [[NSString alloc] initWithBytes:(void*)bytes length:namelen
437                                            encoding:NSUTF8StringEncoding];
438             bytes += namelen;
439         }
440         int tiplen = *((int*)bytes);  bytes += sizeof(int);
441         if (tiplen > 0) {
442             tip = [[NSString alloc] initWithBytes:(void*)bytes length:tiplen
443                                            encoding:NSUTF8StringEncoding];
444             bytes += tiplen;
445         }
446         int iconlen = *((int*)bytes);  bytes += sizeof(int);
447         if (iconlen > 0) {
448             icon = [[NSString alloc] initWithBytes:(void*)bytes length:iconlen
449                                            encoding:NSUTF8StringEncoding];
450             bytes += iconlen;
451         }
452         int actionlen = *((int*)bytes);  bytes += sizeof(int);
453         if (actionlen > 0) {
454             action = [[NSString alloc] initWithBytes:(void*)bytes
455                                               length:actionlen
456                                             encoding:NSUTF8StringEncoding];
457             bytes += actionlen;
458         }
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
468                                 atIndex:idx];
469         } else {
470             NSMenu *parent = [self menuForTag:parentTag];
471             [self addMenuItemWithTag:tag parent:parent title:title tip:tip
472                        keyEquivalent:key modifiers:mask action:action
473                              atIndex:idx];
474         }
476         [title release];
477         [tip release];
478         [icon release];
479         [action release];
480     } else if (RemoveMenuItemMsgID == msgid) {
481         const void *bytes = [data bytes];
482         int tag = *((int*)bytes);  bytes += sizeof(int);
484         id item;
485         int idx;
486         if ((item = [self toolbarItemForTag:tag index:&idx])) {
487             [toolbar removeItemAtIndex:idx];
488         } else if ((item = [self menuItemForTag:tag])) {
489             [item retain];
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];
498             }
500             if ([item menu])
501                 [[item menu] removeItem:item];
503             [item release];
504         }
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];
511         if (!item)
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;
526         }
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
556                                     identifier:ident];
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
564                                       identifier:ident];
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];
574         if (font)
575             [windowController setFont:font];
577         [name release];
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
591                              length:len
592                            encoding:NSUTF8StringEncoding
593                        freeWhenDone:NO];
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
606                              length:len
607                            encoding:NSUTF8StringEncoding
608                        freeWhenDone:NO];
610         NSMenu *menu = [self topLevelMenuForTitle:title];
611         if (menu) {
612             [windowController popupMenu:menu atRow:row column:col];
613         } else {
614             NSLog(@"WARNING: Cannot popup menu with title %@; no such menu.",
615                     title);
616         }
618         [title release];
619     } else {
620         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
621     }
624 - (void)performBatchDrawWithData:(NSData *)data
626     // TODO!  Move to window controller.
627     MMTextStorage *textStorage = [windowController textStorage];
628     if (!textStorage)
629         return;
631     const void *bytes = [data bytes];
632     const void *end = bytes + [data length];
634     [textStorage beginEditing];
636     // TODO:
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
677                                  length:len
678                                encoding:NSUTF8StringEncoding
679                            freeWhenDone:NO];
680             bytes += len;
682             [textStorage replaceString:string
683                                  atRow:row column:col
684                              withFlags:flags
685                        foregroundColor:[NSColor colorWithRgbInt:fg]
686                        backgroundColor:[NSColor colorWithRgbInt:bg]];
688             [string release];
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]];
700         } else {
701             NSLog(@"WARNING: Unknown draw type (type=%d)", type);
702         }
703     }
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;
725     }
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;
734     }
736     return nil;
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];
753     }
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];
760     }
762     return nil;
765 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
766                atIndex:(int)idx
768     NSMenu *parent = [self menuForTag:parentTag];
769     NSMenuItem *item = [[NSMenuItem alloc] init];
770     NSMenu *menu = [[NSMenu alloc] initWithTitle:title];
772     [menu setAutoenablesItems:NO];
773     [item setTag:tag];
774     [item setTitle:title];
775     [item setSubmenu:menu];
777     if (parent) {
778         if ([parent numberOfItems] <= idx) {
779             [parent addItem:item];
780         } else {
781             [parent insertItem:item atIndex:idx];
782         }
783     } else {
784         NSMutableArray *items = (MenuPopupType == parentTag)
785             ? popupMenuItems : mainMenuItems;
786         if ([items count] <= idx) {
787             [items addObject:item];
788         } else {
789             [items insertObject:item atIndex:idx];
790         }
792         shouldUpdateMainMenu = (MenuPopupType != parentTag);
793     }
795     [item release];
796     [menu release];
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
804     if (parent) {
805         NSMenuItem *item = nil;
806         if (!title || ([title hasPrefix:@"-"] && [title hasSuffix:@"-"])) {
807             item = [NSMenuItem separatorItem];
808         } else {
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];
817             if (key != 0) {
818                 NSString *keyString =
819                     [NSString stringWithFormat:@"%C", key];
820                 [item setKeyEquivalent:keyString];
821                 [item setKeyEquivalentModifierMask:mask];
822             }
823         }
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
827         // (tag == 0).
828         [item setTag:tag];
830         if ([parent numberOfItems] <= idx) {
831             [parent addItem:item];
832         } else {
833             [parent insertItem:item atIndex:idx];
834         }
835     } else {
836         NSLog(@"WARNING: Menu item '%@' (tag=%d) has no parent.", title, tag);
837     }
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];
851     }
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]];
857     }
859     // Set the new Window menu.
860     // TODO!  Need to look for 'Window' in all localized languages.
861     NSMenu *windowMenu = [[mainMenu itemWithTitle:@"Window"] submenu];
862     if (windowMenu) {
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];
868             if (![item tag]) {
869                 [windowMenu removeItem:item];
870             }
871         }
873         [NSApp setWindowsMenu:windowMenu];
874     }
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;
889             return item;
890         }
891     }
893     return nil;
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])
909         return;
911     NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:title];
912     [item setTag:tag];
913     [item setLabel:title];
914     [item setToolTip:tip];
915     [item setAction:@selector(vimMenuItemAction:)];
916     [item setAutovalidates:NO];
918     NSImage *img = [NSImage imageNamed:icon];
919     if (!img) {
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];
926     }
928     [item setImage:img];
930     [toolbarItemDict setObject:item forKey:title];
932     [item release];
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.
941     if (!label) {
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;
951         } else {
952             label = NSToolbarSeparatorItemIdentifier;
953         }
954     }
956     [self addToolbarItemToDictionaryWithTag:tag label:label toolTip:tip
957                                        icon:icon];
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
974 #if 0
975     static NSDictionary *actionDict = nil;
977     if (!actionDict) {
978         NSBundle *mainBundle = [NSBundle mainBundle];
979         NSString *path = [mainBundle pathForResource:@"Actions"
980                                               ofType:@"plist"];
981         if (path) {
982             actionDict = [[NSDictionary alloc] initWithContentsOfFile:path];
983             NSLog(@"Actions = %@", actionDict);
984         } else {
985             NSLog(@"WARNING: Failed to load dictionary of actions "
986                     "(Actions.plist).");
987             return NO;
988         }
989     }
991     if ([actionDict objectForKey:name]) {
992         NSLog(@"Executing action %@", name);
993         SEL sel = NSSelectorFromString(name);
995         if ([NSApp sendAction:sel to:nil from:self])
996             return YES;
998         NSLog(@"WARNING: Failed to send action");
999     } else {
1000         NSLog(@"WARNING: Action with name '%@' cannot be executed.", name);
1001     }
1003 #endif
1004     return NO;
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)