Experimental ATSUI text rendering support
[MacVim.git] / src / MacVim / MMVimController.m
blob4a036cbdb482dd93606b34856adc3ac682e33cfd
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  * MMVimController
12  *
13  * Coordinates input/output to/from backend.  Each MMBackend communicates
14  * directly with a MMVimController.
15  *
16  * MMVimController does not deal with visual presentation.  Essentially it
17  * should be able to run with no window present.
18  *
19  * Output from the backend is received in processCommandQueue:.  Input is sent
20  * to the backend via sendMessage:data: or addVimInput:.  The latter allows
21  * execution of arbitrary stings in the Vim process, much like the Vim script
22  * function remote_send() does.  The messages that may be passed between
23  * frontend and backend are defined in an enum in MacVim.h.
24  */
26 #import "MMVimController.h"
27 #import "MMWindowController.h"
28 #import "MMTextView.h"
29 #import "MMAppController.h"
30 #import "MMTextStorage.h"
31 #import "MMAtsuiTextView.h"
34 // This is taken from gui.h
35 #define DRAW_CURSOR 0x20
37 static NSString *MMDefaultToolbarImageName = @"Attention";
38 static int MMAlertTextFieldHeight = 22;
40 // NOTE: By default a message sent to the backend will be dropped if it cannot
41 // be delivered instantly; otherwise there is a possibility that MacVim will
42 // 'beachball' while waiting to deliver DO messages to an unresponsive Vim
43 // process.  This means that you cannot rely on any message sent with
44 // sendMessage: to actually reach Vim.
45 static NSTimeInterval MMBackendProxyRequestTimeout = 0;
47 #if MM_RESEND_LAST_FAILURE
48 // If a message send fails, the message will be resent after this many seconds
49 // have passed.  (No queue is kept, only the very last message is resent.)
50 static NSTimeInterval MMResendInterval = 0.5;
51 #endif
54 @interface MMAlert : NSAlert {
55     NSTextField *textField;
57 - (void)setTextFieldString:(NSString *)textFieldString;
58 - (NSTextField *)textField;
59 @end
62 @interface MMVimController (Private)
63 - (void)handleMessage:(int)msgid data:(NSData *)data;
64 - (void)performBatchDrawWithData:(NSData *)data;
65 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
66                 context:(void *)context;
67 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context;
68 - (NSMenuItem *)recurseMenuItemForTag:(int)tag rootMenu:(NSMenu *)root;
69 - (NSMenuItem *)menuItemForTag:(int)tag;
70 - (NSMenu *)menuForTag:(int)tag;
71 - (NSMenu *)topLevelMenuForTitle:(NSString *)title;
72 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
73                atIndex:(int)idx;
74 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
75                      title:(NSString *)title tip:(NSString *)tip
76              keyEquivalent:(int)key modifiers:(int)mask
77                     action:(NSString *)action atIndex:(int)idx;
78 - (void)updateMainMenu;
79 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index;
80 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
81         toolTip:(NSString *)tip icon:(NSString *)icon;
82 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label
83                           tip:(NSString *)tip icon:(NSString *)icon
84                       atIndex:(int)idx;
85 - (void)connectionDidDie:(NSNotification *)notification;
86 #if MM_RESEND_LAST_FAILURE
87 - (void)resendTimerFired:(NSTimer *)timer;
88 #endif
89 @end
94 @implementation MMVimController
96 - (id)initWithBackend:(id)backend pid:(int)processIdentifier
98     if ((self = [super init])) {
99         windowController =
100             [[MMWindowController alloc] initWithVimController:self];
101         backendProxy = [backend retain];
102         sendQueue = [NSMutableArray new];
103         mainMenuItems = [[NSMutableArray alloc] init];
104         popupMenuItems = [[NSMutableArray alloc] init];
105         toolbarItemDict = [[NSMutableDictionary alloc] init];
106         pid = processIdentifier;
108         NSConnection *connection = [backendProxy connectionForProxy];
110         // TODO: Check that this will not set the timeout for the root proxy
111         // (in MMAppController).
112         [connection setRequestTimeout:MMBackendProxyRequestTimeout];
114         [[NSNotificationCenter defaultCenter] addObserver:self
115                 selector:@selector(connectionDidDie:)
116                     name:NSConnectionDidDieNotification object:connection];
119         NSWindow *win = [windowController window];
121         [[NSNotificationCenter defaultCenter]
122                 addObserver:self
123                    selector:@selector(windowDidBecomeMain:)
124                        name:NSWindowDidBecomeMainNotification
125                      object:win];
127         isInitialized = YES;
128     }
130     return self;
133 - (void)dealloc
135     //NSLog(@"%@ %s", [self className], _cmd);
136     isInitialized = NO;
138 #if MM_RESEND_LAST_FAILURE
139     [resendData release];  resendData = nil;
140 #endif
142     [serverName release];  serverName = nil;
143     [backendProxy release];  backendProxy = nil;
144     [sendQueue release];  sendQueue = nil;
146     [toolbarItemDict release];  toolbarItemDict = nil;
147     [toolbar release];  toolbar = nil;
148     [popupMenuItems release];  popupMenuItems = nil;
149     [mainMenuItems release];  mainMenuItems = nil;
150     [windowController release];  windowController = nil;
152     [super dealloc];
155 - (MMWindowController *)windowController
157     return windowController;
160 - (void)setServerName:(NSString *)name
162     if (name != serverName) {
163         [serverName release];
164         serverName = [name copy];
165     }
168 - (NSString *)serverName
170     return serverName;
173 - (int)pid
175     return pid;
178 - (void)dropFiles:(NSArray *)filenames forceOpen:(BOOL)force
180     unsigned i, numberOfFiles = [filenames count];
181     NSMutableData *data = [NSMutableData data];
183     [data appendBytes:&force length:sizeof(BOOL)];
184     [data appendBytes:&numberOfFiles length:sizeof(int)];
186     for (i = 0; i < numberOfFiles; ++i) {
187         NSString *file = [filenames objectAtIndex:i];
188         int len = [file lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
190         if (len > 0) {
191             ++len;  // include NUL as well
192             [data appendBytes:&len length:sizeof(int)];
193             [data appendBytes:[file UTF8String] length:len];
194         }
195     }
197     [self sendMessage:DropFilesMsgID data:data];
200 - (void)dropString:(NSString *)string
202     int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
203     if (len > 0) {
204         NSMutableData *data = [NSMutableData data];
206         [data appendBytes:&len length:sizeof(int)];
207         [data appendBytes:[string UTF8String] length:len];
209         [self sendMessage:DropStringMsgID data:data];
210     }
213 - (void)odbEdit:(NSArray *)filenames server:(OSType)theID path:(NSString *)path
214           token:(NSAppleEventDescriptor *)token
216     int len;
217     unsigned i, numberOfFiles = [filenames count];
218     NSMutableData *data = [NSMutableData data];
220     if (0 == numberOfFiles || 0 == theID)
221         return;
223     [data appendBytes:&theID length:sizeof(theID)];
225     if (path && [path length] > 0) {
226         len = [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
227         [data appendBytes:&len length:sizeof(int)];
228         [data appendBytes:[path UTF8String] length:len];
229     } else {
230         len = 0;
231         [data appendBytes:&len length:sizeof(int)];
232     }
234     if (token) {
235         DescType tokenType = [token descriptorType];
236         NSData *tokenData = [token data];
237         len = [tokenData length];
239         [data appendBytes:&tokenType length:sizeof(tokenType)];
240         [data appendBytes:&len length:sizeof(int)];
241         if (len > 0)
242             [data appendBytes:[tokenData bytes] length:len];
243     } else {
244         DescType tokenType = 0;
245         len = 0;
246         [data appendBytes:&tokenType length:sizeof(tokenType)];
247         [data appendBytes:&len length:sizeof(int)];
248     }
250     [data appendBytes:&numberOfFiles length:sizeof(int)];
252     for (i = 0; i < numberOfFiles; ++i) {
253         NSString *file = [filenames objectAtIndex:i];
254         len = [file lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
256         if (len > 0) {
257             ++len;  // include NUL as well
258             [data appendBytes:&len length:sizeof(unsigned)];
259             [data appendBytes:[file UTF8String] length:len];
260         }
261     }
263     [self sendMessage:ODBEditMsgID data:data];
266 - (void)sendMessage:(int)msgid data:(NSData *)data
268     //NSLog(@"sendMessage:%s (isInitialized=%d inProcessCommandQueue=%d)",
269     //        MessageStrings[msgid], isInitialized, inProcessCommandQueue);
271     if (!isInitialized) return;
273     if (inProcessCommandQueue) {
274         //NSLog(@"In process command queue; delaying message send.");
275         [sendQueue addObject:[NSNumber numberWithInt:msgid]];
276         if (data)
277             [sendQueue addObject:data];
278         else
279             [sendQueue addObject:[NSNull null]];
280         return;
281     }
283 #if MM_RESEND_LAST_FAILURE
284     if (resendTimer) {
285         //NSLog(@"cancelling scheduled resend of %s",
286         //        MessageStrings[resendMsgid]);
288         [resendTimer invalidate];
289         [resendTimer release];
290         resendTimer = nil;
291     }
293     if (resendData) {
294         [resendData release];
295         resendData = nil;
296     }
297 #endif
299     @try {
300         [backendProxy processInput:msgid data:data];
301     }
302     @catch (NSException *e) {
303         //NSLog(@"%@ %s Exception caught during DO call: %@",
304         //        [self className], _cmd, e);
305 #if MM_RESEND_LAST_FAILURE
306         //NSLog(@"%s failed, scheduling message %s for resend", _cmd,
307         //        MessageStrings[msgid]);
309         resendMsgid = msgid;
310         resendData = [data retain];
311         resendTimer = [NSTimer
312             scheduledTimerWithTimeInterval:MMResendInterval
313                                     target:self
314                                   selector:@selector(resendTimerFired:)
315                                   userInfo:nil
316                                    repeats:NO];
317         [resendTimer retain];
318 #endif
319     }
322 - (BOOL)sendMessageNow:(int)msgid data:(NSData *)data
323                timeout:(NSTimeInterval)timeout
325     if (!isInitialized || inProcessCommandQueue)
326         return NO;
328     if (timeout < 0) timeout = 0;
330     BOOL sendOk = YES;
331     NSConnection *conn = [backendProxy connectionForProxy];
332     NSTimeInterval oldTimeout = [conn requestTimeout];
334     [conn setRequestTimeout:timeout];
336     @try {
337         [backendProxy processInput:msgid data:data];
338     }
339     @catch (NSException *e) {
340         sendOk = NO;
341     }
342     @finally {
343         [conn setRequestTimeout:oldTimeout];
344     }
346     return sendOk;
349 - (void)addVimInput:(NSString *)string
351     // This is a very general method of adding input to the Vim process.  It is
352     // basically the same as calling remote_send() on the process (see
353     // ':h remote_send').
354     if (string) {
355         NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
356         [self sendMessage:AddInputMsgID data:data];
357     }
360 - (id)backendProxy
362     return backendProxy;
365 - (void)cleanup
367     //NSLog(@"%@ %s", [self className], _cmd);
368     if (!isInitialized) return;
370     isInitialized = NO;
371     [toolbar setDelegate:nil];
372     [[NSNotificationCenter defaultCenter] removeObserver:self];
373     [windowController cleanup];
376 - (oneway void)showSavePanelForDirectory:(in bycopy NSString *)dir
377                                    title:(in bycopy NSString *)title
378                                   saving:(int)saving
380     if (!isInitialized) return;
382     if (saving) {
383         [[NSSavePanel savePanel] beginSheetForDirectory:dir file:nil
384                 modalForWindow:[windowController window]
385                  modalDelegate:self
386                 didEndSelector:@selector(savePanelDidEnd:code:context:)
387                    contextInfo:NULL];
388     } else {
389         NSOpenPanel *panel = [NSOpenPanel openPanel];
390         [panel setAllowsMultipleSelection:NO];
391         [panel beginSheetForDirectory:dir file:nil types:nil
392                 modalForWindow:[windowController window]
393                  modalDelegate:self
394                 didEndSelector:@selector(savePanelDidEnd:code:context:)
395                    contextInfo:NULL];
396     }
399 - (oneway void)presentDialogWithStyle:(int)style
400                               message:(in bycopy NSString *)message
401                       informativeText:(in bycopy NSString *)text
402                          buttonTitles:(in bycopy NSArray *)buttonTitles
403                       textFieldString:(in bycopy NSString *)textFieldString
405     if (!(windowController && buttonTitles && [buttonTitles count])) return;
407     MMAlert *alert = [[MMAlert alloc] init];
409     // NOTE! This has to be done before setting the informative text.
410     if (textFieldString)
411         [alert setTextFieldString:textFieldString];
413     [alert setAlertStyle:style];
415     if (message) {
416         [alert setMessageText:message];
417     } else {
418         // If no message text is specified 'Alert' is used, which we don't
419         // want, so set an empty string as message text.
420         [alert setMessageText:@""];
421     }
423     if (text) {
424         [alert setInformativeText:text];
425     } else if (textFieldString) {
426         // Make sure there is always room for the input text field.
427         [alert setInformativeText:@""];
428     }
430     unsigned i, count = [buttonTitles count];
431     for (i = 0; i < count; ++i) {
432         NSString *title = [buttonTitles objectAtIndex:i];
433         // NOTE: The title of the button may contain the character '&' to
434         // indicate that the following letter should be the key equivalent
435         // associated with the button.  Extract this letter and lowercase it.
436         NSString *keyEquivalent = nil;
437         NSRange hotkeyRange = [title rangeOfString:@"&"];
438         if (NSNotFound != hotkeyRange.location) {
439             if ([title length] > NSMaxRange(hotkeyRange)) {
440                 NSRange keyEquivRange = NSMakeRange(hotkeyRange.location+1, 1);
441                 keyEquivalent = [[title substringWithRange:keyEquivRange]
442                     lowercaseString];
443             }
445             NSMutableString *string = [NSMutableString stringWithString:title];
446             [string deleteCharactersInRange:hotkeyRange];
447             title = string;
448         }
450         [alert addButtonWithTitle:title];
452         // Set key equivalent for the button, but only if NSAlert hasn't
453         // already done so.  (Check the documentation for
454         // - [NSAlert addButtonWithTitle:] to see what key equivalents are
455         // automatically assigned.)
456         NSButton *btn = [[alert buttons] lastObject];
457         if ([[btn keyEquivalent] length] == 0 && keyEquivalent) {
458             [btn setKeyEquivalent:keyEquivalent];
459         }
460     }
462     [alert beginSheetModalForWindow:[windowController window]
463                       modalDelegate:self
464                      didEndSelector:@selector(alertDidEnd:code:context:)
465                         contextInfo:NULL];
467     [alert release];
470 - (oneway void)processCommandQueue:(in bycopy NSArray *)queue
472     if (!isInitialized) return;
474     unsigned i, count = [queue count];
475     if (count % 2) {
476         NSLog(@"WARNING: Uneven number of components (%d) in flush queue "
477                 "message; ignoring this message.", count);
478         return;
479     }
481     inProcessCommandQueue = YES;
483     //NSLog(@"======== %s BEGIN ========", _cmd);
484     for (i = 0; i < count; i += 2) {
485         NSData *value = [queue objectAtIndex:i];
486         NSData *data = [queue objectAtIndex:i+1];
488         int msgid = *((int*)[value bytes]);
489         //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
491         [self handleMessage:msgid data:data];
492     }
493     //NSLog(@"======== %s  END  ========", _cmd);
495     if (shouldUpdateMainMenu) {
496         [self updateMainMenu];
497     }
499     [windowController processCommandQueueDidFinish];
501     inProcessCommandQueue = NO;
503     if ([sendQueue count] > 0) {
504         @try {
505             [backendProxy processInputAndData:sendQueue];
506         }
507         @catch (NSException *e) {
508             // Connection timed out, just ignore this.
509             //NSLog(@"WARNING! Connection timed out in %s", _cmd);
510         }
512         [sendQueue removeAllObjects];
513     }
516 - (void)windowDidBecomeMain:(NSNotification *)notification
518     if (isInitialized)
519         [self updateMainMenu];
522 - (NSToolbarItem *)toolbar:(NSToolbar *)theToolbar
523     itemForItemIdentifier:(NSString *)itemId
524     willBeInsertedIntoToolbar:(BOOL)flag
526     NSToolbarItem *item = [toolbarItemDict objectForKey:itemId];
527     if (!item) {
528         NSLog(@"WARNING:  No toolbar item with id '%@'", itemId);
529     }
531     return item;
534 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)theToolbar
536     return nil;
539 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)theToolbar
541     return nil;
544 @end // MMVimController
548 @implementation MMVimController (Private)
550 - (void)handleMessage:(int)msgid data:(NSData *)data
552     //NSLog(@"%@ %s", [self className], _cmd);
554     if (OpenVimWindowMsgID == msgid) {
555         [windowController openWindow];
556     } else if (BatchDrawMsgID == msgid) {
557         if ([[NSUserDefaults standardUserDefaults]
558                 boolForKey:MMAtsuiRendererKey])
559             [(MMAtsuiTextView *)[windowController textView] performBatchDrawWithData:data];
560         else
561             [self performBatchDrawWithData:data];
562     } else if (SelectTabMsgID == msgid) {
563 #if 0   // NOTE: Tab selection is done inside updateTabsWithData:.
564         const void *bytes = [data bytes];
565         int idx = *((int*)bytes);
566         //NSLog(@"Selecting tab with index %d", idx);
567         [windowController selectTabWithIndex:idx];
568 #endif
569     } else if (UpdateTabBarMsgID == msgid) {
570         [windowController updateTabsWithData:data];
571     } else if (ShowTabBarMsgID == msgid) {
572         [windowController showTabBar:YES];
573     } else if (HideTabBarMsgID == msgid) {
574         [windowController showTabBar:NO];
575     } else if (SetTextDimensionsMsgID == msgid) {
576         const void *bytes = [data bytes];
577         int rows = *((int*)bytes);  bytes += sizeof(int);
578         int cols = *((int*)bytes);  bytes += sizeof(int);
580         [windowController setTextDimensionsWithRows:rows columns:cols];
581     } else if (SetWindowTitleMsgID == msgid) {
582         const void *bytes = [data bytes];
583         int len = *((int*)bytes);  bytes += sizeof(int);
585         NSString *string = [[NSString alloc] initWithBytes:(void*)bytes
586                 length:len encoding:NSUTF8StringEncoding];
588         [[windowController window] setTitle:string];
590         [string release];
591     } else if (AddMenuMsgID == msgid) {
592         NSString *title = nil;
593         const void *bytes = [data bytes];
594         int tag = *((int*)bytes);  bytes += sizeof(int);
595         int parentTag = *((int*)bytes);  bytes += sizeof(int);
596         int len = *((int*)bytes);  bytes += sizeof(int);
597         if (len > 0) {
598             title = [[NSString alloc] initWithBytes:(void*)bytes length:len
599                                            encoding:NSUTF8StringEncoding];
600             bytes += len;
601         }
602         int idx = *((int*)bytes);  bytes += sizeof(int);
604         if (MenuToolbarType == parentTag) {
605             if (!toolbar) {
606                 // NOTE! Each toolbar must have a unique identifier, else each
607                 // window will have the same toolbar.
608                 NSString *ident = [NSString stringWithFormat:@"%d.%d",
609                          (int)self, tag];
610                 toolbar = [[NSToolbar alloc] initWithIdentifier:ident];
612                 [toolbar setShowsBaselineSeparator:NO];
613                 [toolbar setDelegate:self];
614                 [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
615                 [toolbar setSizeMode:NSToolbarSizeModeSmall];
617                 NSWindow *win = [windowController window];
618                 [win setToolbar:toolbar];
620                 // HACK! Redirect the pill button so that we can ask Vim to
621                 // hide the toolbar.
622                 NSButton *pillButton = [win
623                     standardWindowButton:NSWindowToolbarButton];
624                 if (pillButton) {
625                     [pillButton setAction:@selector(toggleToolbar:)];
626                     [pillButton setTarget:windowController];
627                 }
628             }
629         } else if (title) {
630             [self addMenuWithTag:tag parent:parentTag title:title atIndex:idx];
631         }
633         [title release];
634     } else if (AddMenuItemMsgID == msgid) {
635         NSString *title = nil, *tip = nil, *icon = nil, *action = nil;
636         const void *bytes = [data bytes];
637         int tag = *((int*)bytes);  bytes += sizeof(int);
638         int parentTag = *((int*)bytes);  bytes += sizeof(int);
639         int namelen = *((int*)bytes);  bytes += sizeof(int);
640         if (namelen > 0) {
641             title = [[NSString alloc] initWithBytes:(void*)bytes length:namelen
642                                            encoding:NSUTF8StringEncoding];
643             bytes += namelen;
644         }
645         int tiplen = *((int*)bytes);  bytes += sizeof(int);
646         if (tiplen > 0) {
647             tip = [[NSString alloc] initWithBytes:(void*)bytes length:tiplen
648                                            encoding:NSUTF8StringEncoding];
649             bytes += tiplen;
650         }
651         int iconlen = *((int*)bytes);  bytes += sizeof(int);
652         if (iconlen > 0) {
653             icon = [[NSString alloc] initWithBytes:(void*)bytes length:iconlen
654                                            encoding:NSUTF8StringEncoding];
655             bytes += iconlen;
656         }
657         int actionlen = *((int*)bytes);  bytes += sizeof(int);
658         if (actionlen > 0) {
659             action = [[NSString alloc] initWithBytes:(void*)bytes
660                                               length:actionlen
661                                             encoding:NSUTF8StringEncoding];
662             bytes += actionlen;
663         }
664         int idx = *((int*)bytes);  bytes += sizeof(int);
665         if (idx < 0) idx = 0;
666         int key = *((int*)bytes);  bytes += sizeof(int);
667         int mask = *((int*)bytes);  bytes += sizeof(int);
669         NSString *ident = [NSString stringWithFormat:@"%d.%d",
670                 (int)self, parentTag];
671         if (toolbar && [[toolbar identifier] isEqual:ident]) {
672             [self addToolbarItemWithTag:tag label:title tip:tip icon:icon
673                                 atIndex:idx];
674         } else {
675             NSMenu *parent = [self menuForTag:parentTag];
676             [self addMenuItemWithTag:tag parent:parent title:title tip:tip
677                        keyEquivalent:key modifiers:mask action:action
678                              atIndex:idx];
679         }
681         [title release];
682         [tip release];
683         [icon release];
684         [action release];
685     } else if (RemoveMenuItemMsgID == msgid) {
686         const void *bytes = [data bytes];
687         int tag = *((int*)bytes);  bytes += sizeof(int);
689         id item;
690         int idx;
691         if ((item = [self toolbarItemForTag:tag index:&idx])) {
692             [toolbar removeItemAtIndex:idx];
693         } else if ((item = [self menuItemForTag:tag])) {
694             [item retain];
696             if ([item menu] == [NSApp mainMenu] || ![item menu]) {
697                 // NOTE: To be on the safe side we try to remove the item from
698                 // both arrays (it is ok to call removeObject: even if an array
699                 // does not contain the object to remove).
700                 [mainMenuItems removeObject:item];
701                 [popupMenuItems removeObject:item];
702             }
704             if ([item menu])
705                 [[item menu] removeItem:item];
707             [item release];
708         }
710         // Reset cached menu, just to be on the safe side.
711         lastMenuSearched = nil;
712     } else if (EnableMenuItemMsgID == msgid) {
713         const void *bytes = [data bytes];
714         int tag = *((int*)bytes);  bytes += sizeof(int);
715         int state = *((int*)bytes);  bytes += sizeof(int);
717         id item = [self toolbarItemForTag:tag index:NULL];
718         if (!item)
719             item = [self menuItemForTag:tag];
721         [item setEnabled:state];
722     } else if (ShowToolbarMsgID == msgid) {
723         const void *bytes = [data bytes];
724         int enable = *((int*)bytes);  bytes += sizeof(int);
725         int flags = *((int*)bytes);  bytes += sizeof(int);
727         int mode = NSToolbarDisplayModeDefault;
728         if (flags & ToolbarLabelFlag) {
729             mode = flags & ToolbarIconFlag ? NSToolbarDisplayModeIconAndLabel
730                     : NSToolbarDisplayModeLabelOnly;
731         } else if (flags & ToolbarIconFlag) {
732             mode = NSToolbarDisplayModeIconOnly;
733         }
735         int size = flags & ToolbarSizeRegularFlag ? NSToolbarSizeModeRegular
736                 : NSToolbarSizeModeSmall;
738         [windowController showToolbar:enable size:size mode:mode];
739     } else if (CreateScrollbarMsgID == msgid) {
740         const void *bytes = [data bytes];
741         long ident = *((long*)bytes);  bytes += sizeof(long);
742         int type = *((int*)bytes);  bytes += sizeof(int);
744         [windowController createScrollbarWithIdentifier:ident type:type];
745     } else if (DestroyScrollbarMsgID == msgid) {
746         const void *bytes = [data bytes];
747         long ident = *((long*)bytes);  bytes += sizeof(long);
749         [windowController destroyScrollbarWithIdentifier:ident];
750     } else if (ShowScrollbarMsgID == msgid) {
751         const void *bytes = [data bytes];
752         long ident = *((long*)bytes);  bytes += sizeof(long);
753         int visible = *((int*)bytes);  bytes += sizeof(int);
755         [windowController showScrollbarWithIdentifier:ident state:visible];
756     } else if (SetScrollbarPositionMsgID == msgid) {
757         const void *bytes = [data bytes];
758         long ident = *((long*)bytes);  bytes += sizeof(long);
759         int pos = *((int*)bytes);  bytes += sizeof(int);
760         int len = *((int*)bytes);  bytes += sizeof(int);
762         [windowController setScrollbarPosition:pos length:len
763                                     identifier:ident];
764     } else if (SetScrollbarThumbMsgID == msgid) {
765         const void *bytes = [data bytes];
766         long ident = *((long*)bytes);  bytes += sizeof(long);
767         float val = *((float*)bytes);  bytes += sizeof(float);
768         float prop = *((float*)bytes);  bytes += sizeof(float);
770         [windowController setScrollbarThumbValue:val proportion:prop
771                                       identifier:ident];
772     } else if (SetFontMsgID == msgid) {
773         const void *bytes = [data bytes];
774         float size = *((float*)bytes);  bytes += sizeof(float);
775         int len = *((int*)bytes);  bytes += sizeof(int);
776         NSString *name = [[NSString alloc]
777                 initWithBytes:(void*)bytes length:len
778                      encoding:NSUTF8StringEncoding];
779         NSFont *font = [NSFont fontWithName:name size:size];
781         if (font)
782             [windowController setFont:font];
784         [name release];
785     } else if (SetWideFontMsgID == msgid) {
786         const void *bytes = [data bytes];
787         float size = *((float*)bytes);  bytes += sizeof(float);
788         int len = *((int*)bytes);  bytes += sizeof(int);
789         if (len > 0) {
790             NSString *name = [[NSString alloc]
791                     initWithBytes:(void*)bytes length:len
792                          encoding:NSUTF8StringEncoding];
793             NSFont *font = [NSFont fontWithName:name size:size];
794             [windowController setWideFont:font];
796             [name release];
797         } else {
798             [windowController setWideFont:nil];
799         }
800     } else if (SetDefaultColorsMsgID == msgid) {
801         const void *bytes = [data bytes];
802         unsigned bg = *((unsigned*)bytes);  bytes += sizeof(unsigned);
803         unsigned fg = *((unsigned*)bytes);  bytes += sizeof(unsigned);
804         NSColor *back = [NSColor colorWithArgbInt:bg];
805         NSColor *fore = [NSColor colorWithRgbInt:fg];
807         [windowController setDefaultColorsBackground:back foreground:fore];
808     } else if (ExecuteActionMsgID == msgid) {
809         const void *bytes = [data bytes];
810         int len = *((int*)bytes);  bytes += sizeof(int);
811         NSString *actionName = [[NSString alloc]
812                 initWithBytesNoCopy:(void*)bytes
813                              length:len
814                            encoding:NSUTF8StringEncoding
815                        freeWhenDone:NO];
817         SEL sel = NSSelectorFromString(actionName);
818         [NSApp sendAction:sel to:nil from:self];
820         [actionName release];
821     } else if (ShowPopupMenuMsgID == msgid) {
822         const void *bytes = [data bytes];
823         int row = *((int*)bytes);  bytes += sizeof(int);
824         int col = *((int*)bytes);  bytes += sizeof(int);
825         int len = *((int*)bytes);  bytes += sizeof(int);
826         NSString *title = [[NSString alloc]
827                 initWithBytesNoCopy:(void*)bytes
828                              length:len
829                            encoding:NSUTF8StringEncoding
830                        freeWhenDone:NO];
832         NSMenu *menu = [self topLevelMenuForTitle:title];
833         if (menu) {
834             [windowController popupMenu:menu atRow:row column:col];
835         } else {
836             NSLog(@"WARNING: Cannot popup menu with title %@; no such menu.",
837                     title);
838         }
840         [title release];
841     } else if (SetMouseShapeMsgID == msgid) {
842         const void *bytes = [data bytes];
843         int shape = *((int*)bytes);  bytes += sizeof(int);
845         [windowController setMouseShape:shape];
846     } else if (AdjustLinespaceMsgID == msgid) {
847         const void *bytes = [data bytes];
848         int linespace = *((int*)bytes);  bytes += sizeof(int);
850         [windowController adjustLinespace:linespace];
851     } else if (ActivateMsgID == msgid) {
852         //NSLog(@"ActivateMsgID");
853         [NSApp activateIgnoringOtherApps:YES];
854         [[windowController window] makeKeyAndOrderFront:self];
855     } else if (SetServerNameMsgID == msgid) {
856         NSString *name = [[NSString alloc] initWithData:data
857                                                encoding:NSUTF8StringEncoding];
858         [self setServerName:name];
859         [name release];
860     } else if (EnterFullscreenMsgID == msgid) {
861         [windowController enterFullscreen];
862     } else if (LeaveFullscreenMsgID == msgid) {
863         [windowController leaveFullscreen];
864     } else if (BuffersNotModifiedMsgID == msgid) {
865         [[windowController window] setDocumentEdited:NO];
866     } else if (BuffersModifiedMsgID == msgid) {
867         [[windowController window] setDocumentEdited:YES];
868     } else if (SetPreEditPositionMsgID == msgid) {
869         const int *dim = (const int*)[data bytes];
870         [[windowController textView] setPreEditRow:dim[0] column:dim[1]];
871     } else {
872         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
873     }
877 #define MM_DEBUG_DRAWING 0
879 - (void)performBatchDrawWithData:(NSData *)data
881     // TODO!  Move to window controller.
882     MMTextStorage *textStorage = [windowController textStorage];
883     MMTextView *textView = [windowController textView];
884     if (!(textStorage && textView))
885         return;
887     const void *bytes = [data bytes];
888     const void *end = bytes + [data length];
890 #if MM_DEBUG_DRAWING
891     NSLog(@"====> BEGIN %s", _cmd);
892 #endif
893     [textStorage beginEditing];
895     // TODO: Sanity check input
897     while (bytes < end) {
898         int type = *((int*)bytes);  bytes += sizeof(int);
900         if (ClearAllDrawType == type) {
901 #if MM_DEBUG_DRAWING
902             NSLog(@"   Clear all");
903 #endif
904             [textStorage clearAll];
905         } else if (ClearBlockDrawType == type) {
906             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
907             int row1 = *((int*)bytes);  bytes += sizeof(int);
908             int col1 = *((int*)bytes);  bytes += sizeof(int);
909             int row2 = *((int*)bytes);  bytes += sizeof(int);
910             int col2 = *((int*)bytes);  bytes += sizeof(int);
912 #if MM_DEBUG_DRAWING
913             NSLog(@"   Clear block (%d,%d) -> (%d,%d)", row1, col1,
914                     row2,col2);
915 #endif
916             [textStorage clearBlockFromRow:row1 column:col1
917                     toRow:row2 column:col2
918                     color:[NSColor colorWithArgbInt:color]];
919         } else if (DeleteLinesDrawType == type) {
920             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
921             int row = *((int*)bytes);  bytes += sizeof(int);
922             int count = *((int*)bytes);  bytes += sizeof(int);
923             int bot = *((int*)bytes);  bytes += sizeof(int);
924             int left = *((int*)bytes);  bytes += sizeof(int);
925             int right = *((int*)bytes);  bytes += sizeof(int);
927 #if MM_DEBUG_DRAWING
928             NSLog(@"   Delete %d line(s) from %d", count, row);
929 #endif
930             [textStorage deleteLinesFromRow:row lineCount:count
931                     scrollBottom:bot left:left right:right
932                            color:[NSColor colorWithArgbInt:color]];
933         } else if (DrawStringDrawType == type) {
934             int bg = *((int*)bytes);  bytes += sizeof(int);
935             int fg = *((int*)bytes);  bytes += sizeof(int);
936             int sp = *((int*)bytes);  bytes += sizeof(int);
937             int row = *((int*)bytes);  bytes += sizeof(int);
938             int col = *((int*)bytes);  bytes += sizeof(int);
939             int cells = *((int*)bytes);  bytes += sizeof(int);
940             int flags = *((int*)bytes);  bytes += sizeof(int);
941             int len = *((int*)bytes);  bytes += sizeof(int);
942             NSString *string = [[NSString alloc]
943                     initWithBytesNoCopy:(void*)bytes
944                                  length:len
945                                encoding:NSUTF8StringEncoding
946                            freeWhenDone:NO];
947             bytes += len;
949 #if MM_DEBUG_DRAWING
950             NSLog(@"   Draw string at (%d,%d) length=%d flags=%d fg=0x%x "
951                     "bg=0x%x sp=0x%x (%@)", row, col, len, flags, fg, bg, sp,
952                     len > 0 ? [string substringToIndex:1] : @"");
953 #endif
954             // NOTE: If this is a call to draw the (block) cursor, then cancel
955             // any previous request to draw the insertion point, or it might
956             // get drawn as well.
957             if (flags & DRAW_CURSOR) {
958                 [textView setShouldDrawInsertionPoint:NO];
959                 //NSColor *color = [NSColor colorWithRgbInt:bg];
960                 //[textView drawInsertionPointAtRow:row column:col
961                 //                            shape:MMInsertionPointBlock
962                 //                            color:color];
963             }
965             [textStorage drawString:string
966                               atRow:row column:col cells:cells
967                           withFlags:flags
968                     foregroundColor:[NSColor colorWithRgbInt:fg]
969                     backgroundColor:[NSColor colorWithArgbInt:bg]
970                        specialColor:[NSColor colorWithRgbInt:sp]];
972             [string release];
973         } else if (InsertLinesDrawType == type) {
974             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
975             int row = *((int*)bytes);  bytes += sizeof(int);
976             int count = *((int*)bytes);  bytes += sizeof(int);
977             int bot = *((int*)bytes);  bytes += sizeof(int);
978             int left = *((int*)bytes);  bytes += sizeof(int);
979             int right = *((int*)bytes);  bytes += sizeof(int);
981 #if MM_DEBUG_DRAWING
982             NSLog(@"   Insert %d line(s) at row %d", count, row);
983 #endif
984             [textStorage insertLinesAtRow:row lineCount:count
985                              scrollBottom:bot left:left right:right
986                                     color:[NSColor colorWithArgbInt:color]];
987         } else if (DrawCursorDrawType == type) {
988             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
989             int row = *((int*)bytes);  bytes += sizeof(int);
990             int col = *((int*)bytes);  bytes += sizeof(int);
991             int shape = *((int*)bytes);  bytes += sizeof(int);
992             int percent = *((int*)bytes);  bytes += sizeof(int);
994 #if MM_DEBUG_DRAWING
995             NSLog(@"   Draw cursor at (%d,%d)", row, col);
996 #endif
997             [textView drawInsertionPointAtRow:row column:col shape:shape
998                                      fraction:percent
999                                         color:[NSColor colorWithRgbInt:color]];
1000         } else {
1001             NSLog(@"WARNING: Unknown draw type (type=%d)", type);
1002         }
1003     }
1005     [textStorage endEditing];
1007     // NOTE: During resizing, Cocoa only sends draw messages before Vim's rows
1008     // and columns are changed (due to ipc delays). Force a redraw here.
1009     [[windowController vimView] displayIfNeeded];
1011 #if MM_DEBUG_DRAWING
1012     NSLog(@"<==== END   %s", _cmd);
1013 #endif
1016 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
1017                 context:(void *)context
1019     NSString *string = (code == NSOKButton) ? [panel filename] : nil;
1020     @try {
1021         [backendProxy setDialogReturn:string];
1022     }
1023     @catch (NSException *e) {
1024         NSLog(@"Exception caught in %s %@", _cmd, e);
1025     }
1028 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context
1030     NSArray *ret = nil;
1032     code = code - NSAlertFirstButtonReturn + 1;
1034     if ([alert isKindOfClass:[MMAlert class]] && [alert textField]) {
1035         ret = [NSArray arrayWithObjects:[NSNumber numberWithInt:code],
1036             [[alert textField] stringValue], nil];
1037     } else {
1038         ret = [NSArray arrayWithObject:[NSNumber numberWithInt:code]];
1039     }
1041     @try {
1042         [backendProxy setDialogReturn:ret];
1043     }
1044     @catch (NSException *e) {
1045         NSLog(@"Exception caught in %s %@", _cmd, e);
1046     }
1049 - (NSMenuItem *)recurseMenuItemForTag:(int)tag rootMenu:(NSMenu *)root
1051     if (root) {
1052         NSMenuItem *item = [root itemWithTag:tag];
1053         if (item) {
1054             lastMenuSearched = root;
1055             return item;
1056         }
1058         NSArray *items = [root itemArray];
1059         unsigned i, count = [items count];
1060         for (i = 0; i < count; ++i) {
1061             item = [items objectAtIndex:i];
1062             if ([item hasSubmenu]) {
1063                 item = [self recurseMenuItemForTag:tag
1064                                           rootMenu:[item submenu]];
1065                 if (item) {
1066                     lastMenuSearched = [item submenu];
1067                     return item;
1068                 }
1069             }
1070         }
1071     }
1073     return nil;
1076 - (NSMenuItem *)menuItemForTag:(int)tag
1078     // First search the same menu that was search last time this method was
1079     // called.  Since this method is often called for each menu item in a
1080     // menu this can significantly improve search times.
1081     if (lastMenuSearched) {
1082         NSMenuItem *item = [self recurseMenuItemForTag:tag
1083                                               rootMenu:lastMenuSearched];
1084         if (item) return item;
1085     }
1087     // Search the main menu.
1088     int i, count = [mainMenuItems count];
1089     for (i = 0; i < count; ++i) {
1090         NSMenuItem *item = [mainMenuItems objectAtIndex:i];
1091         if ([item tag] == tag) return item;
1092         item = [self recurseMenuItemForTag:tag rootMenu:[item submenu]];
1093         if (item) {
1094             lastMenuSearched = [item submenu];
1095             return item;
1096         }
1097     }
1099     // Search the popup menus.
1100     count = [popupMenuItems count];
1101     for (i = 0; i < count; ++i) {
1102         NSMenuItem *item = [popupMenuItems objectAtIndex:i];
1103         if ([item tag] == tag) return item;
1104         item = [self recurseMenuItemForTag:tag rootMenu:[item submenu]];
1105         if (item) {
1106             lastMenuSearched = [item submenu];
1107             return item;
1108         }
1109     }
1111     return nil;
1114 - (NSMenu *)menuForTag:(int)tag
1116     return [[self menuItemForTag:tag] submenu];
1119 - (NSMenu *)topLevelMenuForTitle:(NSString *)title
1121     // Search only the top-level menus.
1123     unsigned i, count = [popupMenuItems count];
1124     for (i = 0; i < count; ++i) {
1125         NSMenuItem *item = [popupMenuItems objectAtIndex:i];
1126         if ([title isEqual:[item title]])
1127             return [item submenu];
1128     }
1130     count = [mainMenuItems count];
1131     for (i = 0; i < count; ++i) {
1132         NSMenuItem *item = [mainMenuItems objectAtIndex:i];
1133         if ([title isEqual:[item title]])
1134             return [item submenu];
1135     }
1137     return nil;
1140 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
1141                atIndex:(int)idx
1143     NSMenu *parent = [self menuForTag:parentTag];
1144     NSMenuItem *item = [[NSMenuItem alloc] init];
1145     NSMenu *menu = [[NSMenu alloc] initWithTitle:title];
1147     [menu setAutoenablesItems:NO];
1148     [item setTag:tag];
1149     [item setTitle:title];
1150     [item setSubmenu:menu];
1152     if (parent) {
1153         if ([parent numberOfItems] <= idx) {
1154             [parent addItem:item];
1155         } else {
1156             [parent insertItem:item atIndex:idx];
1157         }
1158     } else {
1159         NSMutableArray *items = (MenuPopupType == parentTag)
1160             ? popupMenuItems : mainMenuItems;
1161         if ([items count] <= idx) {
1162             [items addObject:item];
1163         } else {
1164             [items insertObject:item atIndex:idx];
1165         }
1167         shouldUpdateMainMenu = (MenuPopupType != parentTag);
1168     }
1170     [item release];
1171     [menu release];
1174 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
1175                      title:(NSString *)title tip:(NSString *)tip
1176              keyEquivalent:(int)key modifiers:(int)mask
1177                     action:(NSString *)action atIndex:(int)idx
1179     if (parent) {
1180         NSMenuItem *item = nil;
1181         if (!title || ([title hasPrefix:@"-"] && [title hasSuffix:@"-"])) {
1182             item = [NSMenuItem separatorItem];
1183         } else {
1184             item = [[[NSMenuItem alloc] init] autorelease];
1185             [item setTitle:title];
1186             // TODO: Check that 'action' is a valid action (nothing will happen
1187             // if it isn't, but it would be nice with a warning).
1188             if (action) [item setAction:NSSelectorFromString(action)];
1189             else        [item setAction:@selector(vimMenuItemAction:)];
1190             if (tip) [item setToolTip:tip];
1192             if (key != 0) {
1193                 NSString *keyString =
1194                     [NSString stringWithFormat:@"%C", key];
1195                 [item setKeyEquivalent:keyString];
1196                 [item setKeyEquivalentModifierMask:mask];
1197             }
1198         }
1200         // NOTE!  The tag is used to idenfity which menu items were
1201         // added by Vim (tag != 0) and which were added by the AppKit
1202         // (tag == 0).
1203         [item setTag:tag];
1205         if ([parent numberOfItems] <= idx) {
1206             [parent addItem:item];
1207         } else {
1208             [parent insertItem:item atIndex:idx];
1209         }
1210     } else {
1211         NSLog(@"WARNING: Menu item '%@' (tag=%d) has no parent.", title, tag);
1212     }
1215 - (void)updateMainMenu
1217     NSMenu *mainMenu = [NSApp mainMenu];
1219     // Stop NSApp from updating the Window menu.
1220     [NSApp setWindowsMenu:nil];
1222     // Remove all menus from main menu (except the MacVim menu).
1223     int i, count = [mainMenu numberOfItems];
1224     for (i = count-1; i > 0; --i) {
1225         [mainMenu removeItemAtIndex:i];
1226     }
1228     // Add menus from 'mainMenuItems' to main menu.
1229     count = [mainMenuItems count];
1230     for (i = 0; i < count; ++i) {
1231         [mainMenu addItem:[mainMenuItems objectAtIndex:i]];
1232     }
1234     // Set the new Window menu.
1235     // TODO!  Need to look for 'Window' in all localized languages.
1236     NSMenu *windowMenu = [[mainMenu itemWithTitle:@"Window"] submenu];
1237     if (windowMenu) {
1238         // Remove all AppKit owned menu items (tag == 0); they will be added
1239         // again when setWindowsMenu: is called.
1240         count = [windowMenu numberOfItems];
1241         for (i = count-1; i >= 0; --i) {
1242             NSMenuItem *item = [windowMenu itemAtIndex:i];
1243             if (![item tag]) {
1244                 [windowMenu removeItem:item];
1245             }
1246         }
1248         [NSApp setWindowsMenu:windowMenu];
1249     }
1251     shouldUpdateMainMenu = NO;
1254 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index
1256     if (!toolbar) return nil;
1258     NSArray *items = [toolbar items];
1259     int i, count = [items count];
1260     for (i = 0; i < count; ++i) {
1261         NSToolbarItem *item = [items objectAtIndex:i];
1262         if ([item tag] == tag) {
1263             if (index) *index = i;
1264             return item;
1265         }
1266     }
1268     return nil;
1271 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
1272         toolTip:(NSString *)tip icon:(NSString *)icon
1274     // If the item corresponds to a separator then do nothing, since it is
1275     // already defined by Cocoa.
1276     if (!title || [title isEqual:NSToolbarSeparatorItemIdentifier]
1277                || [title isEqual:NSToolbarSpaceItemIdentifier]
1278                || [title isEqual:NSToolbarFlexibleSpaceItemIdentifier])
1279         return;
1281     NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:title];
1282     [item setTag:tag];
1283     [item setLabel:title];
1284     [item setToolTip:tip];
1285     [item setAction:@selector(vimMenuItemAction:)];
1286     [item setAutovalidates:NO];
1288     NSImage *img = [NSImage imageNamed:icon];
1289     if (!img) {
1290         NSLog(@"WARNING: Could not find image with name '%@' to use as toolbar"
1291                " image for identifier '%@';"
1292                " using default toolbar icon '%@' instead.",
1293                icon, title, MMDefaultToolbarImageName);
1295         img = [NSImage imageNamed:MMDefaultToolbarImageName];
1296     }
1298     [item setImage:img];
1300     [toolbarItemDict setObject:item forKey:title];
1302     [item release];
1305 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label tip:(NSString
1306                    *)tip icon:(NSString *)icon atIndex:(int)idx
1308     if (!toolbar) return;
1310     // Check for separator items.
1311     if (!label) {
1312         label = NSToolbarSeparatorItemIdentifier;
1313     } else if ([label length] >= 2 && [label hasPrefix:@"-"]
1314                                    && [label hasSuffix:@"-"]) {
1315         // The label begins and ends with '-'; decided which kind of separator
1316         // item it is by looking at the prefix.
1317         if ([label hasPrefix:@"-space"]) {
1318             label = NSToolbarSpaceItemIdentifier;
1319         } else if ([label hasPrefix:@"-flexspace"]) {
1320             label = NSToolbarFlexibleSpaceItemIdentifier;
1321         } else {
1322             label = NSToolbarSeparatorItemIdentifier;
1323         }
1324     }
1326     [self addToolbarItemToDictionaryWithTag:tag label:label toolTip:tip
1327                                        icon:icon];
1329     int maxIdx = [[toolbar items] count];
1330     if (maxIdx < idx) idx = maxIdx;
1332     [toolbar insertItemWithItemIdentifier:label atIndex:idx];
1335 - (void)connectionDidDie:(NSNotification *)notification
1337     //NSLog(@"%@ %s%@", [self className], _cmd, notification);
1339     [self cleanup];
1341     // NOTE!  This causes the call to removeVimController: to be delayed.
1342     [[NSApp delegate]
1343             performSelectorOnMainThread:@selector(removeVimController:)
1344                              withObject:self waitUntilDone:NO];
1347 - (NSString *)description
1349     return [NSString stringWithFormat:@"%@ : isInitialized=%d inProcessCommandQueue=%d mainMenuItems=%@ popupMenuItems=%@ toolbar=%@", [self className], isInitialized, inProcessCommandQueue, mainMenuItems, popupMenuItems, toolbar];
1352 #if MM_RESEND_LAST_FAILURE
1353 - (void)resendTimerFired:(NSTimer *)timer
1355     int msgid = resendMsgid;
1356     NSData *data = nil;
1358     [resendTimer release];
1359     resendTimer = nil;
1361     if (!isInitialized)
1362         return;
1364     if (resendData)
1365         data = [resendData copy];
1367     //NSLog(@"Resending message: %s", MessageStrings[msgid]);
1368     [self sendMessage:msgid data:data];
1370 #endif
1372 @end // MMVimController (Private)
1376 @implementation MMAlert
1377 - (void)dealloc
1379     [textField release];
1380     [super dealloc];
1383 - (void)setTextFieldString:(NSString *)textFieldString
1385     [textField release];
1386     textField = [[NSTextField alloc] init];
1387     [textField setStringValue:textFieldString];
1390 - (NSTextField *)textField
1392     return textField;
1395 - (void)setInformativeText:(NSString *)text
1397     if (textField) {
1398         // HACK! Add some space for the text field.
1399         [super setInformativeText:[text stringByAppendingString:@"\n\n\n"]];
1400     } else {
1401         [super setInformativeText:text];
1402     }
1405 - (void)beginSheetModalForWindow:(NSWindow *)window
1406                    modalDelegate:(id)delegate
1407                   didEndSelector:(SEL)didEndSelector
1408                      contextInfo:(void *)contextInfo
1410     [super beginSheetModalForWindow:window
1411                       modalDelegate:delegate
1412                      didEndSelector:didEndSelector
1413                         contextInfo:contextInfo];
1415     // HACK! Place the input text field at the bottom of the informative text
1416     // (which has been made a bit larger by adding newline characters).
1417     NSView *contentView = [[self window] contentView];
1418     NSRect rect = [contentView frame];
1419     rect.origin.y = rect.size.height;
1421     NSArray *subviews = [contentView subviews];
1422     unsigned i, count = [subviews count];
1423     for (i = 0; i < count; ++i) {
1424         NSView *view = [subviews objectAtIndex:i];
1425         if ([view isKindOfClass:[NSTextField class]]
1426                 && [view frame].origin.y < rect.origin.y) {
1427             // NOTE: The informative text field is the lowest NSTextField in
1428             // the alert dialog.
1429             rect = [view frame];
1430         }
1431     }
1433     rect.size.height = MMAlertTextFieldHeight;
1434     [textField setFrame:rect];
1435     [contentView addSubview:textField];
1436     [textField becomeFirstResponder];
1439 @end // MMAlert