Add eval & add input methods to MacVim, apply to file open
[MacVim/jjgod.git] / src / MacVim / MMVimController.m
blobb06b78b7e3367b2e8d1bed45ee23b1f02cf8d10b
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 *)recurseMenuItemForTag:(int)tag rootMenu:(NSMenu *)root;
53 - (NSMenuItem *)menuItemForTag:(int)tag;
54 - (NSMenu *)menuForTag:(int)tag;
55 - (NSMenu *)topLevelMenuForTitle:(NSString *)title;
56 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
57                atIndex:(int)idx;
58 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
59                      title:(NSString *)title tip:(NSString *)tip
60              keyEquivalent:(int)key modifiers:(int)mask
61                     action:(NSString *)action atIndex:(int)idx;
62 - (void)updateMainMenu;
63 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index;
64 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
65         toolTip:(NSString *)tip icon:(NSString *)icon;
66 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label
67                           tip:(NSString *)tip icon:(NSString *)icon
68                       atIndex:(int)idx;
69 - (void)connectionDidDie:(NSNotification *)notification;
70 #if MM_RESEND_LAST_FAILURE
71 - (void)resendTimerFired:(NSTimer *)timer;
72 #endif
73 @end
77 // TODO: Move to separate file
78 @interface NSColor (MMProtocol)
79 + (NSColor *)colorWithRgbInt:(unsigned)rgb;
80 + (NSColor *)colorWithArgbInt:(unsigned)argb;
81 @end
86 @implementation MMVimController
88 - (id)initWithBackend:(id)backend pid:(int)processIdentifier
90     if ((self = [super init])) {
91         windowController =
92             [[MMWindowController alloc] initWithVimController:self];
93         backendProxy = [backend retain];
94         sendQueue = [NSMutableArray new];
95         mainMenuItems = [[NSMutableArray alloc] init];
96         popupMenuItems = [[NSMutableArray alloc] init];
97         toolbarItemDict = [[NSMutableDictionary alloc] init];
98         pid = processIdentifier;
100         NSConnection *connection = [backendProxy connectionForProxy];
102         // TODO: Check that this will not set the timeout for the root proxy
103         // (in MMAppController).
104         [connection setRequestTimeout:MMBackendProxyRequestTimeout];
106         [[NSNotificationCenter defaultCenter] addObserver:self
107                 selector:@selector(connectionDidDie:)
108                     name:NSConnectionDidDieNotification object:connection];
111         NSWindow *win = [windowController window];
113         [[NSNotificationCenter defaultCenter]
114                 addObserver:self
115                    selector:@selector(windowDidBecomeMain:)
116                        name:NSWindowDidBecomeMainNotification
117                      object:win];
119         isInitialized = YES;
120     }
122     return self;
125 - (void)dealloc
127     //NSLog(@"%@ %s", [self className], _cmd);
128     isInitialized = NO;
130 #if MM_RESEND_LAST_FAILURE
131     [resendData release];  resendData = nil;
132 #endif
134     [serverName release];  serverName = nil;
135     [backendProxy release];  backendProxy = nil;
136     [sendQueue release];  sendQueue = nil;
138     [toolbarItemDict release];  toolbarItemDict = nil;
139     [toolbar release];  toolbar = nil;
140     [popupMenuItems release];  popupMenuItems = nil;
141     [mainMenuItems release];  mainMenuItems = nil;
142     [windowController release];  windowController = nil;
144     [super dealloc];
147 - (MMWindowController *)windowController
149     return windowController;
152 - (void)setServerName:(NSString *)name
154     if (name != serverName) {
155         [serverName release];
156         serverName = [name copy];
157     }
160 - (NSString *)serverName
162     return serverName;
165 - (int)pid
167     return pid;
170 - (void)dropFiles:(NSArray *)filenames
172     int i, numberOfFiles = [filenames count];
173     NSMutableData *data = [NSMutableData data];
175     [data appendBytes:&numberOfFiles length:sizeof(int)];
177     for (i = 0; i < numberOfFiles; ++i) {
178         NSString *file = [filenames objectAtIndex:i];
179         int len = [file lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
181         if (len > 0) {
182             ++len;  // include NUL as well
183             [data appendBytes:&len length:sizeof(int)];
184             [data appendBytes:[file UTF8String] length:len];
185         }
186     }
188     [self sendMessage:DropFilesMsgID data:data];
191 - (void)dropString:(NSString *)string
193     int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
194     if (len > 0) {
195         NSMutableData *data = [NSMutableData data];
197         [data appendBytes:&len length:sizeof(int)];
198         [data appendBytes:[string UTF8String] length:len];
200         [self sendMessage:DropStringMsgID data:data];
201     }
204 - (void)sendMessage:(int)msgid data:(NSData *)data
206     //NSLog(@"sendMessage:%s (isInitialized=%d inProcessCommandQueue=%d)",
207     //        MessageStrings[msgid], isInitialized, inProcessCommandQueue);
209     if (!isInitialized) return;
211     if (inProcessCommandQueue) {
212         //NSLog(@"In process command queue; delaying message send.");
213         [sendQueue addObject:[NSNumber numberWithInt:msgid]];
214         if (data)
215             [sendQueue addObject:data];
216         else
217             [sendQueue addObject:[NSNull null]];
218         return;
219     }
221 #if MM_RESEND_LAST_FAILURE
222     if (resendTimer) {
223         //NSLog(@"cancelling scheduled resend of %s",
224         //        MessageStrings[resendMsgid]);
226         [resendTimer invalidate];
227         [resendTimer release];
228         resendTimer = nil;
229     }
231     if (resendData) {
232         [resendData release];
233         resendData = nil;
234     }
235 #endif
237     @try {
238         [backendProxy processInput:msgid data:data];
239     }
240     @catch (NSException *e) {
241         //NSLog(@"%@ %s Exception caught during DO call: %@",
242         //        [self className], _cmd, e);
243 #if MM_RESEND_LAST_FAILURE
244         //NSLog(@"%s failed, scheduling message %s for resend", _cmd,
245         //        MessageStrings[msgid]);
247         resendMsgid = msgid;
248         resendData = [data retain];
249         resendTimer = [NSTimer
250             scheduledTimerWithTimeInterval:MMResendInterval
251                                     target:self
252                                   selector:@selector(resendTimerFired:)
253                                   userInfo:nil
254                                    repeats:NO];
255         [resendTimer retain];
256 #endif
257     }
260 - (BOOL)sendMessageNow:(int)msgid data:(NSData *)data
261                timeout:(NSTimeInterval)timeout
263     if (!isInitialized || inProcessCommandQueue)
264         return NO;
266     if (timeout < 0) timeout = 0;
268     BOOL sendOk = YES;
269     NSConnection *conn = [backendProxy connectionForProxy];
270     NSTimeInterval oldTimeout = [conn requestTimeout];
272     [conn setRequestTimeout:timeout];
274     @try {
275         [backendProxy processInput:msgid data:data];
276     }
277     @catch (NSException *e) {
278         sendOk = NO;
279     }
280     @finally {
281         [conn setRequestTimeout:oldTimeout];
282     }
284     return sendOk;
287 - (void)addVimInput:(NSString *)string
289     // This is a very general method of adding input to the Vim process.  It is
290     // basically the same as calling remote_send() on the process (see
291     // ':h remote_send').
292     if (string) {
293         NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
294         [self sendMessage:AddInputMsgID data:data];
295     }
298 - (id)backendProxy
300     return backendProxy;
303 - (void)cleanup
305     //NSLog(@"%@ %s", [self className], _cmd);
306     if (!isInitialized) return;
308     isInitialized = NO;
309     [toolbar setDelegate:nil];
310     [[NSNotificationCenter defaultCenter] removeObserver:self];
311     [windowController cleanup];
314 - (oneway void)showSavePanelForDirectory:(in bycopy NSString *)dir
315                                    title:(in bycopy NSString *)title
316                                   saving:(int)saving
318     if (!isInitialized) return;
320     if (saving) {
321         [[NSSavePanel savePanel] beginSheetForDirectory:dir file:nil
322                 modalForWindow:[windowController window]
323                  modalDelegate:self
324                 didEndSelector:@selector(savePanelDidEnd:code:context:)
325                    contextInfo:NULL];
326     } else {
327         NSOpenPanel *panel = [NSOpenPanel openPanel];
328         [panel setAllowsMultipleSelection:NO];
329         [panel beginSheetForDirectory:dir file:nil types:nil
330                 modalForWindow:[windowController window]
331                  modalDelegate:self
332                 didEndSelector:@selector(savePanelDidEnd:code:context:)
333                    contextInfo:NULL];
334     }
337 - (oneway void)presentDialogWithStyle:(int)style
338                               message:(in bycopy NSString *)message
339                       informativeText:(in bycopy NSString *)text
340                          buttonTitles:(in bycopy NSArray *)buttonTitles
341                       textFieldString:(in bycopy NSString *)textFieldString
343     if (!(windowController && buttonTitles && [buttonTitles count])) return;
345     MMAlert *alert = [[MMAlert alloc] init];
347     // NOTE! This has to be done before setting the informative text.
348     if (textFieldString)
349         [alert setTextFieldString:textFieldString];
351     [alert setAlertStyle:style];
353     if (message) {
354         [alert setMessageText:message];
355     } else {
356         // If no message text is specified 'Alert' is used, which we don't
357         // want, so set an empty string as message text.
358         [alert setMessageText:@""];
359     }
361     if (text) {
362         [alert setInformativeText:text];
363     } else if (textFieldString) {
364         // Make sure there is always room for the input text field.
365         [alert setInformativeText:@""];
366     }
368     unsigned i, count = [buttonTitles count];
369     for (i = 0; i < count; ++i) {
370         NSString *title = [buttonTitles objectAtIndex:i];
371         // NOTE: The title of the button may contain the character '&' to
372         // indicate that the following letter should be the key equivalent
373         // associated with the button.  Extract this letter and lowercase it.
374         NSString *keyEquivalent = nil;
375         NSRange hotkeyRange = [title rangeOfString:@"&"];
376         if (NSNotFound != hotkeyRange.location) {
377             if ([title length] > NSMaxRange(hotkeyRange)) {
378                 NSRange keyEquivRange = NSMakeRange(hotkeyRange.location+1, 1);
379                 keyEquivalent = [[title substringWithRange:keyEquivRange]
380                     lowercaseString];
381             }
383             NSMutableString *string = [NSMutableString stringWithString:title];
384             [string deleteCharactersInRange:hotkeyRange];
385             title = string;
386         }
388         [alert addButtonWithTitle:title];
390         // Set key equivalent for the button, but only if NSAlert hasn't
391         // already done so.  (Check the documentation for
392         // - [NSAlert addButtonWithTitle:] to see what key equivalents are
393         // automatically assigned.)
394         NSButton *btn = [[alert buttons] lastObject];
395         if ([[btn keyEquivalent] length] == 0 && keyEquivalent) {
396             [btn setKeyEquivalent:keyEquivalent];
397         }
398     }
400     [alert beginSheetModalForWindow:[windowController window]
401                       modalDelegate:self
402                      didEndSelector:@selector(alertDidEnd:code:context:)
403                         contextInfo:NULL];
405     [alert release];
408 - (oneway void)processCommandQueue:(in bycopy NSArray *)queue
410     if (!isInitialized) return;
412     unsigned i, count = [queue count];
413     if (count % 2) {
414         NSLog(@"WARNING: Uneven number of components (%d) in flush queue "
415                 "message; ignoring this message.", count);
416         return;
417     }
419     inProcessCommandQueue = YES;
421     //NSLog(@"======== %s BEGIN ========", _cmd);
422     for (i = 0; i < count; i += 2) {
423         NSData *value = [queue objectAtIndex:i];
424         NSData *data = [queue objectAtIndex:i+1];
426         int msgid = *((int*)[value bytes]);
427         //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
429         [self handleMessage:msgid data:data];
430     }
431     //NSLog(@"======== %s  END  ========", _cmd);
433     if (shouldUpdateMainMenu) {
434         [self updateMainMenu];
435     }
437     [windowController processCommandQueueDidFinish];
439     inProcessCommandQueue = NO;
441     if ([sendQueue count] > 0) {
442         @try {
443             [backendProxy processInputAndData:sendQueue];
444         }
445         @catch (NSException *e) {
446             // Connection timed out, just ignore this.
447             //NSLog(@"WARNING! Connection timed out in %s", _cmd);
448         }
450         [sendQueue removeAllObjects];
451     }
454 - (void)windowDidBecomeMain:(NSNotification *)notification
456     if (isInitialized)
457         [self updateMainMenu];
460 - (NSToolbarItem *)toolbar:(NSToolbar *)theToolbar
461     itemForItemIdentifier:(NSString *)itemId
462     willBeInsertedIntoToolbar:(BOOL)flag
464     NSToolbarItem *item = [toolbarItemDict objectForKey:itemId];
465     if (!item) {
466         NSLog(@"WARNING:  No toolbar item with id '%@'", itemId);
467     }
469     return item;
472 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)theToolbar
474     return nil;
477 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)theToolbar
479     return nil;
482 @end // MMVimController
486 @implementation MMVimController (Private)
488 - (void)handleMessage:(int)msgid data:(NSData *)data
490     //NSLog(@"%@ %s", [self className], _cmd);
492     if (OpenVimWindowMsgID == msgid) {
493         [windowController openWindow];
494     } else if (BatchDrawMsgID == msgid) {
495         [self performBatchDrawWithData:data];
496     } else if (SelectTabMsgID == msgid) {
497 #if 0   // NOTE: Tab selection is done inside updateTabsWithData:.
498         const void *bytes = [data bytes];
499         int idx = *((int*)bytes);
500         //NSLog(@"Selecting tab with index %d", idx);
501         [windowController selectTabWithIndex:idx];
502 #endif
503     } else if (UpdateTabBarMsgID == msgid) {
504         [windowController updateTabsWithData:data];
505     } else if (ShowTabBarMsgID == msgid) {
506         [windowController showTabBar:YES];
507     } else if (HideTabBarMsgID == msgid) {
508         [windowController showTabBar:NO];
509     } else if (SetTextDimensionsMsgID == msgid) {
510         const void *bytes = [data bytes];
511         int rows = *((int*)bytes);  bytes += sizeof(int);
512         int cols = *((int*)bytes);  bytes += sizeof(int);
514         [windowController setTextDimensionsWithRows:rows columns:cols];
515     } else if (SetWindowTitleMsgID == msgid) {
516         const void *bytes = [data bytes];
517         int len = *((int*)bytes);  bytes += sizeof(int);
519         NSString *string = [[NSString alloc] initWithBytes:(void*)bytes
520                 length:len encoding:NSUTF8StringEncoding];
522         [[windowController window] setTitle:string];
524         [string release];
525     } else if (AddMenuMsgID == msgid) {
526         NSString *title = nil;
527         const void *bytes = [data bytes];
528         int tag = *((int*)bytes);  bytes += sizeof(int);
529         int parentTag = *((int*)bytes);  bytes += sizeof(int);
530         int len = *((int*)bytes);  bytes += sizeof(int);
531         if (len > 0) {
532             title = [[NSString alloc] initWithBytes:(void*)bytes length:len
533                                            encoding:NSUTF8StringEncoding];
534             bytes += len;
535         }
536         int idx = *((int*)bytes);  bytes += sizeof(int);
538         if (MenuToolbarType == parentTag) {
539             if (!toolbar) {
540                 // NOTE! Each toolbar must have a unique identifier, else each
541                 // window will have the same toolbar.
542                 NSString *ident = [NSString stringWithFormat:@"%d.%d",
543                          (int)self, tag];
544                 toolbar = [[NSToolbar alloc] initWithIdentifier:ident];
546                 [toolbar setShowsBaselineSeparator:NO];
547                 [toolbar setDelegate:self];
548                 [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
549                 [toolbar setSizeMode:NSToolbarSizeModeSmall];
551                 NSWindow *win = [windowController window];
552                 [win setToolbar:toolbar];
554                 // HACK! Redirect the pill button so that we can ask Vim to
555                 // hide the toolbar.
556                 NSButton *pillButton = [win
557                     standardWindowButton:NSWindowToolbarButton];
558                 if (pillButton) {
559                     [pillButton setAction:@selector(toggleToolbar:)];
560                     [pillButton setTarget:windowController];
561                 }
562             }
563         } else if (title) {
564             [self addMenuWithTag:tag parent:parentTag title:title atIndex:idx];
565         }
567         [title release];
568     } else if (AddMenuItemMsgID == msgid) {
569         NSString *title = nil, *tip = nil, *icon = nil, *action = nil;
570         const void *bytes = [data bytes];
571         int tag = *((int*)bytes);  bytes += sizeof(int);
572         int parentTag = *((int*)bytes);  bytes += sizeof(int);
573         int namelen = *((int*)bytes);  bytes += sizeof(int);
574         if (namelen > 0) {
575             title = [[NSString alloc] initWithBytes:(void*)bytes length:namelen
576                                            encoding:NSUTF8StringEncoding];
577             bytes += namelen;
578         }
579         int tiplen = *((int*)bytes);  bytes += sizeof(int);
580         if (tiplen > 0) {
581             tip = [[NSString alloc] initWithBytes:(void*)bytes length:tiplen
582                                            encoding:NSUTF8StringEncoding];
583             bytes += tiplen;
584         }
585         int iconlen = *((int*)bytes);  bytes += sizeof(int);
586         if (iconlen > 0) {
587             icon = [[NSString alloc] initWithBytes:(void*)bytes length:iconlen
588                                            encoding:NSUTF8StringEncoding];
589             bytes += iconlen;
590         }
591         int actionlen = *((int*)bytes);  bytes += sizeof(int);
592         if (actionlen > 0) {
593             action = [[NSString alloc] initWithBytes:(void*)bytes
594                                               length:actionlen
595                                             encoding:NSUTF8StringEncoding];
596             bytes += actionlen;
597         }
598         int idx = *((int*)bytes);  bytes += sizeof(int);
599         if (idx < 0) idx = 0;
600         int key = *((int*)bytes);  bytes += sizeof(int);
601         int mask = *((int*)bytes);  bytes += sizeof(int);
603         NSString *ident = [NSString stringWithFormat:@"%d.%d",
604                 (int)self, parentTag];
605         if (toolbar && [[toolbar identifier] isEqual:ident]) {
606             [self addToolbarItemWithTag:tag label:title tip:tip icon:icon
607                                 atIndex:idx];
608         } else {
609             NSMenu *parent = [self menuForTag:parentTag];
610             [self addMenuItemWithTag:tag parent:parent title:title tip:tip
611                        keyEquivalent:key modifiers:mask action:action
612                              atIndex:idx];
613         }
615         [title release];
616         [tip release];
617         [icon release];
618         [action release];
619     } else if (RemoveMenuItemMsgID == msgid) {
620         const void *bytes = [data bytes];
621         int tag = *((int*)bytes);  bytes += sizeof(int);
623         id item;
624         int idx;
625         if ((item = [self toolbarItemForTag:tag index:&idx])) {
626             [toolbar removeItemAtIndex:idx];
627         } else if ((item = [self menuItemForTag:tag])) {
628             [item retain];
630             if ([item menu] == [NSApp mainMenu] || ![item menu]) {
631                 // NOTE: To be on the safe side we try to remove the item from
632                 // both arrays (it is ok to call removeObject: even if an array
633                 // does not contain the object to remove).
634                 [mainMenuItems removeObject:item];
635                 [popupMenuItems removeObject:item];
636             }
638             if ([item menu])
639                 [[item menu] removeItem:item];
641             [item release];
642         }
644         // Reset cached menu, just to be on the safe side.
645         lastMenuSearched = nil;
646     } else if (EnableMenuItemMsgID == msgid) {
647         const void *bytes = [data bytes];
648         int tag = *((int*)bytes);  bytes += sizeof(int);
649         int state = *((int*)bytes);  bytes += sizeof(int);
651         id item = [self toolbarItemForTag:tag index:NULL];
652         if (!item)
653             item = [self menuItemForTag:tag];
655         [item setEnabled:state];
656     } else if (ShowToolbarMsgID == msgid) {
657         const void *bytes = [data bytes];
658         int enable = *((int*)bytes);  bytes += sizeof(int);
659         int flags = *((int*)bytes);  bytes += sizeof(int);
661         int mode = NSToolbarDisplayModeDefault;
662         if (flags & ToolbarLabelFlag) {
663             mode = flags & ToolbarIconFlag ? NSToolbarDisplayModeIconAndLabel
664                     : NSToolbarDisplayModeLabelOnly;
665         } else if (flags & ToolbarIconFlag) {
666             mode = NSToolbarDisplayModeIconOnly;
667         }
669         int size = flags & ToolbarSizeRegularFlag ? NSToolbarSizeModeRegular
670                 : NSToolbarSizeModeSmall;
672         [windowController showToolbar:enable size:size mode:mode];
673     } else if (CreateScrollbarMsgID == msgid) {
674         const void *bytes = [data bytes];
675         long ident = *((long*)bytes);  bytes += sizeof(long);
676         int type = *((int*)bytes);  bytes += sizeof(int);
678         [windowController createScrollbarWithIdentifier:ident type:type];
679     } else if (DestroyScrollbarMsgID == msgid) {
680         const void *bytes = [data bytes];
681         long ident = *((long*)bytes);  bytes += sizeof(long);
683         [windowController destroyScrollbarWithIdentifier:ident];
684     } else if (ShowScrollbarMsgID == msgid) {
685         const void *bytes = [data bytes];
686         long ident = *((long*)bytes);  bytes += sizeof(long);
687         int visible = *((int*)bytes);  bytes += sizeof(int);
689         [windowController showScrollbarWithIdentifier:ident state:visible];
690     } else if (SetScrollbarPositionMsgID == msgid) {
691         const void *bytes = [data bytes];
692         long ident = *((long*)bytes);  bytes += sizeof(long);
693         int pos = *((int*)bytes);  bytes += sizeof(int);
694         int len = *((int*)bytes);  bytes += sizeof(int);
696         [windowController setScrollbarPosition:pos length:len
697                                     identifier:ident];
698     } else if (SetScrollbarThumbMsgID == msgid) {
699         const void *bytes = [data bytes];
700         long ident = *((long*)bytes);  bytes += sizeof(long);
701         float val = *((float*)bytes);  bytes += sizeof(float);
702         float prop = *((float*)bytes);  bytes += sizeof(float);
704         [windowController setScrollbarThumbValue:val proportion:prop
705                                       identifier:ident];
706     } else if (SetFontMsgID == msgid) {
707         const void *bytes = [data bytes];
708         float size = *((float*)bytes);  bytes += sizeof(float);
709         int len = *((int*)bytes);  bytes += sizeof(int);
710         NSString *name = [[NSString alloc]
711                 initWithBytes:(void*)bytes length:len
712                      encoding:NSUTF8StringEncoding];
713         NSFont *font = [NSFont fontWithName:name size:size];
715         if (font)
716             [windowController setFont:font];
718         [name release];
719     } else if (SetDefaultColorsMsgID == msgid) {
720         const void *bytes = [data bytes];
721         unsigned bg = *((unsigned*)bytes);  bytes += sizeof(unsigned);
722         unsigned fg = *((unsigned*)bytes);  bytes += sizeof(unsigned);
723         NSColor *back = [NSColor colorWithArgbInt:bg];
724         NSColor *fore = [NSColor colorWithRgbInt:fg];
726         [windowController setDefaultColorsBackground:back foreground:fore];
727     } else if (ExecuteActionMsgID == msgid) {
728         const void *bytes = [data bytes];
729         int len = *((int*)bytes);  bytes += sizeof(int);
730         NSString *actionName = [[NSString alloc]
731                 initWithBytesNoCopy:(void*)bytes
732                              length:len
733                            encoding:NSUTF8StringEncoding
734                        freeWhenDone:NO];
736         SEL sel = NSSelectorFromString(actionName);
737         [NSApp sendAction:sel to:nil from:self];
739         [actionName release];
740     } else if (ShowPopupMenuMsgID == msgid) {
741         const void *bytes = [data bytes];
742         int row = *((int*)bytes);  bytes += sizeof(int);
743         int col = *((int*)bytes);  bytes += sizeof(int);
744         int len = *((int*)bytes);  bytes += sizeof(int);
745         NSString *title = [[NSString alloc]
746                 initWithBytesNoCopy:(void*)bytes
747                              length:len
748                            encoding:NSUTF8StringEncoding
749                        freeWhenDone:NO];
751         NSMenu *menu = [self topLevelMenuForTitle:title];
752         if (menu) {
753             [windowController popupMenu:menu atRow:row column:col];
754         } else {
755             NSLog(@"WARNING: Cannot popup menu with title %@; no such menu.",
756                     title);
757         }
759         [title release];
760     } else if (SetMouseShapeMsgID == msgid) {
761         const void *bytes = [data bytes];
762         int shape = *((int*)bytes);  bytes += sizeof(int);
764         [windowController setMouseShape:shape];
765     } else if (AdjustLinespaceMsgID == msgid) {
766         const void *bytes = [data bytes];
767         int linespace = *((int*)bytes);  bytes += sizeof(int);
769         [windowController adjustLinespace:linespace];
770     } else if (ActivateMsgID == msgid) {
771         //NSLog(@"ActivateMsgID");
772         [NSApp activateIgnoringOtherApps:YES];
773         [[windowController window] makeKeyAndOrderFront:self];
774     } else if (SetServerNameMsgID == msgid) {
775         NSString *name = [[NSString alloc] initWithData:data
776                                                encoding:NSUTF8StringEncoding];
777         [self setServerName:name];
778         [name release];
779     } else if (EnterFullscreenMsgID == msgid) {
780         [windowController enterFullscreen];
781     } else if (LeaveFullscreenMsgID == msgid) {
782         [windowController leaveFullscreen];
783     } else if (BuffersNotModifiedMsgID == msgid) {
784         [[windowController window] setDocumentEdited:NO];
785     } else if (BuffersModifiedMsgID == msgid) {
786         [[windowController window] setDocumentEdited:YES];
787     } else {
788         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
789     }
793 #define MM_DEBUG_DRAWING 0
795 - (void)performBatchDrawWithData:(NSData *)data
797     // TODO!  Move to window controller.
798     MMTextStorage *textStorage = [windowController textStorage];
799     MMTextView *textView = [windowController textView];
800     if (!(textStorage && textView))
801         return;
803     const void *bytes = [data bytes];
804     const void *end = bytes + [data length];
806 #if MM_DEBUG_DRAWING
807     NSLog(@"====> BEGIN %s", _cmd);
808 #endif
809     [textStorage beginEditing];
811     // TODO: Sanity check input
813     while (bytes < end) {
814         int type = *((int*)bytes);  bytes += sizeof(int);
816         if (ClearAllDrawType == type) {
817 #if MM_DEBUG_DRAWING
818             NSLog(@"   Clear all");
819 #endif
820             [textStorage clearAll];
821         } else if (ClearBlockDrawType == type) {
822             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
823             int row1 = *((int*)bytes);  bytes += sizeof(int);
824             int col1 = *((int*)bytes);  bytes += sizeof(int);
825             int row2 = *((int*)bytes);  bytes += sizeof(int);
826             int col2 = *((int*)bytes);  bytes += sizeof(int);
828 #if MM_DEBUG_DRAWING
829             NSLog(@"   Clear block (%d,%d) -> (%d,%d)", row1, col1,
830                     row2,col2);
831 #endif
832             [textStorage clearBlockFromRow:row1 column:col1
833                     toRow:row2 column:col2
834                     color:[NSColor colorWithArgbInt:color]];
835         } else if (DeleteLinesDrawType == type) {
836             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
837             int row = *((int*)bytes);  bytes += sizeof(int);
838             int count = *((int*)bytes);  bytes += sizeof(int);
839             int bot = *((int*)bytes);  bytes += sizeof(int);
840             int left = *((int*)bytes);  bytes += sizeof(int);
841             int right = *((int*)bytes);  bytes += sizeof(int);
843 #if MM_DEBUG_DRAWING
844             NSLog(@"   Delete %d line(s) from %d", count, row);
845 #endif
846             [textStorage deleteLinesFromRow:row lineCount:count
847                     scrollBottom:bot left:left right:right
848                            color:[NSColor colorWithArgbInt:color]];
849         } else if (ReplaceStringDrawType == type) {
850             int bg = *((int*)bytes);  bytes += sizeof(int);
851             int fg = *((int*)bytes);  bytes += sizeof(int);
852             int sp = *((int*)bytes);  bytes += sizeof(int);
853             int row = *((int*)bytes);  bytes += sizeof(int);
854             int col = *((int*)bytes);  bytes += sizeof(int);
855             int flags = *((int*)bytes);  bytes += sizeof(int);
856             int len = *((int*)bytes);  bytes += sizeof(int);
857             NSString *string = [[NSString alloc]
858                     initWithBytesNoCopy:(void*)bytes
859                                  length:len
860                                encoding:NSUTF8StringEncoding
861                            freeWhenDone:NO];
862             bytes += len;
864 #if MM_DEBUG_DRAWING
865             NSLog(@"   Draw string at (%d,%d) length=%d flags=%d fg=0x%x "
866                     "bg=0x%x sp=0x%x (%@)", row, col, len, flags, fg, bg, sp,
867                     len > 0 ? [string substringToIndex:1] : @"");
868 #endif
869             // NOTE: If this is a call to draw the (block) cursor, then cancel
870             // any previous request to draw the insertion point, or it might
871             // get drawn as well.
872             if (flags & DRAW_CURSOR) {
873                 [textView setShouldDrawInsertionPoint:NO];
874                 //NSColor *color = [NSColor colorWithRgbInt:bg];
875                 //[textView drawInsertionPointAtRow:row column:col
876                 //                            shape:MMInsertionPointBlock
877                 //                            color:color];
878             }
879             [textStorage replaceString:string
880                                  atRow:row column:col
881                              withFlags:flags
882                        foregroundColor:[NSColor colorWithRgbInt:fg]
883                        backgroundColor:[NSColor colorWithArgbInt:bg]
884                           specialColor:[NSColor colorWithRgbInt:sp]];
886             [string release];
887         } else if (InsertLinesDrawType == type) {
888             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
889             int row = *((int*)bytes);  bytes += sizeof(int);
890             int count = *((int*)bytes);  bytes += sizeof(int);
891             int bot = *((int*)bytes);  bytes += sizeof(int);
892             int left = *((int*)bytes);  bytes += sizeof(int);
893             int right = *((int*)bytes);  bytes += sizeof(int);
895 #if MM_DEBUG_DRAWING
896             NSLog(@"   Insert %d line(s) at row %d", count, row);
897 #endif
898             [textStorage insertLinesAtRow:row lineCount:count
899                              scrollBottom:bot left:left right:right
900                                     color:[NSColor colorWithArgbInt:color]];
901         } else if (DrawCursorDrawType == type) {
902             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
903             int row = *((int*)bytes);  bytes += sizeof(int);
904             int col = *((int*)bytes);  bytes += sizeof(int);
905             int shape = *((int*)bytes);  bytes += sizeof(int);
906             int percent = *((int*)bytes);  bytes += sizeof(int);
908 #if MM_DEBUG_DRAWING
909             NSLog(@"   Draw cursor at (%d,%d)", row, col);
910 #endif
911             [textView drawInsertionPointAtRow:row column:col shape:shape
912                                      fraction:percent
913                                         color:[NSColor colorWithRgbInt:color]];
914         } else {
915             NSLog(@"WARNING: Unknown draw type (type=%d)", type);
916         }
917     }
919     [textStorage endEditing];
921     // NOTE: During resizing, Cocoa only sends draw messages before Vim's rows
922     // and columns are changed (due to ipc delays). Force a redraw here.
923     [[windowController vimView] displayIfNeeded];
925 #if MM_DEBUG_DRAWING
926     NSLog(@"<==== END   %s", _cmd);
927 #endif
930 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
931                 context:(void *)context
933     NSString *string = (code == NSOKButton) ? [panel filename] : nil;
934     @try {
935         [backendProxy setDialogReturn:string];
936     }
937     @catch (NSException *e) {
938         NSLog(@"Exception caught in %s %@", _cmd, e);
939     }
942 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context
944     NSArray *ret = nil;
946     code = code - NSAlertFirstButtonReturn + 1;
948     if ([alert isKindOfClass:[MMAlert class]] && [alert textField]) {
949         ret = [NSArray arrayWithObjects:[NSNumber numberWithInt:code],
950             [[alert textField] stringValue], nil];
951     } else {
952         ret = [NSArray arrayWithObject:[NSNumber numberWithInt:code]];
953     }
955     @try {
956         [backendProxy setDialogReturn:ret];
957     }
958     @catch (NSException *e) {
959         NSLog(@"Exception caught in %s %@", _cmd, e);
960     }
963 - (NSMenuItem *)recurseMenuItemForTag:(int)tag rootMenu:(NSMenu *)root
965     if (root) {
966         NSMenuItem *item = [root itemWithTag:tag];
967         if (item) {
968             lastMenuSearched = root;
969             return item;
970         }
972         NSArray *items = [root itemArray];
973         unsigned i, count = [items count];
974         for (i = 0; i < count; ++i) {
975             item = [items objectAtIndex:i];
976             if ([item hasSubmenu]) {
977                 item = [self recurseMenuItemForTag:tag
978                                           rootMenu:[item submenu]];
979                 if (item) {
980                     lastMenuSearched = [item submenu];
981                     return item;
982                 }
983             }
984         }
985     }
987     return nil;
990 - (NSMenuItem *)menuItemForTag:(int)tag
992     // First search the same menu that was search last time this method was
993     // called.  Since this method is often called for each menu item in a
994     // menu this can significantly improve search times.
995     if (lastMenuSearched) {
996         NSMenuItem *item = [self recurseMenuItemForTag:tag
997                                               rootMenu:lastMenuSearched];
998         if (item) return item;
999     }
1001     // Search the main menu.
1002     int i, count = [mainMenuItems count];
1003     for (i = 0; i < count; ++i) {
1004         NSMenuItem *item = [mainMenuItems objectAtIndex:i];
1005         if ([item tag] == tag) return item;
1006         item = [self recurseMenuItemForTag:tag rootMenu:[item submenu]];
1007         if (item) {
1008             lastMenuSearched = [item submenu];
1009             return item;
1010         }
1011     }
1013     // Search the popup menus.
1014     count = [popupMenuItems count];
1015     for (i = 0; i < count; ++i) {
1016         NSMenuItem *item = [popupMenuItems objectAtIndex:i];
1017         if ([item tag] == tag) return item;
1018         item = [self recurseMenuItemForTag:tag rootMenu:[item submenu]];
1019         if (item) {
1020             lastMenuSearched = [item submenu];
1021             return item;
1022         }
1023     }
1025     return nil;
1028 - (NSMenu *)menuForTag:(int)tag
1030     return [[self menuItemForTag:tag] submenu];
1033 - (NSMenu *)topLevelMenuForTitle:(NSString *)title
1035     // Search only the top-level menus.
1037     unsigned i, count = [popupMenuItems count];
1038     for (i = 0; i < count; ++i) {
1039         NSMenuItem *item = [popupMenuItems objectAtIndex:i];
1040         if ([title isEqual:[item title]])
1041             return [item submenu];
1042     }
1044     count = [mainMenuItems count];
1045     for (i = 0; i < count; ++i) {
1046         NSMenuItem *item = [mainMenuItems objectAtIndex:i];
1047         if ([title isEqual:[item title]])
1048             return [item submenu];
1049     }
1051     return nil;
1054 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
1055                atIndex:(int)idx
1057     NSMenu *parent = [self menuForTag:parentTag];
1058     NSMenuItem *item = [[NSMenuItem alloc] init];
1059     NSMenu *menu = [[NSMenu alloc] initWithTitle:title];
1061     [menu setAutoenablesItems:NO];
1062     [item setTag:tag];
1063     [item setTitle:title];
1064     [item setSubmenu:menu];
1066     if (parent) {
1067         if ([parent numberOfItems] <= idx) {
1068             [parent addItem:item];
1069         } else {
1070             [parent insertItem:item atIndex:idx];
1071         }
1072     } else {
1073         NSMutableArray *items = (MenuPopupType == parentTag)
1074             ? popupMenuItems : mainMenuItems;
1075         if ([items count] <= idx) {
1076             [items addObject:item];
1077         } else {
1078             [items insertObject:item atIndex:idx];
1079         }
1081         shouldUpdateMainMenu = (MenuPopupType != parentTag);
1082     }
1084     [item release];
1085     [menu release];
1088 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
1089                      title:(NSString *)title tip:(NSString *)tip
1090              keyEquivalent:(int)key modifiers:(int)mask
1091                     action:(NSString *)action atIndex:(int)idx
1093     if (parent) {
1094         NSMenuItem *item = nil;
1095         if (!title || ([title hasPrefix:@"-"] && [title hasSuffix:@"-"])) {
1096             item = [NSMenuItem separatorItem];
1097         } else {
1098             item = [[[NSMenuItem alloc] init] autorelease];
1099             [item setTitle:title];
1100             // TODO: Check that 'action' is a valid action (nothing will happen
1101             // if it isn't, but it would be nice with a warning).
1102             if (action) [item setAction:NSSelectorFromString(action)];
1103             else        [item setAction:@selector(vimMenuItemAction:)];
1104             if (tip) [item setToolTip:tip];
1106             if (key != 0) {
1107                 NSString *keyString =
1108                     [NSString stringWithFormat:@"%C", key];
1109                 [item setKeyEquivalent:keyString];
1110                 [item setKeyEquivalentModifierMask:mask];
1111             }
1112         }
1114         // NOTE!  The tag is used to idenfity which menu items were
1115         // added by Vim (tag != 0) and which were added by the AppKit
1116         // (tag == 0).
1117         [item setTag:tag];
1119         if ([parent numberOfItems] <= idx) {
1120             [parent addItem:item];
1121         } else {
1122             [parent insertItem:item atIndex:idx];
1123         }
1124     } else {
1125         NSLog(@"WARNING: Menu item '%@' (tag=%d) has no parent.", title, tag);
1126     }
1129 - (void)updateMainMenu
1131     NSMenu *mainMenu = [NSApp mainMenu];
1133     // Stop NSApp from updating the Window menu.
1134     [NSApp setWindowsMenu:nil];
1136     // Remove all menus from main menu (except the MacVim menu).
1137     int i, count = [mainMenu numberOfItems];
1138     for (i = count-1; i > 0; --i) {
1139         [mainMenu removeItemAtIndex:i];
1140     }
1142     // Add menus from 'mainMenuItems' to main menu.
1143     count = [mainMenuItems count];
1144     for (i = 0; i < count; ++i) {
1145         [mainMenu addItem:[mainMenuItems objectAtIndex:i]];
1146     }
1148     // Set the new Window menu.
1149     // TODO!  Need to look for 'Window' in all localized languages.
1150     NSMenu *windowMenu = [[mainMenu itemWithTitle:@"Window"] submenu];
1151     if (windowMenu) {
1152         // Remove all AppKit owned menu items (tag == 0); they will be added
1153         // again when setWindowsMenu: is called.
1154         count = [windowMenu numberOfItems];
1155         for (i = count-1; i >= 0; --i) {
1156             NSMenuItem *item = [windowMenu itemAtIndex:i];
1157             if (![item tag]) {
1158                 [windowMenu removeItem:item];
1159             }
1160         }
1162         [NSApp setWindowsMenu:windowMenu];
1163     }
1165     shouldUpdateMainMenu = NO;
1168 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index
1170     if (!toolbar) return nil;
1172     NSArray *items = [toolbar items];
1173     int i, count = [items count];
1174     for (i = 0; i < count; ++i) {
1175         NSToolbarItem *item = [items objectAtIndex:i];
1176         if ([item tag] == tag) {
1177             if (index) *index = i;
1178             return item;
1179         }
1180     }
1182     return nil;
1185 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
1186         toolTip:(NSString *)tip icon:(NSString *)icon
1188     // If the item corresponds to a separator then do nothing, since it is
1189     // already defined by Cocoa.
1190     if (!title || [title isEqual:NSToolbarSeparatorItemIdentifier]
1191                || [title isEqual:NSToolbarSpaceItemIdentifier]
1192                || [title isEqual:NSToolbarFlexibleSpaceItemIdentifier])
1193         return;
1195     NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:title];
1196     [item setTag:tag];
1197     [item setLabel:title];
1198     [item setToolTip:tip];
1199     [item setAction:@selector(vimMenuItemAction:)];
1200     [item setAutovalidates:NO];
1202     NSImage *img = [NSImage imageNamed:icon];
1203     if (!img) {
1204         NSLog(@"WARNING: Could not find image with name '%@' to use as toolbar"
1205                " image for identifier '%@';"
1206                " using default toolbar icon '%@' instead.",
1207                icon, title, MMDefaultToolbarImageName);
1209         img = [NSImage imageNamed:MMDefaultToolbarImageName];
1210     }
1212     [item setImage:img];
1214     [toolbarItemDict setObject:item forKey:title];
1216     [item release];
1219 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label tip:(NSString
1220                    *)tip icon:(NSString *)icon atIndex:(int)idx
1222     if (!toolbar) return;
1224     // Check for separator items.
1225     if (!label) {
1226         label = NSToolbarSeparatorItemIdentifier;
1227     } else if ([label length] >= 2 && [label hasPrefix:@"-"]
1228                                    && [label hasSuffix:@"-"]) {
1229         // The label begins and ends with '-'; decided which kind of separator
1230         // item it is by looking at the prefix.
1231         if ([label hasPrefix:@"-space"]) {
1232             label = NSToolbarSpaceItemIdentifier;
1233         } else if ([label hasPrefix:@"-flexspace"]) {
1234             label = NSToolbarFlexibleSpaceItemIdentifier;
1235         } else {
1236             label = NSToolbarSeparatorItemIdentifier;
1237         }
1238     }
1240     [self addToolbarItemToDictionaryWithTag:tag label:label toolTip:tip
1241                                        icon:icon];
1243     int maxIdx = [[toolbar items] count];
1244     if (maxIdx < idx) idx = maxIdx;
1246     [toolbar insertItemWithItemIdentifier:label atIndex:idx];
1249 - (void)connectionDidDie:(NSNotification *)notification
1251     //NSLog(@"%@ %s%@", [self className], _cmd, notification);
1253     [self cleanup];
1255     // NOTE!  This causes the call to removeVimController: to be delayed.
1256     [[NSApp delegate]
1257             performSelectorOnMainThread:@selector(removeVimController:)
1258                              withObject:self waitUntilDone:NO];
1261 - (NSString *)description
1263     return [NSString stringWithFormat:@"%@ : isInitialized=%d inProcessCommandQueue=%d mainMenuItems=%@ popupMenuItems=%@ toolbar=%@", [self className], isInitialized, inProcessCommandQueue, mainMenuItems, popupMenuItems, toolbar];
1266 #if MM_RESEND_LAST_FAILURE
1267 - (void)resendTimerFired:(NSTimer *)timer
1269     int msgid = resendMsgid;
1270     NSData *data = nil;
1272     [resendTimer release];
1273     resendTimer = nil;
1275     if (!isInitialized)
1276         return;
1278     if (resendData)
1279         data = [resendData copy];
1281     //NSLog(@"Resending message: %s", MessageStrings[msgid]);
1282     [self sendMessage:msgid data:data];
1284 #endif
1286 @end // MMVimController (Private)
1290 @implementation NSColor (MMProtocol)
1292 + (NSColor *)colorWithRgbInt:(unsigned)rgb
1294     float r = ((rgb>>16) & 0xff)/255.0f;
1295     float g = ((rgb>>8) & 0xff)/255.0f;
1296     float b = (rgb & 0xff)/255.0f;
1298     return [NSColor colorWithCalibratedRed:r green:g blue:b alpha:1.0f];
1301 + (NSColor *)colorWithArgbInt:(unsigned)argb
1303     float a = ((argb>>24) & 0xff)/255.0f;
1304     float r = ((argb>>16) & 0xff)/255.0f;
1305     float g = ((argb>>8) & 0xff)/255.0f;
1306     float b = (argb & 0xff)/255.0f;
1308     return [NSColor colorWithCalibratedRed:r green:g blue:b alpha:a];
1311 @end // NSColor (MMProtocol)
1315 @implementation MMAlert
1316 - (void)dealloc
1318     [textField release];
1319     [super dealloc];
1322 - (void)setTextFieldString:(NSString *)textFieldString
1324     [textField release];
1325     textField = [[NSTextField alloc] init];
1326     [textField setStringValue:textFieldString];
1329 - (NSTextField *)textField
1331     return textField;
1334 - (void)setInformativeText:(NSString *)text
1336     if (textField) {
1337         // HACK! Add some space for the text field.
1338         [super setInformativeText:[text stringByAppendingString:@"\n\n\n"]];
1339     } else {
1340         [super setInformativeText:text];
1341     }
1344 - (void)beginSheetModalForWindow:(NSWindow *)window
1345                    modalDelegate:(id)delegate
1346                   didEndSelector:(SEL)didEndSelector
1347                      contextInfo:(void *)contextInfo
1349     [super beginSheetModalForWindow:window
1350                       modalDelegate:delegate
1351                      didEndSelector:didEndSelector
1352                         contextInfo:contextInfo];
1354     // HACK! Place the input text field at the bottom of the informative text
1355     // (which has been made a bit larger by adding newline characters).
1356     NSView *contentView = [[self window] contentView];
1357     NSRect rect = [contentView frame];
1358     rect.origin.y = rect.size.height;
1360     NSArray *subviews = [contentView subviews];
1361     unsigned i, count = [subviews count];
1362     for (i = 0; i < count; ++i) {
1363         NSView *view = [subviews objectAtIndex:i];
1364         if ([view isKindOfClass:[NSTextField class]]
1365                 && [view frame].origin.y < rect.origin.y) {
1366             // NOTE: The informative text field is the lowest NSTextField in
1367             // the alert dialog.
1368             rect = [view frame];
1369         }
1370     }
1372     rect.size.height = MMAlertTextFieldHeight;
1373     [textField setFrame:rect];
1374     [contentView addSubview:textField];
1375     [textField becomeFirstResponder];
1378 @end // MMAlert