Escape more chars (' ', '\t', '\\', '%', '#', '|' '"') in "drop files"
[MacVim/jjgod.git] / MMVimController.m
blob91ffdaf22e63156eafd3c33b9a2caa6269dde2f8
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 // This is taken from gui.h
19 #define DRAW_CURSOR 0x20
21 static NSString *MMDefaultToolbarImageName = @"Attention";
22 static int MMAlertTextFieldHeight = 22;
24 // NOTE: By default a message sent to the backend will be dropped if it cannot
25 // be delivered instantly; otherwise there is a possibility that MacVim will
26 // 'beachball' while waiting to deliver DO messages to an unresponsive Vim
27 // process.  This means that you cannot rely on any message sent with
28 // sendMessage: to actually reach Vim.
29 static NSTimeInterval MMBackendProxyRequestTimeout = 0;
31 #if MM_RESEND_LAST_FAILURE
32 // If a message send fails, the message will be resent after this many seconds
33 // have passed.  (No queue is kept, only the very last message is resent.)
34 static NSTimeInterval MMResendInterval = 0.5;
35 #endif
38 @interface MMAlert : NSAlert {
39     NSTextField *textField;
41 - (void)setTextFieldString:(NSString *)textFieldString;
42 - (NSTextField *)textField;
43 @end
46 @interface MMVimController (Private)
47 - (void)handleMessage:(int)msgid data:(NSData *)data;
48 - (void)performBatchDrawWithData:(NSData *)data;
49 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
50                 context:(void *)context;
51 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context;
52 - (NSMenuItem *)menuItemForTag:(int)tag;
53 - (NSMenu *)menuForTag:(int)tag;
54 - (NSMenu *)topLevelMenuForTitle:(NSString *)title;
55 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
56                atIndex:(int)idx;
57 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
58                      title:(NSString *)title tip:(NSString *)tip
59              keyEquivalent:(int)key modifiers:(int)mask
60                     action:(NSString *)action atIndex:(int)idx;
61 - (void)updateMainMenu;
62 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index;
63 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
64         toolTip:(NSString *)tip icon:(NSString *)icon;
65 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label
66                           tip:(NSString *)tip icon:(NSString *)icon
67                       atIndex:(int)idx;
68 - (void)connectionDidDie:(NSNotification *)notification;
69 #if MM_RESEND_LAST_FAILURE
70 - (void)resendTimerFired:(NSTimer *)timer;
71 #endif
72 @end
76 // TODO: Move to separate file
77 @interface NSColor (MMProtocol)
78 + (NSColor *)colorWithRgbInt:(unsigned)rgb;
79 + (NSColor *)colorWithArgbInt:(unsigned)argb;
80 @end
84 static NSMenuItem *findMenuItemWithTagInMenu(NSMenu *root, int tag)
86     if (root) {
87         NSMenuItem *item = [root itemWithTag:tag];
88         if (item) return item;
90         NSArray *items = [root itemArray];
91         unsigned i, count = [items count];
92         for (i = 0; i < count; ++i) {
93             item = [items objectAtIndex:i];
94             if ([item hasSubmenu]) {
95                 item = findMenuItemWithTagInMenu([item submenu], tag);
96                 if (item) return item;
97             }
98         }
99     }
101     return nil;
106 @implementation MMVimController
108 - (id)initWithBackend:(id)backend pid:(int)processIdentifier
110     if ((self = [super init])) {
111         windowController =
112             [[MMWindowController alloc] initWithVimController:self];
113         backendProxy = [backend retain];
114         sendQueue = [NSMutableArray new];
115         mainMenuItems = [[NSMutableArray alloc] init];
116         popupMenuItems = [[NSMutableArray alloc] init];
117         toolbarItemDict = [[NSMutableDictionary alloc] init];
118         pid = processIdentifier;
120         NSConnection *connection = [backendProxy connectionForProxy];
122         // TODO: Check that this will not set the timeout for the root proxy
123         // (in MMAppController).
124         [connection setRequestTimeout:MMBackendProxyRequestTimeout];
126         [[NSNotificationCenter defaultCenter] addObserver:self
127                 selector:@selector(connectionDidDie:)
128                     name:NSConnectionDidDieNotification object:connection];
131         NSWindow *win = [windowController window];
133         [[NSNotificationCenter defaultCenter]
134                 addObserver:self
135                    selector:@selector(windowDidBecomeMain:)
136                        name:NSWindowDidBecomeMainNotification
137                      object:win];
139         isInitialized = YES;
140     }
142     return self;
145 - (void)dealloc
147     //NSLog(@"%@ %s", [self className], _cmd);
148     isInitialized = NO;
150 #if MM_RESEND_LAST_FAILURE
151     [resendData release];  resendData = nil;
152 #endif
154     [serverName release];  serverName = nil;
155     [backendProxy release];  backendProxy = nil;
156     [sendQueue release];  sendQueue = nil;
158     [toolbarItemDict release];  toolbarItemDict = nil;
159     [toolbar release];  toolbar = nil;
160     [popupMenuItems release];  popupMenuItems = nil;
161     [mainMenuItems release];  mainMenuItems = nil;
162     [windowController release];  windowController = nil;
164     [super dealloc];
167 - (MMWindowController *)windowController
169     return windowController;
172 - (void)setServerName:(NSString *)name
174     if (name != serverName) {
175         [serverName release];
176         serverName = [name copy];
177     }
180 - (NSString *)serverName
182     return serverName;
185 - (int)pid
187     return pid;
190 - (void)dropFiles:(NSArray *)filenames
192     int i, numberOfFiles = [filenames count];
193     NSMutableData *data = [NSMutableData data];
195     [data appendBytes:&numberOfFiles length:sizeof(int)];
197     for (i = 0; i < numberOfFiles; ++i) {
198         NSString *file = [filenames objectAtIndex:i];
199         int len = [file lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
201         if (len > 0) {
202             ++len;  // include NUL as well
203             [data appendBytes:&len length:sizeof(int)];
204             [data appendBytes:[file UTF8String] length:len];
205         }
206     }
208     [self sendMessage:DropFilesMsgID data:data];
211 - (void)dropString:(NSString *)string
213     int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
214     if (len > 0) {
215         NSMutableData *data = [NSMutableData data];
217         [data appendBytes:&len length:sizeof(int)];
218         [data appendBytes:[string UTF8String] length:len];
220         [self sendMessage:DropStringMsgID data:data];
221     }
224 - (void)sendMessage:(int)msgid data:(NSData *)data
226     //NSLog(@"sendMessage:%s (isInitialized=%d inProcessCommandQueue=%d)",
227     //        MessageStrings[msgid], isInitialized, inProcessCommandQueue);
229     if (!isInitialized) return;
231     if (inProcessCommandQueue) {
232         //NSLog(@"In process command queue; delaying message send.");
233         [sendQueue addObject:[NSNumber numberWithInt:msgid]];
234         if (data)
235             [sendQueue addObject:data];
236         else
237             [sendQueue addObject:[NSNull null]];
238         return;
239     }
241 #if MM_RESEND_LAST_FAILURE
242     if (resendTimer) {
243         //NSLog(@"cancelling scheduled resend of %s",
244         //        MessageStrings[resendMsgid]);
246         [resendTimer invalidate];
247         [resendTimer release];
248         resendTimer = nil;
249     }
251     if (resendData) {
252         [resendData release];
253         resendData = nil;
254     }
255 #endif
257     @try {
258         [backendProxy processInput:msgid data:data];
259     }
260     @catch (NSException *e) {
261         //NSLog(@"%@ %s Exception caught during DO call: %@",
262         //        [self className], _cmd, e);
263 #if MM_RESEND_LAST_FAILURE
264         //NSLog(@"%s failed, scheduling message %s for resend", _cmd,
265         //        MessageStrings[msgid]);
267         resendMsgid = msgid;
268         resendData = [data retain];
269         resendTimer = [NSTimer
270             scheduledTimerWithTimeInterval:MMResendInterval
271                                     target:self
272                                   selector:@selector(resendTimerFired:)
273                                   userInfo:nil
274                                    repeats:NO];
275         [resendTimer retain];
276 #endif
277     }
280 - (BOOL)sendMessageNow:(int)msgid data:(NSData *)data
281                timeout:(NSTimeInterval)timeout
283     if (!isInitialized || inProcessCommandQueue)
284         return NO;
286     if (timeout < 0) timeout = 0;
288     BOOL sendOk = YES;
289     NSConnection *conn = [backendProxy connectionForProxy];
290     NSTimeInterval oldTimeout = [conn requestTimeout];
292     [conn setRequestTimeout:timeout];
294     @try {
295         [backendProxy processInput:msgid data:data];
296     }
297     @catch (NSException *e) {
298         sendOk = NO;
299     }
300     @finally {
301         [conn setRequestTimeout:oldTimeout];
302     }
304     return sendOk;
307 - (id)backendProxy
309     return backendProxy;
312 - (void)cleanup
314     //NSLog(@"%@ %s", [self className], _cmd);
315     if (!isInitialized) return;
317     isInitialized = NO;
318     [toolbar setDelegate:nil];
319     [[NSNotificationCenter defaultCenter] removeObserver:self];
320     [windowController cleanup];
323 - (oneway void)showSavePanelForDirectory:(in bycopy NSString *)dir
324                                    title:(in bycopy NSString *)title
325                                   saving:(int)saving
327     if (!isInitialized) return;
329     if (saving) {
330         [[NSSavePanel savePanel] beginSheetForDirectory:dir file:nil
331                 modalForWindow:[windowController window]
332                  modalDelegate:self
333                 didEndSelector:@selector(savePanelDidEnd:code:context:)
334                    contextInfo:NULL];
335     } else {
336         NSOpenPanel *panel = [NSOpenPanel openPanel];
337         [panel setAllowsMultipleSelection:NO];
338         [panel beginSheetForDirectory:dir file:nil types:nil
339                 modalForWindow:[windowController window]
340                  modalDelegate:self
341                 didEndSelector:@selector(savePanelDidEnd:code:context:)
342                    contextInfo:NULL];
343     }
346 - (oneway void)presentDialogWithStyle:(int)style
347                               message:(in bycopy NSString *)message
348                       informativeText:(in bycopy NSString *)text
349                          buttonTitles:(in bycopy NSArray *)buttonTitles
350                       textFieldString:(in bycopy NSString *)textFieldString
352     if (!(windowController && buttonTitles && [buttonTitles count])) return;
354     MMAlert *alert = [[MMAlert alloc] init];
356     // NOTE! This has to be done before setting the informative text.
357     if (textFieldString)
358         [alert setTextFieldString:textFieldString];
360     [alert setAlertStyle:style];
362     if (message) {
363         [alert setMessageText:message];
364     } else {
365         // If no message text is specified 'Alert' is used, which we don't
366         // want, so set an empty string as message text.
367         [alert setMessageText:@""];
368     }
370     if (text) {
371         [alert setInformativeText:text];
372     } else if (textFieldString) {
373         // Make sure there is always room for the input text field.
374         [alert setInformativeText:@""];
375     }
377     unsigned i, count = [buttonTitles count];
378     for (i = 0; i < count; ++i) {
379         NSString *title = [buttonTitles objectAtIndex:i];
380         // NOTE: The title of the button may contain the character '&' to
381         // indicate that the following letter should be the key equivalent
382         // associated with the button.  Extract this letter and lowercase it.
383         NSString *keyEquivalent = nil;
384         NSRange hotkeyRange = [title rangeOfString:@"&"];
385         if (NSNotFound != hotkeyRange.location) {
386             if ([title length] > NSMaxRange(hotkeyRange)) {
387                 NSRange keyEquivRange = NSMakeRange(hotkeyRange.location+1, 1);
388                 keyEquivalent = [[title substringWithRange:keyEquivRange]
389                     lowercaseString];
390             }
392             NSMutableString *string = [NSMutableString stringWithString:title];
393             [string deleteCharactersInRange:hotkeyRange];
394             title = string;
395         }
397         [alert addButtonWithTitle:title];
399         // Set key equivalent for the button, but only if NSAlert hasn't
400         // already done so.  (Check the documentation for
401         // - [NSAlert addButtonWithTitle:] to see what key equivalents are
402         // automatically assigned.)
403         NSButton *btn = [[alert buttons] lastObject];
404         if ([[btn keyEquivalent] length] == 0 && keyEquivalent) {
405             [btn setKeyEquivalent:keyEquivalent];
406         }
407     }
409     [alert beginSheetModalForWindow:[windowController window]
410                       modalDelegate:self
411                      didEndSelector:@selector(alertDidEnd:code:context:)
412                         contextInfo:NULL];
414     [alert release];
417 - (oneway void)processCommandQueue:(in bycopy NSArray *)queue
419     if (!isInitialized) return;
421     unsigned i, count = [queue count];
422     if (count % 2) {
423         NSLog(@"WARNING: Uneven number of components (%d) in flush queue "
424                 "message; ignoring this message.", count);
425         return;
426     }
428     inProcessCommandQueue = YES;
430     //NSLog(@"======== %s BEGIN ========", _cmd);
431     for (i = 0; i < count; i += 2) {
432         NSData *value = [queue objectAtIndex:i];
433         NSData *data = [queue objectAtIndex:i+1];
435         int msgid = *((int*)[value bytes]);
436 #if 0
437         if (msgid != EnableMenuItemMsgID && msgid != AddMenuItemMsgID
438                 && msgid != AddMenuMsgID) {
439             NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
440         }
441 #endif
443         [self handleMessage:msgid data:data];
444     }
445     //NSLog(@"======== %s  END  ========", _cmd);
447     if (shouldUpdateMainMenu) {
448         [self updateMainMenu];
449     }
451     [windowController processCommandQueueDidFinish];
453     inProcessCommandQueue = NO;
455     if ([sendQueue count] > 0) {
456         @try {
457             [backendProxy processInputAndData:sendQueue];
458         }
459         @catch (NSException *e) {
460             // Connection timed out, just ignore this.
461             //NSLog(@"WARNING! Connection timed out in %s", _cmd);
462         }
464         [sendQueue removeAllObjects];
465     }
468 - (void)windowDidBecomeMain:(NSNotification *)notification
470     if (isInitialized)
471         [self updateMainMenu];
474 - (NSToolbarItem *)toolbar:(NSToolbar *)theToolbar
475     itemForItemIdentifier:(NSString *)itemId
476     willBeInsertedIntoToolbar:(BOOL)flag
478     NSToolbarItem *item = [toolbarItemDict objectForKey:itemId];
479     if (!item) {
480         NSLog(@"WARNING:  No toolbar item with id '%@'", itemId);
481     }
483     return item;
486 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)theToolbar
488     return nil;
491 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)theToolbar
493     return nil;
496 @end // MMVimController
500 @implementation MMVimController (Private)
502 - (void)handleMessage:(int)msgid data:(NSData *)data
504     //NSLog(@"%@ %s", [self className], _cmd);
506     if (OpenVimWindowMsgID == msgid) {
507         [windowController openWindow];
508     } else if (BatchDrawMsgID == msgid) {
509         [self performBatchDrawWithData:data];
510     } else if (SelectTabMsgID == msgid) {
511 #if 0   // NOTE: Tab selection is done inside updateTabsWithData:.
512         const void *bytes = [data bytes];
513         int idx = *((int*)bytes);
514         //NSLog(@"Selecting tab with index %d", idx);
515         [windowController selectTabWithIndex:idx];
516 #endif
517     } else if (UpdateTabBarMsgID == msgid) {
518         [windowController updateTabsWithData:data];
519     } else if (ShowTabBarMsgID == msgid) {
520         [windowController showTabBar:YES];
521     } else if (HideTabBarMsgID == msgid) {
522         [windowController showTabBar:NO];
523     } else if (SetTextDimensionsMsgID == msgid) {
524         const void *bytes = [data bytes];
525         int rows = *((int*)bytes);  bytes += sizeof(int);
526         int cols = *((int*)bytes);  bytes += sizeof(int);
528         [windowController setTextDimensionsWithRows:rows columns:cols];
529     } else if (SetWindowTitleMsgID == msgid) {
530         const void *bytes = [data bytes];
531         int len = *((int*)bytes);  bytes += sizeof(int);
533         NSString *string = [[NSString alloc] initWithBytes:(void*)bytes
534                 length:len encoding:NSUTF8StringEncoding];
536         [[windowController window] setTitle:string];
538         [string release];
539     } else if (AddMenuMsgID == msgid) {
540         NSString *title = nil;
541         const void *bytes = [data bytes];
542         int tag = *((int*)bytes);  bytes += sizeof(int);
543         int parentTag = *((int*)bytes);  bytes += sizeof(int);
544         int len = *((int*)bytes);  bytes += sizeof(int);
545         if (len > 0) {
546             title = [[NSString alloc] initWithBytes:(void*)bytes length:len
547                                            encoding:NSUTF8StringEncoding];
548             bytes += len;
549         }
550         int idx = *((int*)bytes);  bytes += sizeof(int);
552         if (MenuToolbarType == parentTag) {
553             if (!toolbar) {
554                 // NOTE! Each toolbar must have a unique identifier, else each
555                 // window will have the same toolbar.
556                 NSString *ident = [NSString stringWithFormat:@"%d.%d",
557                          (int)self, tag];
558                 toolbar = [[NSToolbar alloc] initWithIdentifier:ident];
560                 [toolbar setShowsBaselineSeparator:NO];
561                 [toolbar setDelegate:self];
562                 [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
563                 [toolbar setSizeMode:NSToolbarSizeModeSmall];
565                 NSWindow *win = [windowController window];
566                 [win setToolbar:toolbar];
568                 // HACK! Redirect the pill button so that we can ask Vim to
569                 // hide the toolbar.
570                 NSButton *pillButton = [win
571                     standardWindowButton:NSWindowToolbarButton];
572                 if (pillButton) {
573                     [pillButton setAction:@selector(toggleToolbar:)];
574                     [pillButton setTarget:windowController];
575                 }
576             }
577         } else if (title) {
578             [self addMenuWithTag:tag parent:parentTag title:title atIndex:idx];
579         }
581         [title release];
582     } else if (AddMenuItemMsgID == msgid) {
583         NSString *title = nil, *tip = nil, *icon = nil, *action = nil;
584         const void *bytes = [data bytes];
585         int tag = *((int*)bytes);  bytes += sizeof(int);
586         int parentTag = *((int*)bytes);  bytes += sizeof(int);
587         int namelen = *((int*)bytes);  bytes += sizeof(int);
588         if (namelen > 0) {
589             title = [[NSString alloc] initWithBytes:(void*)bytes length:namelen
590                                            encoding:NSUTF8StringEncoding];
591             bytes += namelen;
592         }
593         int tiplen = *((int*)bytes);  bytes += sizeof(int);
594         if (tiplen > 0) {
595             tip = [[NSString alloc] initWithBytes:(void*)bytes length:tiplen
596                                            encoding:NSUTF8StringEncoding];
597             bytes += tiplen;
598         }
599         int iconlen = *((int*)bytes);  bytes += sizeof(int);
600         if (iconlen > 0) {
601             icon = [[NSString alloc] initWithBytes:(void*)bytes length:iconlen
602                                            encoding:NSUTF8StringEncoding];
603             bytes += iconlen;
604         }
605         int actionlen = *((int*)bytes);  bytes += sizeof(int);
606         if (actionlen > 0) {
607             action = [[NSString alloc] initWithBytes:(void*)bytes
608                                               length:actionlen
609                                             encoding:NSUTF8StringEncoding];
610             bytes += actionlen;
611         }
612         int idx = *((int*)bytes);  bytes += sizeof(int);
613         if (idx < 0) idx = 0;
614         int key = *((int*)bytes);  bytes += sizeof(int);
615         int mask = *((int*)bytes);  bytes += sizeof(int);
617         NSString *ident = [NSString stringWithFormat:@"%d.%d",
618                 (int)self, parentTag];
619         if (toolbar && [[toolbar identifier] isEqual:ident]) {
620             [self addToolbarItemWithTag:tag label:title tip:tip icon:icon
621                                 atIndex:idx];
622         } else {
623             NSMenu *parent = [self menuForTag:parentTag];
624             [self addMenuItemWithTag:tag parent:parent title:title tip:tip
625                        keyEquivalent:key modifiers:mask action:action
626                              atIndex:idx];
627         }
629         [title release];
630         [tip release];
631         [icon release];
632         [action release];
633     } else if (RemoveMenuItemMsgID == msgid) {
634         const void *bytes = [data bytes];
635         int tag = *((int*)bytes);  bytes += sizeof(int);
637         id item;
638         int idx;
639         if ((item = [self toolbarItemForTag:tag index:&idx])) {
640             [toolbar removeItemAtIndex:idx];
641         } else if ((item = [self menuItemForTag:tag])) {
642             [item retain];
644             if ([item menu] == [NSApp mainMenu] || ![item menu]) {
645                 // NOTE: To be on the safe side we try to remove the item from
646                 // both arrays (it is ok to call removeObject: even if an array
647                 // does not contain the object to remove).
648                 [mainMenuItems removeObject:item];
649                 [popupMenuItems removeObject:item];
650             }
652             if ([item menu])
653                 [[item menu] removeItem:item];
655             [item release];
656         }
657     } else if (EnableMenuItemMsgID == msgid) {
658         const void *bytes = [data bytes];
659         int tag = *((int*)bytes);  bytes += sizeof(int);
660         int state = *((int*)bytes);  bytes += sizeof(int);
662         id item = [self toolbarItemForTag:tag index:NULL];
663         if (!item)
664             item = [self menuItemForTag:tag];
666         [item setEnabled:state];
667     } else if (ShowToolbarMsgID == msgid) {
668         const void *bytes = [data bytes];
669         int enable = *((int*)bytes);  bytes += sizeof(int);
670         int flags = *((int*)bytes);  bytes += sizeof(int);
672         int mode = NSToolbarDisplayModeDefault;
673         if (flags & ToolbarLabelFlag) {
674             mode = flags & ToolbarIconFlag ? NSToolbarDisplayModeIconAndLabel
675                     : NSToolbarDisplayModeLabelOnly;
676         } else if (flags & ToolbarIconFlag) {
677             mode = NSToolbarDisplayModeIconOnly;
678         }
680         int size = flags & ToolbarSizeRegularFlag ? NSToolbarSizeModeRegular
681                 : NSToolbarSizeModeSmall;
683         [windowController showToolbar:enable size:size mode:mode];
684     } else if (CreateScrollbarMsgID == msgid) {
685         const void *bytes = [data bytes];
686         long ident = *((long*)bytes);  bytes += sizeof(long);
687         int type = *((int*)bytes);  bytes += sizeof(int);
689         [windowController createScrollbarWithIdentifier:ident type:type];
690     } else if (DestroyScrollbarMsgID == msgid) {
691         const void *bytes = [data bytes];
692         long ident = *((long*)bytes);  bytes += sizeof(long);
694         [windowController destroyScrollbarWithIdentifier:ident];
695     } else if (ShowScrollbarMsgID == msgid) {
696         const void *bytes = [data bytes];
697         long ident = *((long*)bytes);  bytes += sizeof(long);
698         int visible = *((int*)bytes);  bytes += sizeof(int);
700         [windowController showScrollbarWithIdentifier:ident state:visible];
701     } else if (SetScrollbarPositionMsgID == msgid) {
702         const void *bytes = [data bytes];
703         long ident = *((long*)bytes);  bytes += sizeof(long);
704         int pos = *((int*)bytes);  bytes += sizeof(int);
705         int len = *((int*)bytes);  bytes += sizeof(int);
707         [windowController setScrollbarPosition:pos length:len
708                                     identifier:ident];
709     } else if (SetScrollbarThumbMsgID == msgid) {
710         const void *bytes = [data bytes];
711         long ident = *((long*)bytes);  bytes += sizeof(long);
712         float val = *((float*)bytes);  bytes += sizeof(float);
713         float prop = *((float*)bytes);  bytes += sizeof(float);
715         [windowController setScrollbarThumbValue:val proportion:prop
716                                       identifier:ident];
717     } else if (SetFontMsgID == msgid) {
718         const void *bytes = [data bytes];
719         float size = *((float*)bytes);  bytes += sizeof(float);
720         int len = *((int*)bytes);  bytes += sizeof(int);
721         NSString *name = [[NSString alloc]
722                 initWithBytes:(void*)bytes length:len
723                      encoding:NSUTF8StringEncoding];
724         NSFont *font = [NSFont fontWithName:name size:size];
726         if (font)
727             [windowController setFont:font];
729         [name release];
730     } else if (SetDefaultColorsMsgID == msgid) {
731         const void *bytes = [data bytes];
732         unsigned bg = *((unsigned*)bytes);  bytes += sizeof(unsigned);
733         unsigned fg = *((unsigned*)bytes);  bytes += sizeof(unsigned);
734         NSColor *back = [NSColor colorWithArgbInt:bg];
735         NSColor *fore = [NSColor colorWithRgbInt:fg];
737         [windowController setDefaultColorsBackground:back foreground:fore];
738     } else if (ExecuteActionMsgID == msgid) {
739         const void *bytes = [data bytes];
740         int len = *((int*)bytes);  bytes += sizeof(int);
741         NSString *actionName = [[NSString alloc]
742                 initWithBytesNoCopy:(void*)bytes
743                              length:len
744                            encoding:NSUTF8StringEncoding
745                        freeWhenDone:NO];
747         SEL sel = NSSelectorFromString(actionName);
748         [NSApp sendAction:sel to:nil from:self];
750         [actionName release];
751     } else if (ShowPopupMenuMsgID == msgid) {
752         const void *bytes = [data bytes];
753         int row = *((int*)bytes);  bytes += sizeof(int);
754         int col = *((int*)bytes);  bytes += sizeof(int);
755         int len = *((int*)bytes);  bytes += sizeof(int);
756         NSString *title = [[NSString alloc]
757                 initWithBytesNoCopy:(void*)bytes
758                              length:len
759                            encoding:NSUTF8StringEncoding
760                        freeWhenDone:NO];
762         NSMenu *menu = [self topLevelMenuForTitle:title];
763         if (menu) {
764             [windowController popupMenu:menu atRow:row column:col];
765         } else {
766             NSLog(@"WARNING: Cannot popup menu with title %@; no such menu.",
767                     title);
768         }
770         [title release];
771     } else if (SetMouseShapeMsgID == msgid) {
772         const void *bytes = [data bytes];
773         int shape = *((int*)bytes);  bytes += sizeof(int);
775         [windowController setMouseShape:shape];
776     } else if (AdjustLinespaceMsgID == msgid) {
777         const void *bytes = [data bytes];
778         int linespace = *((int*)bytes);  bytes += sizeof(int);
780         [windowController adjustLinespace:linespace];
781     } else if (ActivateMsgID == msgid) {
782         [NSApp activateIgnoringOtherApps:YES];
783         [[windowController window] makeKeyAndOrderFront:self];
784     } else if (SetServerNameMsgID == msgid) {
785         NSString *name = [[NSString alloc] initWithData:data
786                                                encoding:NSUTF8StringEncoding];
787         [self setServerName:name];
788         [name release];
789     } else if (EnterFullscreenMsgID == msgid) {
790         [windowController enterFullscreen];
791     } else if (LeaveFullscreenMsgID == msgid) {
792         [windowController leaveFullscreen];
793     } else if (BuffersNotModifiedMsgID == msgid) {
794         [[windowController window] setDocumentEdited:NO];
795     } else if (BuffersModifiedMsgID == msgid) {
796         [[windowController window] setDocumentEdited:YES];
797     } else {
798         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
799     }
803 #define MM_DEBUG_DRAWING 0
805 - (void)performBatchDrawWithData:(NSData *)data
807     // TODO!  Move to window controller.
808     MMTextStorage *textStorage = [windowController textStorage];
809     MMTextView *textView = [windowController textView];
810     if (!(textStorage && textView))
811         return;
813     const void *bytes = [data bytes];
814     const void *end = bytes + [data length];
816 #if MM_DEBUG_DRAWING
817     NSLog(@"====> BEGIN %s", _cmd);
818 #endif
819     [textStorage beginEditing];
821     // TODO: Sanity check input
823     while (bytes < end) {
824         int type = *((int*)bytes);  bytes += sizeof(int);
826         if (ClearAllDrawType == type) {
827 #if MM_DEBUG_DRAWING
828             NSLog(@"   Clear all");
829 #endif
830             [textStorage clearAll];
831         } else if (ClearBlockDrawType == type) {
832             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
833             int row1 = *((int*)bytes);  bytes += sizeof(int);
834             int col1 = *((int*)bytes);  bytes += sizeof(int);
835             int row2 = *((int*)bytes);  bytes += sizeof(int);
836             int col2 = *((int*)bytes);  bytes += sizeof(int);
838 #if MM_DEBUG_DRAWING
839             NSLog(@"   Clear block (%d,%d) -> (%d,%d)", row1, col1,
840                     row2,col2);
841 #endif
842             [textStorage clearBlockFromRow:row1 column:col1
843                     toRow:row2 column:col2
844                     color:[NSColor colorWithArgbInt:color]];
845         } else if (DeleteLinesDrawType == type) {
846             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
847             int row = *((int*)bytes);  bytes += sizeof(int);
848             int count = *((int*)bytes);  bytes += sizeof(int);
849             int bot = *((int*)bytes);  bytes += sizeof(int);
850             int left = *((int*)bytes);  bytes += sizeof(int);
851             int right = *((int*)bytes);  bytes += sizeof(int);
853 #if MM_DEBUG_DRAWING
854             NSLog(@"   Delete %d line(s) from %d", count, row);
855 #endif
856             [textStorage deleteLinesFromRow:row lineCount:count
857                     scrollBottom:bot left:left right:right
858                            color:[NSColor colorWithArgbInt:color]];
859         } else if (ReplaceStringDrawType == type) {
860             int bg = *((int*)bytes);  bytes += sizeof(int);
861             int fg = *((int*)bytes);  bytes += sizeof(int);
862             int sp = *((int*)bytes);  bytes += sizeof(int);
863             int row = *((int*)bytes);  bytes += sizeof(int);
864             int col = *((int*)bytes);  bytes += sizeof(int);
865             int flags = *((int*)bytes);  bytes += sizeof(int);
866             int len = *((int*)bytes);  bytes += sizeof(int);
867             NSString *string = [[NSString alloc]
868                     initWithBytesNoCopy:(void*)bytes
869                                  length:len
870                                encoding:NSUTF8StringEncoding
871                            freeWhenDone:NO];
872             bytes += len;
874 #if MM_DEBUG_DRAWING
875             NSLog(@"   Draw string at (%d,%d) length=%d flags=%d fg=0x%x "
876                     "bg=0x%x sp=0x%x (%@)", row, col, len, flags, fg, bg, sp,
877                     len > 0 ? [string substringToIndex:1] : @"");
878 #endif
879             // NOTE: If this is a call to draw the (block) cursor, then cancel
880             // any previous request to draw the insertion point, or it might
881             // get drawn as well.
882             if (flags & DRAW_CURSOR) {
883                 [textView setShouldDrawInsertionPoint:NO];
884                 //NSColor *color = [NSColor colorWithRgbInt:bg];
885                 //[textView drawInsertionPointAtRow:row column:col
886                 //                            shape:MMInsertionPointBlock
887                 //                            color:color];
888             }
889             [textStorage replaceString:string
890                                  atRow:row column:col
891                              withFlags:flags
892                        foregroundColor:[NSColor colorWithRgbInt:fg]
893                        backgroundColor:[NSColor colorWithArgbInt:bg]
894                           specialColor:[NSColor colorWithRgbInt:sp]];
896             [string release];
897         } else if (InsertLinesDrawType == type) {
898             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
899             int row = *((int*)bytes);  bytes += sizeof(int);
900             int count = *((int*)bytes);  bytes += sizeof(int);
901             int bot = *((int*)bytes);  bytes += sizeof(int);
902             int left = *((int*)bytes);  bytes += sizeof(int);
903             int right = *((int*)bytes);  bytes += sizeof(int);
905 #if MM_DEBUG_DRAWING
906             NSLog(@"   Insert %d line(s) at row %d", count, row);
907 #endif
908             [textStorage insertLinesAtRow:row lineCount:count
909                              scrollBottom:bot left:left right:right
910                                     color:[NSColor colorWithArgbInt:color]];
911         } else if (DrawCursorDrawType == type) {
912             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
913             int row = *((int*)bytes);  bytes += sizeof(int);
914             int col = *((int*)bytes);  bytes += sizeof(int);
915             int shape = *((int*)bytes);  bytes += sizeof(int);
916             int percent = *((int*)bytes);  bytes += sizeof(int);
918 #if MM_DEBUG_DRAWING
919             NSLog(@"   Draw cursor at (%d,%d)", row, col);
920 #endif
921             [textView drawInsertionPointAtRow:row column:col shape:shape
922                                      fraction:percent
923                                         color:[NSColor colorWithRgbInt:color]];
924         } else {
925             NSLog(@"WARNING: Unknown draw type (type=%d)", type);
926         }
927     }
929     [textStorage endEditing];
930 #if MM_DEBUG_DRAWING
931     NSLog(@"<==== END   %s", _cmd);
932 #endif
935 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
936                 context:(void *)context
938     NSString *string = (code == NSOKButton) ? [panel filename] : nil;
939     @try {
940         [backendProxy setDialogReturn:string];
941     }
942     @catch (NSException *e) {
943         NSLog(@"Exception caught in %s %@", _cmd, e);
944     }
947 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context
949     NSArray *ret = nil;
951     code = code - NSAlertFirstButtonReturn + 1;
953     if ([alert isKindOfClass:[MMAlert class]] && [alert textField]) {
954         ret = [NSArray arrayWithObjects:[NSNumber numberWithInt:code],
955             [[alert textField] stringValue], nil];
956     } else {
957         ret = [NSArray arrayWithObject:[NSNumber numberWithInt:code]];
958     }
960     @try {
961         [backendProxy setDialogReturn:ret];
962     }
963     @catch (NSException *e) {
964         NSLog(@"Exception caught in %s %@", _cmd, e);
965     }
968 - (NSMenuItem *)menuItemForTag:(int)tag
970     // Search the main menu.
971     int i, count = [mainMenuItems count];
972     for (i = 0; i < count; ++i) {
973         NSMenuItem *item = [mainMenuItems objectAtIndex:i];
974         if ([item tag] == tag) return item;
975         item = findMenuItemWithTagInMenu([item submenu], tag);
976         if (item) return item;
977     }
979     // Search the popup menus.
980     count = [popupMenuItems count];
981     for (i = 0; i < count; ++i) {
982         NSMenuItem *item = [popupMenuItems objectAtIndex:i];
983         if ([item tag] == tag) return item;
984         item = findMenuItemWithTagInMenu([item submenu], tag);
985         if (item) return item;
986     }
988     return nil;
991 - (NSMenu *)menuForTag:(int)tag
993     return [[self menuItemForTag:tag] submenu];
996 - (NSMenu *)topLevelMenuForTitle:(NSString *)title
998     // Search only the top-level menus.
1000     unsigned i, count = [popupMenuItems count];
1001     for (i = 0; i < count; ++i) {
1002         NSMenuItem *item = [popupMenuItems objectAtIndex:i];
1003         if ([title isEqual:[item title]])
1004             return [item submenu];
1005     }
1007     count = [mainMenuItems count];
1008     for (i = 0; i < count; ++i) {
1009         NSMenuItem *item = [mainMenuItems objectAtIndex:i];
1010         if ([title isEqual:[item title]])
1011             return [item submenu];
1012     }
1014     return nil;
1017 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
1018                atIndex:(int)idx
1020     NSMenu *parent = [self menuForTag:parentTag];
1021     NSMenuItem *item = [[NSMenuItem alloc] init];
1022     NSMenu *menu = [[NSMenu alloc] initWithTitle:title];
1024     [menu setAutoenablesItems:NO];
1025     [item setTag:tag];
1026     [item setTitle:title];
1027     [item setSubmenu:menu];
1029     if (parent) {
1030         if ([parent numberOfItems] <= idx) {
1031             [parent addItem:item];
1032         } else {
1033             [parent insertItem:item atIndex:idx];
1034         }
1035     } else {
1036         NSMutableArray *items = (MenuPopupType == parentTag)
1037             ? popupMenuItems : mainMenuItems;
1038         if ([items count] <= idx) {
1039             [items addObject:item];
1040         } else {
1041             [items insertObject:item atIndex:idx];
1042         }
1044         shouldUpdateMainMenu = (MenuPopupType != parentTag);
1045     }
1047     [item release];
1048     [menu release];
1051 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
1052                      title:(NSString *)title tip:(NSString *)tip
1053              keyEquivalent:(int)key modifiers:(int)mask
1054                     action:(NSString *)action atIndex:(int)idx
1056     if (parent) {
1057         NSMenuItem *item = nil;
1058         if (!title || ([title hasPrefix:@"-"] && [title hasSuffix:@"-"])) {
1059             item = [NSMenuItem separatorItem];
1060         } else {
1061             item = [[[NSMenuItem alloc] init] autorelease];
1062             [item setTitle:title];
1063             // TODO: Check that 'action' is a valid action (nothing will happen
1064             // if it isn't, but it would be nice with a warning).
1065             if (action) [item setAction:NSSelectorFromString(action)];
1066             else        [item setAction:@selector(vimMenuItemAction:)];
1067             if (tip) [item setToolTip:tip];
1069             if (key != 0) {
1070                 NSString *keyString =
1071                     [NSString stringWithFormat:@"%C", key];
1072                 [item setKeyEquivalent:keyString];
1073                 [item setKeyEquivalentModifierMask:mask];
1074             }
1075         }
1077         // NOTE!  The tag is used to idenfity which menu items were
1078         // added by Vim (tag != 0) and which were added by the AppKit
1079         // (tag == 0).
1080         [item setTag:tag];
1082         if ([parent numberOfItems] <= idx) {
1083             [parent addItem:item];
1084         } else {
1085             [parent insertItem:item atIndex:idx];
1086         }
1087     } else {
1088         NSLog(@"WARNING: Menu item '%@' (tag=%d) has no parent.", title, tag);
1089     }
1092 - (void)updateMainMenu
1094     NSMenu *mainMenu = [NSApp mainMenu];
1096     // Stop NSApp from updating the Window menu.
1097     [NSApp setWindowsMenu:nil];
1099     // Remove all menus from main menu (except the MacVim menu).
1100     int i, count = [mainMenu numberOfItems];
1101     for (i = count-1; i > 0; --i) {
1102         [mainMenu removeItemAtIndex:i];
1103     }
1105     // Add menus from 'mainMenuItems' to main menu.
1106     count = [mainMenuItems count];
1107     for (i = 0; i < count; ++i) {
1108         [mainMenu addItem:[mainMenuItems objectAtIndex:i]];
1109     }
1111     // Set the new Window menu.
1112     // TODO!  Need to look for 'Window' in all localized languages.
1113     NSMenu *windowMenu = [[mainMenu itemWithTitle:@"Window"] submenu];
1114     if (windowMenu) {
1115         // Remove all AppKit owned menu items (tag == 0); they will be added
1116         // again when setWindowsMenu: is called.
1117         count = [windowMenu numberOfItems];
1118         for (i = count-1; i >= 0; --i) {
1119             NSMenuItem *item = [windowMenu itemAtIndex:i];
1120             if (![item tag]) {
1121                 [windowMenu removeItem:item];
1122             }
1123         }
1125         [NSApp setWindowsMenu:windowMenu];
1126     }
1128     shouldUpdateMainMenu = NO;
1131 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index
1133     if (!toolbar) return nil;
1135     NSArray *items = [toolbar items];
1136     int i, count = [items count];
1137     for (i = 0; i < count; ++i) {
1138         NSToolbarItem *item = [items objectAtIndex:i];
1139         if ([item tag] == tag) {
1140             if (index) *index = i;
1141             return item;
1142         }
1143     }
1145     return nil;
1148 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
1149         toolTip:(NSString *)tip icon:(NSString *)icon
1151     // If the item corresponds to a separator then do nothing, since it is
1152     // already defined by Cocoa.
1153     if (!title || [title isEqual:NSToolbarSeparatorItemIdentifier]
1154                || [title isEqual:NSToolbarSpaceItemIdentifier]
1155                || [title isEqual:NSToolbarFlexibleSpaceItemIdentifier])
1156         return;
1158     NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:title];
1159     [item setTag:tag];
1160     [item setLabel:title];
1161     [item setToolTip:tip];
1162     [item setAction:@selector(vimMenuItemAction:)];
1163     [item setAutovalidates:NO];
1165     NSImage *img = [NSImage imageNamed:icon];
1166     if (!img) {
1167         NSLog(@"WARNING: Could not find image with name '%@' to use as toolbar"
1168                " image for identifier '%@';"
1169                " using default toolbar icon '%@' instead.",
1170                icon, title, MMDefaultToolbarImageName);
1172         img = [NSImage imageNamed:MMDefaultToolbarImageName];
1173     }
1175     [item setImage:img];
1177     [toolbarItemDict setObject:item forKey:title];
1179     [item release];
1182 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label tip:(NSString
1183                    *)tip icon:(NSString *)icon atIndex:(int)idx
1185     if (!toolbar) return;
1187     // Check for separator items.
1188     if (!label) {
1189         label = NSToolbarSeparatorItemIdentifier;
1190     } else if ([label length] >= 2 && [label hasPrefix:@"-"]
1191                                    && [label hasSuffix:@"-"]) {
1192         // The label begins and ends with '-'; decided which kind of separator
1193         // item it is by looking at the prefix.
1194         if ([label hasPrefix:@"-space"]) {
1195             label = NSToolbarSpaceItemIdentifier;
1196         } else if ([label hasPrefix:@"-flexspace"]) {
1197             label = NSToolbarFlexibleSpaceItemIdentifier;
1198         } else {
1199             label = NSToolbarSeparatorItemIdentifier;
1200         }
1201     }
1203     [self addToolbarItemToDictionaryWithTag:tag label:label toolTip:tip
1204                                        icon:icon];
1206     int maxIdx = [[toolbar items] count];
1207     if (maxIdx < idx) idx = maxIdx;
1209     [toolbar insertItemWithItemIdentifier:label atIndex:idx];
1212 - (void)connectionDidDie:(NSNotification *)notification
1214     //NSLog(@"%@ %s%@", [self className], _cmd, notification);
1216     [self cleanup];
1218     // NOTE!  This causes the call to removeVimController: to be delayed.
1219     [[NSApp delegate]
1220             performSelectorOnMainThread:@selector(removeVimController:)
1221                              withObject:self waitUntilDone:NO];
1224 - (NSString *)description
1226     return [NSString stringWithFormat:@"%@ : isInitialized=%d inProcessCommandQueue=%d mainMenuItems=%@ popupMenuItems=%@ toolbar=%@", [self className], isInitialized, inProcessCommandQueue, mainMenuItems, popupMenuItems, toolbar];
1229 #if MM_RESEND_LAST_FAILURE
1230 - (void)resendTimerFired:(NSTimer *)timer
1232     int msgid = resendMsgid;
1233     NSData *data = nil;
1235     [resendTimer release];
1236     resendTimer = nil;
1238     if (!isInitialized)
1239         return;
1241     if (resendData)
1242         data = [resendData copy];
1244     //NSLog(@"Resending message: %s", MessageStrings[msgid]);
1245     [self sendMessage:msgid data:data];
1247 #endif
1249 @end // MMVimController (Private)
1253 @implementation NSColor (MMProtocol)
1255 + (NSColor *)colorWithRgbInt:(unsigned)rgb
1257     float r = ((rgb>>16) & 0xff)/255.0f;
1258     float g = ((rgb>>8) & 0xff)/255.0f;
1259     float b = (rgb & 0xff)/255.0f;
1261     return [NSColor colorWithCalibratedRed:r green:g blue:b alpha:1.0f];
1264 + (NSColor *)colorWithArgbInt:(unsigned)argb
1266     float a = ((argb>>24) & 0xff)/255.0f;
1267     float r = ((argb>>16) & 0xff)/255.0f;
1268     float g = ((argb>>8) & 0xff)/255.0f;
1269     float b = (argb & 0xff)/255.0f;
1271     return [NSColor colorWithCalibratedRed:r green:g blue:b alpha:a];
1274 @end // NSColor (MMProtocol)
1278 @implementation MMAlert
1279 - (void)dealloc
1281     [textField release];
1282     [super dealloc];
1285 - (void)setTextFieldString:(NSString *)textFieldString
1287     [textField release];
1288     textField = [[NSTextField alloc] init];
1289     [textField setStringValue:textFieldString];
1292 - (NSTextField *)textField
1294     return textField;
1297 - (void)setInformativeText:(NSString *)text
1299     if (textField) {
1300         // HACK! Add some space for the text field.
1301         [super setInformativeText:[text stringByAppendingString:@"\n\n\n"]];
1302     } else {
1303         [super setInformativeText:text];
1304     }
1307 - (void)beginSheetModalForWindow:(NSWindow *)window
1308                    modalDelegate:(id)delegate
1309                   didEndSelector:(SEL)didEndSelector
1310                      contextInfo:(void *)contextInfo
1312     [super beginSheetModalForWindow:window
1313                       modalDelegate:delegate
1314                      didEndSelector:didEndSelector
1315                         contextInfo:contextInfo];
1317     // HACK! Place the input text field at the bottom of the informative text
1318     // (which has been made a bit larger by adding newline characters).
1319     NSView *contentView = [[self window] contentView];
1320     NSRect rect = [contentView frame];
1321     rect.origin.y = rect.size.height;
1323     NSArray *subviews = [contentView subviews];
1324     unsigned i, count = [subviews count];
1325     for (i = 0; i < count; ++i) {
1326         NSView *view = [subviews objectAtIndex:i];
1327         if ([view isKindOfClass:[NSTextField class]]
1328                 && [view frame].origin.y < rect.origin.y) {
1329             // NOTE: The informative text field is the lowest NSTextField in
1330             // the alert dialog.
1331             rect = [view frame];
1332         }
1333     }
1335     rect.size.height = MMAlertTextFieldHeight;
1336     [textField setFrame:rect];
1337     [contentView addSubview:textField];
1338     [textField becomeFirstResponder];
1341 @end // MMAlert