Added support for dropping text on window.
[MacVim/jjgod.git] / MMVimController.m
blob2a5058feb39fa4e826d8c97f84b2a3cbf9847300
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 "MMAppController.h"
14 #import "MMTextView.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 - (void)addMenuWithTag:(int)tag parent:(NSMenu *)parent title:(NSString *)title
30                atIndex:(int)idx;
31 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
32                      title:(NSString *)title tip:(NSString *)tip
33              keyEquivalent:(int)key modifiers:(int)mask
34                     action:(NSString *)action atIndex:(int)idx;
35 - (void)updateMainMenu;
36 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index;
37 - (IBAction)toolbarAction:(id)sender;
38 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
39         toolTip:(NSString *)tip icon:(NSString *)icon;
40 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label
41                           tip:(NSString *)tip icon:(NSString *)icon
42                       atIndex:(int)idx;
43 #if MM_USE_DO
44 - (void)connectionDidDie:(NSNotification *)notification;
45 #endif
46 - (BOOL)executeActionWithName:(NSString *)name;
47 @end
51 // TODO: Move to separate file
52 @interface NSColor (MMProtocol)
53 + (NSColor *)colorWithRgbInt:(int)rgb;
54 @end
58 static NSMenuItem *findMenuItemWithTagInMenu(NSMenu *root, int tag)
60     if (root) {
61         NSMenuItem *item = [root itemWithTag:tag];
62         if (item) return item;
64         NSArray *items = [root itemArray];
65         unsigned i, count = [items count];
66         for (i = 0; i < count; ++i) {
67             item = [items objectAtIndex:i];
68             if ([item hasSubmenu]) {
69                 item = findMenuItemWithTagInMenu([item submenu], tag);
70                 if (item) return item;
71             }
72         }
73     }
75     return nil;
80 @implementation MMVimController
82 #if MM_USE_DO
83 - (id)initWithBackend:(id)backend
84 #else
85 - (id)initWithPort:(NSPort *)port
86 #endif
88     if ((self = [super init])) {
89         windowController =
90             [[MMWindowController alloc] initWithVimController:self];
91 #if MM_USE_DO
92         backendProxy = [backend retain];
93 # if MM_DELAY_SEND_IN_PROCESS_CMD_QUEUE
94         sendQueue = [NSMutableArray new];
95 # endif
97         NSConnection *connection = [backendProxy connectionForProxy];
98         [[NSNotificationCenter defaultCenter] addObserver:self
99                 selector:@selector(connectionDidDie:)
100                     name:NSConnectionDidDieNotification object:connection];
101 #else
102         sendPort = [port retain];
104         // Init receive port and send connected message to VimTask
105         receivePort = [NSMachPort new];
106         [receivePort setDelegate:self];
108         // Add to the default run loop mode as well as the event tracking mode;
109         // the latter ensures that updates from the VimTask reaches
110         // MMVimController whilst the user resizes a window with the mouse.
111         [[NSRunLoop currentRunLoop] addPort:receivePort
112                                     forMode:NSDefaultRunLoopMode];
113         [[NSRunLoop currentRunLoop] addPort:receivePort
114                                     forMode:NSEventTrackingRunLoopMode];
116         [NSPortMessage sendMessage:ConnectedMsgID withSendPort:sendPort
117                        receivePort:receivePort wait:YES];
118 #endif
120         mainMenuItems = [[NSMutableArray alloc] init];
122         toolbarItemDict = [[NSMutableDictionary alloc] init];
123         //[self addToolbarItemToDictionaryWithTag:0 label:@"Attention"
124         //                                toolTip:@"A toolbar item is missing"
125         //                                   icon:@"Attention"];
127         [[NSNotificationCenter defaultCenter]
128                 addObserver:self
129                    selector:@selector(windowWillClose:)
130                        name:NSWindowWillCloseNotification
131                      object:[windowController window]];
132         [[NSNotificationCenter defaultCenter]
133                 addObserver:self
134                    selector:@selector(windowDidBecomeMain:)
135                        name:NSWindowDidBecomeMainNotification
136                      object:[windowController window]];
138     }
140     return self;
143 - (void)dealloc
145     //NSLog(@"%@ %s", [self className], _cmd);
147     [[NSNotificationCenter defaultCenter] removeObserver:self];
149 #if MM_USE_DO
150     [backendProxy release];
151 # if MM_DELAY_SEND_IN_PROCESS_CMD_QUEUE
152     [sendQueue release];
153 # endif
154 #else
155     if (sendPort) {
156         // Kill task immediately
157         [NSPortMessage sendMessage:KillTaskMsgID withSendPort:sendPort
158                        receivePort:receivePort wait:NO];
159     }
161     [sendPort release];
162     [receivePort release];
163 #endif
165     [toolbarItemDict release];
166     [toolbar release];
167     [mainMenuItems release];
168     [windowController release];
170     [super dealloc];
173 - (MMWindowController *)windowController
175     return windowController;
178 - (void)sendMessage:(int)msgid data:(NSData *)data wait:(BOOL)wait
180 #if MM_USE_DO
181 # if MM_DELAY_SEND_IN_PROCESS_CMD_QUEUE
182     if (inProcessCommandQueue) {
183         //NSLog(@"In process command queue; delaying message send.");
184         [sendQueue addObject:[NSNumber numberWithInt:msgid]];
185         if (data)
186             [sendQueue addObject:data];
187         else
188             [sendQueue addObject:[NSNull null]];
189         return;
190     }
191 # endif
192     if (wait) {
193         [backendProxy processInput:msgid data:data];
194     } else {
195         // Do not wait for the message to be sent, i.e. drop the message if it
196         // can't be delivered immediately.
197         NSConnection *connection = [backendProxy connectionForProxy];
198         if (connection) {
199             NSTimeInterval req = [connection requestTimeout];
200             [connection setRequestTimeout:0];
201             @try {
202                 [backendProxy processInput:msgid data:data];
203             }
204             @catch (NSException *e) {
205                 // Connection timed out, just ignore this.
206                 //NSLog(@"WARNING! Connection timed out in %s", _cmd);
207             }
208             @finally {
209                 [connection setRequestTimeout:req];
210             }
211         }
212     }
213 #else
214     [NSPortMessage sendMessage:msgid withSendPort:sendPort data:data
215                           wait:wait];
216 #endif
219 #if MM_USE_DO
220 - (id)backendProxy
222     return backendProxy;
225 - (oneway void)showSavePanelForDirectory:(in bycopy NSString *)dir
226                                    title:(in bycopy NSString *)title
227                                   saving:(int)saving
229     [windowController setStatusText:title];
231     if (saving) {
232         [[NSSavePanel savePanel] beginSheetForDirectory:dir file:nil
233                 modalForWindow:[windowController window]
234                  modalDelegate:self
235                 didEndSelector:@selector(panelDidEnd:code:context:)
236                    contextInfo:NULL];
237     } else {
238         NSOpenPanel *panel = [NSOpenPanel openPanel];
239         [panel setAllowsMultipleSelection:NO];
240         [panel beginSheetForDirectory:dir file:nil types:nil
241                 modalForWindow:[windowController window]
242                  modalDelegate:self
243                 didEndSelector:@selector(panelDidEnd:code:context:)
244                    contextInfo:NULL];
245     }
248 - (oneway void)processCommandQueue:(in NSArray *)queue
250     unsigned i, count = [queue count];
251     if (count % 2) {
252         NSLog(@"WARNING: Uneven number of components (%d) in flush queue "
253                 "message; ignoring this message.", count);
254         return;
255     }
257 #if MM_DELAY_SEND_IN_PROCESS_CMD_QUEUE
258     inProcessCommandQueue = YES;
259 #endif
261     //NSLog(@"======== %s BEGIN ========", _cmd);
262     for (i = 0; i < count; i += 2) {
263         NSData *value = [queue objectAtIndex:i];
264         NSData *data = [queue objectAtIndex:i+1];
266         int msgid = *((int*)[value bytes]);
267 #if 0
268         if (msgid != EnableMenuItemMsgID && msgid != AddMenuItemMsgID
269                 && msgid != AddMenuMsgID) {
270             NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
271         }
272 #endif
274         [self handleMessage:msgid data:data];
275     }
276     //NSLog(@"======== %s  END  ========", _cmd);
278     if (shouldUpdateMainMenu) {
279         [self updateMainMenu];
280     }
282 #if MM_DELAY_SEND_IN_PROCESS_CMD_QUEUE
283     inProcessCommandQueue = NO;
285     count = [sendQueue count];
286     if (count > 0) {
287         if (count % 2 == 0) {
288             //NSLog(@"%s Sending %d queued messages", _cmd, count/2);
290             for (i = 0; i < count; i += 2) {
291                 int msgid = [[sendQueue objectAtIndex:i] intValue];
292                 id data = [sendQueue objectAtIndex:i+1];
293                 if ([data isEqual:[NSNull null]])
294                     data = nil;
296                 [backendProxy processInput:msgid data:data];
297             }
298         }
300         [sendQueue removeAllObjects];
301     }
302 #endif
305 #else // MM_USE_DO
307 - (NSPort *)sendPort
309     return sendPort;
312 - (void)handlePortMessage:(NSPortMessage *)portMessage
314     //NSLog(@"%@ %s %@", [self className], _cmd, portMessage);
316     NSArray *components = [portMessage components];
317     unsigned msgid = [portMessage msgid];
319     //NSLog(@"%s%d", _cmd, msgid);
321     if (FlushQueueMsgID == msgid) {
322         unsigned i, count = [components count];
323         if (count % 2) {
324             NSLog(@"WARNING: Uneven number of components (%d) in flush queue "
325                     "message; ignoring this message.", count);
326             return;
327         }
329         for (i = 0; i < count; i += 2) {
330             NSData *value = [components objectAtIndex:i];
331             NSData *data = [components objectAtIndex:i+1];
333             [self handleMessage:*((int*)[value bytes]) data:data];
334         }
335     } else {
336         NSData *data = nil;
337         if ([components count] > 0)
338             data = [components objectAtIndex:0];
340         [self handleMessage:msgid data:data];
341     }
343     if (shouldUpdateMainMenu) {
344         [self updateMainMenu];
345     }
347 #endif // MM_USE_DO
349 - (void)windowWillClose:(NSNotification *)notification
351     // NOTE!  This causes the call to removeVimController: to be delayed.
352     [[NSApp delegate]
353             performSelectorOnMainThread:@selector(removeVimController:)
354                              withObject:self waitUntilDone:NO];
357 - (void)windowDidBecomeMain:(NSNotification *)notification
359     [self updateMainMenu];
362 - (NSToolbarItem *)toolbar:(NSToolbar *)theToolbar
363     itemForItemIdentifier:(NSString *)itemId
364     willBeInsertedIntoToolbar:(BOOL)flag
366     //NSLog(@"%s", _cmd);
368     NSToolbarItem *item = [toolbarItemDict objectForKey:itemId];
369     if (!item) {
370         NSLog(@"WARNING:  No toolbar item with id '%@'", itemId);
371     }
373     return item;
376 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)theToolbar
378     //NSLog(@"%s", _cmd);
379     return nil;
382 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)theToolbar
384     //NSLog(@"%s", _cmd);
385     return nil;
388 @end // MMVimController
392 @implementation MMVimController (Private)
394 - (void)handleMessage:(int)msgid data:(NSData *)data
396     //NSLog(@"%@ %s", [self className], _cmd);
398     if (OpenVimWindowMsgID == msgid) {
399         [windowController openWindow];
400     }
401 #if !MM_USE_DO
402     else if (TaskExitedMsgID == msgid) {
403         //NSLog(@"Received task exited message from VimTask; closing window.");
405         // Release sendPort immediately to avoid dealloc trying to send a 'kill
406         // task' message to the task.
407         [sendPort release];  sendPort = nil;
408         // NOTE!  This causes windowWillClose: to be called, which in turn asks
409         // the MMAppController to remove this MMVimController.
410         [windowController close];
412         // HACK! Make sure no menu updating is done, we're about to close.
413         shouldUpdateMainMenu = NO;
414     }
415 #endif // !MM_USE_DO
416     else if (BatchDrawMsgID == msgid) {
417         //NSLog(@"Received batch draw message from VimTask.");
419         [self performBatchDrawWithData:data];
420     } else if (SelectTabMsgID == msgid) {
421 #if 0   // NOTE: Tab selection is done inside updateTabsWithData:.
422         const void *bytes = [data bytes];
423         int idx = *((int*)bytes);
424         //NSLog(@"Selecting tab with index %d", idx);
425         [windowController selectTabWithIndex:idx];
426 #endif
427     } else if (UpdateTabBarMsgID == msgid) {
428         //NSLog(@"Updating tabs");
429         [windowController updateTabsWithData:data];
430     } else if (ShowTabBarMsgID == msgid) {
431         //NSLog(@"Showing tab bar");
433         // HACK! Vim sends several draw commands etc. after the show message
434         // and these can mess up the display when showing the tab bar results
435         // in the window having to resize to fit the screen; delaying this
436         // message alleviates this problem.
437         [windowController performSelectorOnMainThread:@selector(showTabBar:)
438                                            withObject:self waitUntilDone:NO];
439         //[windowController showTabBar:self];
440     } else if (HideTabBarMsgID == msgid) {
441         //NSLog(@"Hiding tab bar");
442         [windowController hideTabBar:self];
443     } else if (SetTextDimensionsMsgID == msgid) {
444         const void *bytes = [data bytes];
445         int rows = *((int*)bytes);  bytes += sizeof(int);
446         int cols = *((int*)bytes);  bytes += sizeof(int);
448         [windowController setTextDimensionsWithRows:rows columns:cols];
449     } else if (SetVimWindowTitleMsgID == msgid) {
450         const void *bytes = [data bytes];
451         int len = *((int*)bytes);  bytes += sizeof(int);
453 #if 0
454         // BUG!  Using this call leads to ALL windows getting the same title
455         // and then the app crashes if you :q a window.
456         NSString *string = [[NSString alloc]
457                 initWithBytesNoCopy:(void*)bytes
458                              length:len
459                            encoding:NSUTF8StringEncoding
460                        freeWhenDone:NO];
461 #else
462         NSString *string = [[NSString alloc] initWithBytes:(void*)bytes
463                 length:len encoding:NSUTF8StringEncoding];
464 #endif
466         [[windowController window] setTitle:string];
468         [string release];
469     } else if (BrowseForFileMsgID == msgid) {
470         const void *bytes = [data bytes];
471         int save = *((int*)bytes);  bytes += sizeof(int);
473         int len = *((int*)bytes);  bytes += sizeof(int);
474         NSString *dir = nil;
475         if (len > 0) {
476             dir = [[NSString alloc] initWithBytes:(void*)bytes
477                                            length:len
478                                          encoding:NSUTF8StringEncoding];
479             bytes += len;
480         }
482         len = *((int*)bytes);  bytes += sizeof(int);
483         if (len > 0) {
484             NSString *title = [[NSString alloc]
485                     initWithBytes:(void*)bytes length:len
486                          encoding:NSUTF8StringEncoding];
487             bytes += len;
489             [windowController setStatusText:title];
490             [title release];
491         }
493         if (save) {
494             [[NSSavePanel savePanel] beginSheetForDirectory:dir file:nil
495                 modalForWindow:[windowController window]
496                  modalDelegate:self
497                 didEndSelector:@selector(panelDidEnd:code:context:)
498                    contextInfo:NULL];
499         } else {
500             NSOpenPanel *panel = [NSOpenPanel openPanel];
501             [panel setAllowsMultipleSelection:NO];
502             [panel beginSheetForDirectory:dir file:nil types:nil
503                     modalForWindow:[windowController window]
504                      modalDelegate:self
505                     didEndSelector:@selector(panelDidEnd:code:context:)
506                        contextInfo:NULL];
507         }
509         [dir release];
510     } else if (UpdateInsertionPointMsgID == msgid) {
511         const void *bytes = [data bytes];
512         int color = *((int*)bytes);  bytes += sizeof(int);
513         int row = *((int*)bytes);  bytes += sizeof(int);
514         int col = *((int*)bytes);  bytes += sizeof(int);
515         int state = *((int*)bytes);  bytes += sizeof(int);
517         // TODO! Move to window controller.
518         MMTextView *textView = [windowController textView];
519         if (textView) {
520             MMTextStorage *textStorage = (MMTextStorage*)[textView textStorage];
521             unsigned off = [textStorage offsetFromRow:row column:col];
523             [textView setInsertionPointColor:[NSColor colorWithRgbInt:color]];
524             [textView setSelectedRange:NSMakeRange(off, 0)];
525             [textView setShouldDrawInsertionPoint:state];
526         }
527     } else if (AddMenuMsgID == msgid) {
528         NSString *title = nil;
529         const void *bytes = [data bytes];
530         int tag = *((int*)bytes);  bytes += sizeof(int);
531         int parentTag = *((int*)bytes);  bytes += sizeof(int);
532         int len = *((int*)bytes);  bytes += sizeof(int);
533         if (len > 0) {
534             title = [[NSString alloc] initWithBytes:(void*)bytes length:len
535                                            encoding:NSUTF8StringEncoding];
536             bytes += len;
537         }
538         int idx = *((int*)bytes);  bytes += sizeof(int);
540         if (MenuToolbarType == parentTag) {
541             if (!toolbar) {
542                 NSString *ident = [NSString stringWithFormat:@"%d.%d",
543                          (int)self, tag];
544                 //NSLog(@"Creating toolbar with identifier %@", ident);
545                 toolbar = [[NSToolbar alloc] initWithIdentifier:ident];
547                 [toolbar setShowsBaselineSeparator:NO];
548                 [toolbar setDelegate:self];
549                 [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
550                 [toolbar setSizeMode:NSToolbarSizeModeSmall];
552                 [[windowController window] setToolbar:toolbar];
553             }
554         } else if (MenuPopupType == parentTag) {
555             // TODO!
556         } else if (title) {
557             NSMenu *parent = [self menuForTag:parentTag];
558             [self addMenuWithTag:tag parent:parent title:title atIndex:idx];
559         }
561         [title release];
562     } else if (AddMenuItemMsgID == msgid) {
563         NSString *title = nil, *tip = nil, *icon = nil, *action = nil;
564         const void *bytes = [data bytes];
565         int tag = *((int*)bytes);  bytes += sizeof(int);
566         int parentTag = *((int*)bytes);  bytes += sizeof(int);
567         int namelen = *((int*)bytes);  bytes += sizeof(int);
568         if (namelen > 0) {
569             title = [[NSString alloc] initWithBytes:(void*)bytes length:namelen
570                                            encoding:NSUTF8StringEncoding];
571             bytes += namelen;
572         }
573         int tiplen = *((int*)bytes);  bytes += sizeof(int);
574         if (tiplen > 0) {
575             tip = [[NSString alloc] initWithBytes:(void*)bytes length:tiplen
576                                            encoding:NSUTF8StringEncoding];
577             bytes += tiplen;
578         }
579         int iconlen = *((int*)bytes);  bytes += sizeof(int);
580         if (iconlen > 0) {
581             icon = [[NSString alloc] initWithBytes:(void*)bytes length:iconlen
582                                            encoding:NSUTF8StringEncoding];
583             bytes += iconlen;
584         }
585         int actionlen = *((int*)bytes);  bytes += sizeof(int);
586         if (actionlen > 0) {
587             action = [[NSString alloc] initWithBytes:(void*)bytes
588                                               length:actionlen
589                                             encoding:NSUTF8StringEncoding];
590             bytes += actionlen;
591         }
592         int idx = *((int*)bytes);  bytes += sizeof(int);
593         if (idx < 0) idx = 0;
594         int key = *((int*)bytes);  bytes += sizeof(int);
595         int mask = *((int*)bytes);  bytes += sizeof(int);
597         NSString *ident = [NSString stringWithFormat:@"%d.%d",
598                 (int)self, parentTag];
599         if (toolbar && [[toolbar identifier] isEqual:ident]) {
600             [self addToolbarItemWithTag:tag label:title tip:tip icon:icon
601                                 atIndex:idx];
602         } else {
603             NSMenu *parent = [self menuForTag:parentTag];
604             [self addMenuItemWithTag:tag parent:parent title:title tip:tip
605                        keyEquivalent:key modifiers:mask action:action
606                              atIndex:idx];
607         }
609         [title release];
610         [tip release];
611         [icon release];
612         [action release];
613     } else if (RemoveMenuItemMsgID == msgid) {
614         const void *bytes = [data bytes];
615         int tag = *((int*)bytes);  bytes += sizeof(int);
617         // TODO: Search for tag in popup menus.
618         id item;
619         int idx;
620         if ((item = [self toolbarItemForTag:tag index:&idx])) {
621             [toolbar removeItemAtIndex:idx];
622         } else if ((item = [self menuItemForTag:tag])) {
623             if ([item menu] == [NSApp mainMenu]) {
624                 NSLog(@"Removing menu: %@", item);
625                 [mainMenuItems removeObject:item];
626             }
627             [[item menu] removeItem:item];
628         }
629     } else if (EnableMenuItemMsgID == msgid) {
630         const void *bytes = [data bytes];
631         int tag = *((int*)bytes);  bytes += sizeof(int);
632         int state = *((int*)bytes);  bytes += sizeof(int);
634         // TODO: Search for tag in popup menus.
635         id item = [self toolbarItemForTag:tag index:NULL];
636         if (!item)
637             item = [self menuItemForTag:tag];
639         [item setEnabled:state];
640     } else if (ShowToolbarMsgID == msgid) {
641         const void *bytes = [data bytes];
642         int enable = *((int*)bytes);  bytes += sizeof(int);
643         int flags = *((int*)bytes);  bytes += sizeof(int);
645         int mode = NSToolbarDisplayModeDefault;
646         if (flags & ToolbarLabelFlag) {
647             mode = flags & ToolbarIconFlag ? NSToolbarDisplayModeIconAndLabel
648                     : NSToolbarDisplayModeLabelOnly;
649         } else if (flags & ToolbarIconFlag) {
650             mode = NSToolbarDisplayModeIconOnly;
651         }
653         int size = flags & ToolbarSizeRegularFlag ? NSToolbarSizeModeRegular
654                 : NSToolbarSizeModeSmall;
656         [toolbar setSizeMode:size];
657         [toolbar setDisplayMode:mode];
658         [toolbar setVisible:enable];
659     } else if (CreateScrollbarMsgID == msgid) {
660         const void *bytes = [data bytes];
661         long ident = *((long*)bytes);  bytes += sizeof(long);
662         int type = *((int*)bytes);  bytes += sizeof(int);
664         [windowController createScrollbarWithIdentifier:ident type:type];
665     } else if (DestroyScrollbarMsgID == msgid) {
666         const void *bytes = [data bytes];
667         long ident = *((long*)bytes);  bytes += sizeof(long);
669         [windowController destroyScrollbarWithIdentifier:ident];
670     } else if (ShowScrollbarMsgID == msgid) {
671         const void *bytes = [data bytes];
672         long ident = *((long*)bytes);  bytes += sizeof(long);
673         int visible = *((int*)bytes);  bytes += sizeof(int);
675         [windowController showScrollbarWithIdentifier:ident state:visible];
676     } else if (SetScrollbarPositionMsgID == msgid) {
677         const void *bytes = [data bytes];
678         long ident = *((long*)bytes);  bytes += sizeof(long);
679         int pos = *((int*)bytes);  bytes += sizeof(int);
680         int len = *((int*)bytes);  bytes += sizeof(int);
682         [windowController setScrollbarPosition:pos length:len
683                                     identifier:ident];
684     } else if (SetScrollbarThumbMsgID == msgid) {
685         const void *bytes = [data bytes];
686         long ident = *((long*)bytes);  bytes += sizeof(long);
687         float val = *((float*)bytes);  bytes += sizeof(float);
688         float prop = *((float*)bytes);  bytes += sizeof(float);
690         [windowController setScrollbarThumbValue:val proportion:prop
691                                       identifier:ident];
692     } else if (SetFontMsgID == msgid) {
693         const void *bytes = [data bytes];
694         float size = *((float*)bytes);  bytes += sizeof(float);
695         int len = *((int*)bytes);  bytes += sizeof(int);
696         NSString *name = [[NSString alloc]
697                 initWithBytes:(void*)bytes length:len
698                      encoding:NSUTF8StringEncoding];
699         NSFont *font = [NSFont fontWithName:name size:size];
701         if (font)
702             [windowController setFont:font];
704         [name release];
705     } else if (SetDefaultColorsMsgID == msgid) {
706         const void *bytes = [data bytes];
707         int bg = *((int*)bytes);  bytes += sizeof(int);
708         int fg = *((int*)bytes);  bytes += sizeof(int);
709         NSColor *back = [NSColor colorWithRgbInt:bg];
710         NSColor *fore = [NSColor colorWithRgbInt:fg];
712         [windowController setDefaultColorsBackground:back foreground:fore];
713     } else if (ExecuteActionMsgID == msgid) {
714         const void *bytes = [data bytes];
715         int len = *((int*)bytes);  bytes += sizeof(int);
716         NSString *actionName = [[NSString alloc]
717                 initWithBytesNoCopy:(void*)bytes
718                              length:len
719                            encoding:NSUTF8StringEncoding
720                        freeWhenDone:NO];
722         SEL sel = NSSelectorFromString(actionName);
723         [NSApp sendAction:sel to:nil from:self];
725         [actionName release];
726     } else {
727         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
728     }
731 - (void)performBatchDrawWithData:(NSData *)data
733     // TODO!  Move to window controller.
734     MMTextStorage *textStorage = [windowController textStorage];
735     if (!textStorage)
736         return;
738     const void *bytes = [data bytes];
739     const void *end = bytes + [data length];
741     [textStorage beginEditing];
743     // TODO:
744     // 1. Sanity check input
745     // 2. Cache rgb -> NSColor lookups?
747     while (bytes < end) {
748         int type = *((int*)bytes);  bytes += sizeof(int);
750         if (ClearAllDrawType == type) {
751             int color = *((int*)bytes);  bytes += sizeof(int);
753             [textStorage clearAllWithColor:[NSColor colorWithRgbInt:color]];
754         } else if (ClearBlockDrawType == type) {
755             int color = *((int*)bytes);  bytes += sizeof(int);
756             int row1 = *((int*)bytes);  bytes += sizeof(int);
757             int col1 = *((int*)bytes);  bytes += sizeof(int);
758             int row2 = *((int*)bytes);  bytes += sizeof(int);
759             int col2 = *((int*)bytes);  bytes += sizeof(int);
761             [textStorage clearBlockFromRow:row1 column:col1
762                     toRow:row2 column:col2
763                     color:[NSColor colorWithRgbInt:color]];
764         } else if (DeleteLinesDrawType == type) {
765             int color = *((int*)bytes);  bytes += sizeof(int);
766             int row = *((int*)bytes);  bytes += sizeof(int);
767             int count = *((int*)bytes);  bytes += sizeof(int);
768             int bot = *((int*)bytes);  bytes += sizeof(int);
769             int left = *((int*)bytes);  bytes += sizeof(int);
770             int right = *((int*)bytes);  bytes += sizeof(int);
772             [textStorage deleteLinesFromRow:row lineCount:count
773                     scrollBottom:bot left:left right:right
774                            color:[NSColor colorWithRgbInt:color]];
775         } else if (ReplaceStringDrawType == type) {
776             int bg = *((int*)bytes);  bytes += sizeof(int);
777             int fg = *((int*)bytes);  bytes += sizeof(int);
778             int row = *((int*)bytes);  bytes += sizeof(int);
779             int col = *((int*)bytes);  bytes += sizeof(int);
780             int flags = *((int*)bytes);  bytes += sizeof(int);
781             int len = *((int*)bytes);  bytes += sizeof(int);
782             NSString *string = [[NSString alloc]
783                     initWithBytesNoCopy:(void*)bytes
784                                  length:len
785                                encoding:NSUTF8StringEncoding
786                            freeWhenDone:NO];
787             bytes += len;
789             [textStorage replaceString:string
790                                  atRow:row column:col
791                              withFlags:flags
792                        foregroundColor:[NSColor colorWithRgbInt:fg]
793                        backgroundColor:[NSColor colorWithRgbInt:bg]];
795             [string release];
796         } else if (InsertLinesDrawType == type) {
797             int color = *((int*)bytes);  bytes += sizeof(int);
798             int row = *((int*)bytes);  bytes += sizeof(int);
799             int count = *((int*)bytes);  bytes += sizeof(int);
800             int bot = *((int*)bytes);  bytes += sizeof(int);
801             int left = *((int*)bytes);  bytes += sizeof(int);
802             int right = *((int*)bytes);  bytes += sizeof(int);
804             [textStorage insertLinesAtRow:row lineCount:count
805                              scrollBottom:bot left:left right:right
806                                     color:[NSColor colorWithRgbInt:color]];
807         } else {
808             NSLog(@"WARNING: Unknown draw type (type=%d)", type);
809         }
810     }
812     [textStorage endEditing];
815 - (void)panelDidEnd:(NSSavePanel *)panel code:(int)code context:(void *)context
817 #if MM_USE_DO
818     [windowController setStatusText:@""];
820     NSString *string = (code == NSOKButton) ? [panel filename] : nil;
821     [backendProxy setBrowseForFileString:string];
822 #else
823     NSMutableData *data = [NSMutableData data];
824     int ok = (code == NSOKButton);
825     NSString *filename = [panel filename];
826     int len = [filename lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
828     [data appendBytes:&ok length:sizeof(int)];
829     [data appendBytes:&len length:sizeof(int)];
830     if (len > 0)
831         [data appendBytes:[filename UTF8String] length:len];
833     if (![NSPortMessage sendMessage:BrowseForFileReplyMsgID
834                        withSendPort:sendPort data:data wait:YES]) {
835         NSLog(@"WARNING: Failed to send browse for files reply back to "
836                 "VimTask.");
837     }
839     [windowController setStatusText:@""];
840 #endif // !MM_USE_DO
843 - (NSMenuItem *)menuItemForTag:(int)tag
845     int i, count = [mainMenuItems count];
846     for (i = 0; i < count; ++i) {
847         NSMenuItem *item = [mainMenuItems objectAtIndex:i];
848         if ([item tag] == tag) return item;
849         item = findMenuItemWithTagInMenu([item submenu], tag);
850         if (item) return item;
851     }
853     return nil;
856 - (NSMenu *)menuForTag:(int)tag
858     return [[self menuItemForTag:tag] submenu];
861 - (void)addMenuWithTag:(int)tag parent:(NSMenu *)parent title:(NSString *)title
862                atIndex:(int)idx
864     NSMenuItem *item = [[NSMenuItem alloc] init];
865     NSMenu *menu = [[NSMenu alloc] initWithTitle:title];
867     [menu setAutoenablesItems:NO];
868     [item setTag:tag];
869     [item setTitle:title];
870     [item setSubmenu:menu];
872     if (parent) {
873         if ([parent numberOfItems] <= idx) {
874             [parent addItem:item];
875         } else {
876             [parent insertItem:item atIndex:idx];
877         }
878     } else {
879         if ([mainMenuItems count] <= idx) {
880             [mainMenuItems addObject:item];
881         } else {
882             [mainMenuItems insertObject:item atIndex:idx];
883         }
885         shouldUpdateMainMenu = YES;
886     }
888     [item release];
889     [menu release];
892 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
893                      title:(NSString *)title tip:(NSString *)tip
894              keyEquivalent:(int)key modifiers:(int)mask
895                     action:(NSString *)action atIndex:(int)idx
897     if (parent) {
898         NSMenuItem *item = nil;
899         if (title) {
900             item = [[[NSMenuItem alloc] init] autorelease];
901             [item setTitle:title];
902             // TODO: Check that 'action' is a valid action (nothing will happen
903             // if it isn't, but it would be nice with a warning).
904             if (action) [item setAction:NSSelectorFromString(action)];
905             else        [item setAction:@selector(vimMenuItemAction:)];
906             if (tip) [item setToolTip:tip];
908             if (key != 0) {
909                 NSString *keyString =
910                     [NSString stringWithFormat:@"%C", key];
911                 [item setKeyEquivalent:keyString];
912                 [item setKeyEquivalentModifierMask:mask];
913             }
914         } else {
915             item = [NSMenuItem separatorItem];
916         }
918         // NOTE!  The tag is used to idenfity which menu items were
919         // added by Vim (tag != 0) and which were added by the AppKit
920         // (tag == 0).
921         [item setTag:tag];
923         if ([parent numberOfItems] <= idx) {
924             [parent addItem:item];
925         } else {
926             [parent insertItem:item atIndex:idx];
927         }
928     }
931 - (void)updateMainMenu
933     NSMenu *mainMenu = [NSApp mainMenu];
935     // Stop NSApp from updating the Window menu.
936     [NSApp setWindowsMenu:nil];
938     // Remove all menus from main menu (except the MacVim menu).
939     int i, count = [mainMenu numberOfItems];
940     for (i = count-1; i > 0; --i) {
941         [mainMenu removeItemAtIndex:i];
942     }
944     // Add menus from 'mainMenuItems' to main menu.
945     count = [mainMenuItems count];
946     for (i = 0; i < count; ++i) {
947         [mainMenu addItem:[mainMenuItems objectAtIndex:i]];
948     }
950     // Set the new Window menu.
951     // TODO!  Need to look for 'Window' in all localized languages.
952     NSMenu *windowMenu = [[mainMenu itemWithTitle:@"Window"] submenu];
953     if (windowMenu) {
954         // Remove all AppKit owned menu items (tag == 0); they will be added
955         // again when setWindowsMenu: is called.
956         count = [windowMenu numberOfItems];
957         for (i = count-1; i >= 0; --i) {
958             NSMenuItem *item = [windowMenu itemAtIndex:i];
959             if (![item tag]) {
960                 [windowMenu removeItem:item];
961             }
962         }
964         [NSApp setWindowsMenu:windowMenu];
965     }
967     shouldUpdateMainMenu = NO;
970 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index
972     if (!toolbar) return nil;
974     NSArray *items = [toolbar items];
975     int i, count = [items count];
976     for (i = 0; i < count; ++i) {
977         NSToolbarItem *item = [items objectAtIndex:i];
978         if ([item tag] == tag) {
979             if (index) *index = i;
980             return item;
981         }
982     }
984     return nil;
987 - (IBAction)toolbarAction:(id)sender
989     NSLog(@"%s%@", _cmd, sender);
992 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
993         toolTip:(NSString *)tip icon:(NSString *)icon
995     // NOTE!  'title' is nul for separator item.  Since this is already defined
996     // by Coca, we don't need to do anything here.
997     if (!title) return;
999     NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:title];
1000     [item setTag:tag];
1001     [item setLabel:title];
1002     [item setToolTip:tip];
1003     [item setAction:@selector(vimMenuItemAction:)];
1004     [item setAutovalidates:NO];
1006     NSImage *img = [NSImage imageNamed:icon];
1007     if (!img) {
1008         NSLog(@"WARNING: Could not find image with name '%@' to use as toolbar"
1009                " image for identifier '%@';"
1010                " using default toolbar icon '%@' instead.",
1011                icon, title, DefaultToolbarImageName);
1013         img = [NSImage imageNamed:DefaultToolbarImageName];
1014     }
1016     [item setImage:img];
1018     [toolbarItemDict setObject:item forKey:title];
1020     [item release];
1023 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label tip:(NSString
1024                    *)tip icon:(NSString *)icon atIndex:(int)idx
1026     if (!toolbar) return;
1028     [self addToolbarItemToDictionaryWithTag:tag label:label toolTip:tip
1029                                        icon:icon];
1031     int maxIdx = [[toolbar items] count];
1032     if (maxIdx < idx) idx = maxIdx;
1034     // If 'label' is nul, insert a separator.
1035     if (!label) label = NSToolbarSeparatorItemIdentifier;
1036     [toolbar insertItemWithItemIdentifier:label atIndex:idx];
1039 #if MM_USE_DO
1040 - (void)connectionDidDie:(NSNotification *)notification
1042     //NSLog(@"A MMVimController lost its connection to the backend; "
1043     //       "closing the controller.");
1044     [windowController close];
1046 #endif // MM_USE_DO
1048 - (BOOL)executeActionWithName:(NSString *)name
1050 #if 0
1051     static NSDictionary *actionDict = nil;
1053     if (!actionDict) {
1054         NSBundle *mainBundle = [NSBundle mainBundle];
1055         NSString *path = [mainBundle pathForResource:@"Actions"
1056                                               ofType:@"plist"];
1057         if (path) {
1058             actionDict = [[NSDictionary alloc] initWithContentsOfFile:path];
1059             NSLog(@"Actions = %@", actionDict);
1060         } else {
1061             NSLog(@"WARNING: Failed to load dictionary of actions "
1062                     "(Actions.plist).");
1063             return NO;
1064         }
1065     }
1067     if ([actionDict objectForKey:name]) {
1068         NSLog(@"Executing action %@", name);
1069         SEL sel = NSSelectorFromString(name);
1071         if ([NSApp sendAction:sel to:nil from:self])
1072             return YES;
1074         NSLog(@"WARNING: Failed to send action");
1075     } else {
1076         NSLog(@"WARNING: Action with name '%@' cannot be executed.", name);
1077     }
1079 #endif
1080     return NO;
1083 @end // MMVimController (Private)
1087 @implementation NSColor (MMProtocol)
1089 + (NSColor *)colorWithRgbInt:(int)rgb
1091     float r = ((rgb>>16) & 0xff)/255.0f;
1092     float g = ((rgb>>8) & 0xff)/255.0f;
1093     float b = (rgb & 0xff)/255.0f;
1095     return [NSColor colorWithCalibratedRed:r green:g blue:b alpha:1.0f];
1098 @end // NSColor (MMProtocol)