merge changes from upstream
[MacVim/jjgod.git] / src / MacVim / MMVimController.m
blob817443bdc3334ccd1d2af44880b8e13bfc109af2
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"
33 // This is taken from gui.h
34 #define DRAW_CURSOR 0x20
36 static NSString *MMDefaultToolbarImageName = @"Attention";
37 static int MMAlertTextFieldHeight = 22;
39 // NOTE: By default a message sent to the backend will be dropped if it cannot
40 // be delivered instantly; otherwise there is a possibility that MacVim will
41 // 'beachball' while waiting to deliver DO messages to an unresponsive Vim
42 // process.  This means that you cannot rely on any message sent with
43 // sendMessage: to actually reach Vim.
44 static NSTimeInterval MMBackendProxyRequestTimeout = 0;
46 #if MM_RESEND_LAST_FAILURE
47 // If a message send fails, the message will be resent after this many seconds
48 // have passed.  (No queue is kept, only the very last message is resent.)
49 static NSTimeInterval MMResendInterval = 0.5;
50 #endif
53 @interface MMAlert : NSAlert {
54     NSTextField *textField;
56 - (void)setTextFieldString:(NSString *)textFieldString;
57 - (NSTextField *)textField;
58 @end
61 @interface MMVimController (Private)
62 - (void)handleMessage:(int)msgid data:(NSData *)data;
63 - (void)performBatchDrawWithData:(NSData *)data;
64 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
65                 context:(void *)context;
66 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context;
67 - (NSMenuItem *)recurseMenuItemForTag:(int)tag rootMenu:(NSMenu *)root;
68 - (NSMenuItem *)menuItemForTag:(int)tag;
69 - (NSMenu *)menuForTag:(int)tag;
70 - (NSMenu *)topLevelMenuForTitle:(NSString *)title;
71 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
72                atIndex:(int)idx;
73 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
74                      title:(NSString *)title tip:(NSString *)tip
75              keyEquivalent:(int)key modifiers:(int)mask
76                     action:(NSString *)action atIndex:(int)idx;
77 - (void)updateMainMenu;
78 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index;
79 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
80         toolTip:(NSString *)tip icon:(NSString *)icon;
81 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label
82                           tip:(NSString *)tip icon:(NSString *)icon
83                       atIndex:(int)idx;
84 - (void)connectionDidDie:(NSNotification *)notification;
85 #if MM_RESEND_LAST_FAILURE
86 - (void)resendTimerFired:(NSTimer *)timer;
87 #endif
88 @end
92 // TODO: Move to separate file
93 @interface NSColor (MMProtocol)
94 + (NSColor *)colorWithRgbInt:(unsigned)rgb;
95 + (NSColor *)colorWithArgbInt:(unsigned)argb;
96 @end
101 @implementation MMVimController
103 - (id)initWithBackend:(id)backend pid:(int)processIdentifier
105     if ((self = [super init])) {
106         windowController =
107             [[MMWindowController alloc] initWithVimController:self];
108         backendProxy = [backend retain];
109         sendQueue = [NSMutableArray new];
110         mainMenuItems = [[NSMutableArray alloc] init];
111         popupMenuItems = [[NSMutableArray alloc] init];
112         toolbarItemDict = [[NSMutableDictionary alloc] init];
113         pid = processIdentifier;
115         NSConnection *connection = [backendProxy connectionForProxy];
117         // TODO: Check that this will not set the timeout for the root proxy
118         // (in MMAppController).
119         [connection setRequestTimeout:MMBackendProxyRequestTimeout];
121         [[NSNotificationCenter defaultCenter] addObserver:self
122                 selector:@selector(connectionDidDie:)
123                     name:NSConnectionDidDieNotification object:connection];
126         NSWindow *win = [windowController window];
128         [[NSNotificationCenter defaultCenter]
129                 addObserver:self
130                    selector:@selector(windowDidBecomeMain:)
131                        name:NSWindowDidBecomeMainNotification
132                      object:win];
134         isInitialized = YES;
135         NSLog(@"initWithBackend done.");
136     }
138     return self;
141 - (void)dealloc
143     //NSLog(@"%@ %s", [self className], _cmd);
144     isInitialized = NO;
146 #if MM_RESEND_LAST_FAILURE
147     [resendData release];  resendData = nil;
148 #endif
150     [serverName release];  serverName = nil;
151     [backendProxy release];  backendProxy = nil;
152     [sendQueue release];  sendQueue = nil;
154     [toolbarItemDict release];  toolbarItemDict = nil;
155     [toolbar release];  toolbar = nil;
156     [popupMenuItems release];  popupMenuItems = nil;
157     [mainMenuItems release];  mainMenuItems = nil;
158     [windowController release];  windowController = nil;
160     [super dealloc];
163 - (MMWindowController *)windowController
165     return windowController;
168 - (void)setServerName:(NSString *)name
170     if (name != serverName) {
171         [serverName release];
172         serverName = [name copy];
173     }
176 - (NSString *)serverName
178     return serverName;
181 - (int)pid
183     return pid;
186 - (void)dropFiles:(NSArray *)filenames
188     int i, numberOfFiles = [filenames count];
189     NSMutableData *data = [NSMutableData data];
191     [data appendBytes:&numberOfFiles length:sizeof(int)];
193     for (i = 0; i < numberOfFiles; ++i) {
194         NSString *file = [filenames objectAtIndex:i];
195         int len = [file lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
197         if (len > 0) {
198             ++len;  // include NUL as well
199             [data appendBytes:&len length:sizeof(int)];
200             [data appendBytes:[file UTF8String] length:len];
201         }
202     }
204     [self sendMessage:DropFilesMsgID data:data];
207 - (void)dropString:(NSString *)string
209     int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
210     if (len > 0) {
211         NSMutableData *data = [NSMutableData data];
213         [data appendBytes:&len length:sizeof(int)];
214         [data appendBytes:[string UTF8String] length:len];
216         [self sendMessage:DropStringMsgID data:data];
217     }
220 - (void)sendMessage:(int)msgid data:(NSData *)data
222     //NSLog(@"sendMessage:%s (isInitialized=%d inProcessCommandQueue=%d)",
223     //        MessageStrings[msgid], isInitialized, inProcessCommandQueue);
225     if (!isInitialized) return;
227     if (inProcessCommandQueue) {
228         //NSLog(@"In process command queue; delaying message send.");
229         [sendQueue addObject:[NSNumber numberWithInt:msgid]];
230         if (data)
231             [sendQueue addObject:data];
232         else
233             [sendQueue addObject:[NSNull null]];
234         return;
235     }
237 #if MM_RESEND_LAST_FAILURE
238     if (resendTimer) {
239         //NSLog(@"cancelling scheduled resend of %s",
240         //        MessageStrings[resendMsgid]);
242         [resendTimer invalidate];
243         [resendTimer release];
244         resendTimer = nil;
245     }
247     if (resendData) {
248         [resendData release];
249         resendData = nil;
250     }
251 #endif
253     @try {
254         [backendProxy processInput:msgid data:data];
255     }
256     @catch (NSException *e) {
257         //NSLog(@"%@ %s Exception caught during DO call: %@",
258         //        [self className], _cmd, e);
259 #if MM_RESEND_LAST_FAILURE
260         //NSLog(@"%s failed, scheduling message %s for resend", _cmd,
261         //        MessageStrings[msgid]);
263         resendMsgid = msgid;
264         resendData = [data retain];
265         resendTimer = [NSTimer
266             scheduledTimerWithTimeInterval:MMResendInterval
267                                     target:self
268                                   selector:@selector(resendTimerFired:)
269                                   userInfo:nil
270                                    repeats:NO];
271         [resendTimer retain];
272 #endif
273     }
276 - (BOOL)sendMessageNow:(int)msgid data:(NSData *)data
277                timeout:(NSTimeInterval)timeout
279     if (!isInitialized || inProcessCommandQueue)
280         return NO;
282     if (timeout < 0) timeout = 0;
284     BOOL sendOk = YES;
285     NSConnection *conn = [backendProxy connectionForProxy];
286     NSTimeInterval oldTimeout = [conn requestTimeout];
288     [conn setRequestTimeout:timeout];
290     @try {
291         [backendProxy processInput:msgid data:data];
292     }
293     @catch (NSException *e) {
294         sendOk = NO;
295     }
296     @finally {
297         [conn setRequestTimeout:oldTimeout];
298     }
300     return sendOk;
303 - (void)addVimInput:(NSString *)string
305     // This is a very general method of adding input to the Vim process.  It is
306     // basically the same as calling remote_send() on the process (see
307     // ':h remote_send').
308     if (string) {
309         NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
310         [self sendMessage:AddInputMsgID data:data];
311     }
314 - (id)backendProxy
316     return backendProxy;
319 - (void)cleanup
321     //NSLog(@"%@ %s", [self className], _cmd);
322     if (!isInitialized) return;
324     isInitialized = NO;
325     [toolbar setDelegate:nil];
326     [[NSNotificationCenter defaultCenter] removeObserver:self];
327     [windowController cleanup];
330 - (oneway void)showSavePanelForDirectory:(in bycopy NSString *)dir
331                                    title:(in bycopy NSString *)title
332                                   saving:(int)saving
334     if (!isInitialized) return;
336     if (saving) {
337         [[NSSavePanel savePanel] beginSheetForDirectory:dir file:nil
338                 modalForWindow:[windowController window]
339                  modalDelegate:self
340                 didEndSelector:@selector(savePanelDidEnd:code:context:)
341                    contextInfo:NULL];
342     } else {
343         NSOpenPanel *panel = [NSOpenPanel openPanel];
344         [panel setAllowsMultipleSelection:NO];
345         [panel beginSheetForDirectory:dir file:nil types:nil
346                 modalForWindow:[windowController window]
347                  modalDelegate:self
348                 didEndSelector:@selector(savePanelDidEnd:code:context:)
349                    contextInfo:NULL];
350     }
353 - (oneway void)presentDialogWithStyle:(int)style
354                               message:(in bycopy NSString *)message
355                       informativeText:(in bycopy NSString *)text
356                          buttonTitles:(in bycopy NSArray *)buttonTitles
357                       textFieldString:(in bycopy NSString *)textFieldString
359     if (!(windowController && buttonTitles && [buttonTitles count])) return;
361     MMAlert *alert = [[MMAlert alloc] init];
363     // NOTE! This has to be done before setting the informative text.
364     if (textFieldString)
365         [alert setTextFieldString:textFieldString];
367     [alert setAlertStyle:style];
369     if (message) {
370         [alert setMessageText:message];
371     } else {
372         // If no message text is specified 'Alert' is used, which we don't
373         // want, so set an empty string as message text.
374         [alert setMessageText:@""];
375     }
377     if (text) {
378         [alert setInformativeText:text];
379     } else if (textFieldString) {
380         // Make sure there is always room for the input text field.
381         [alert setInformativeText:@""];
382     }
384     unsigned i, count = [buttonTitles count];
385     for (i = 0; i < count; ++i) {
386         NSString *title = [buttonTitles objectAtIndex:i];
387         // NOTE: The title of the button may contain the character '&' to
388         // indicate that the following letter should be the key equivalent
389         // associated with the button.  Extract this letter and lowercase it.
390         NSString *keyEquivalent = nil;
391         NSRange hotkeyRange = [title rangeOfString:@"&"];
392         if (NSNotFound != hotkeyRange.location) {
393             if ([title length] > NSMaxRange(hotkeyRange)) {
394                 NSRange keyEquivRange = NSMakeRange(hotkeyRange.location+1, 1);
395                 keyEquivalent = [[title substringWithRange:keyEquivRange]
396                     lowercaseString];
397             }
399             NSMutableString *string = [NSMutableString stringWithString:title];
400             [string deleteCharactersInRange:hotkeyRange];
401             title = string;
402         }
404         [alert addButtonWithTitle:title];
406         // Set key equivalent for the button, but only if NSAlert hasn't
407         // already done so.  (Check the documentation for
408         // - [NSAlert addButtonWithTitle:] to see what key equivalents are
409         // automatically assigned.)
410         NSButton *btn = [[alert buttons] lastObject];
411         if ([[btn keyEquivalent] length] == 0 && keyEquivalent) {
412             [btn setKeyEquivalent:keyEquivalent];
413         }
414     }
416     [alert beginSheetModalForWindow:[windowController window]
417                       modalDelegate:self
418                      didEndSelector:@selector(alertDidEnd:code:context:)
419                         contextInfo:NULL];
421     [alert release];
424 - (oneway void)processCommandQueue:(in bycopy NSArray *)queue
426     if (!isInitialized) return;
428     unsigned i, count = [queue count];
429     if (count % 2) {
430         NSLog(@"WARNING: Uneven number of components (%d) in flush queue "
431                 "message; ignoring this message.", count);
432         return;
433     }
435     inProcessCommandQueue = YES;
437     //NSLog(@"======== %s BEGIN ========", _cmd);
438     for (i = 0; i < count; i += 2) {
439         NSData *value = [queue objectAtIndex:i];
440         NSData *data = [queue objectAtIndex:i+1];
442         int msgid = *((int*)[value bytes]);
443         //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
445         [self handleMessage:msgid data:data];
446     }
447     //NSLog(@"======== %s  END  ========", _cmd);
449     if (shouldUpdateMainMenu) {
450         [self updateMainMenu];
451     }
453     [windowController processCommandQueueDidFinish];
455     inProcessCommandQueue = NO;
457     if ([sendQueue count] > 0) {
458         @try {
459             [backendProxy processInputAndData:sendQueue];
460         }
461         @catch (NSException *e) {
462             // Connection timed out, just ignore this.
463             //NSLog(@"WARNING! Connection timed out in %s", _cmd);
464         }
466         [sendQueue removeAllObjects];
467     }
470 - (void)windowDidBecomeMain:(NSNotification *)notification
472     if (isInitialized)
473         [self updateMainMenu];
476 - (NSToolbarItem *)toolbar:(NSToolbar *)theToolbar
477     itemForItemIdentifier:(NSString *)itemId
478     willBeInsertedIntoToolbar:(BOOL)flag
480     NSToolbarItem *item = [toolbarItemDict objectForKey:itemId];
481     if (!item) {
482         NSLog(@"WARNING:  No toolbar item with id '%@'", itemId);
483     }
485     return item;
488 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)theToolbar
490     return nil;
493 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)theToolbar
495     return nil;
498 @end // MMVimController
502 @implementation MMVimController (Private)
504 - (void)handleMessage:(int)msgid data:(NSData *)data
506     //NSLog(@"%@ %s", [self className], _cmd);
508     if (OpenVimWindowMsgID == msgid) {
509         NSLog(@"openWindow");
510         [windowController openWindow];
511     } else if (BatchDrawMsgID == msgid) {
512         [self performBatchDrawWithData:data];
513     } else if (SelectTabMsgID == msgid) {
514 #if 0   // NOTE: Tab selection is done inside updateTabsWithData:.
515         const void *bytes = [data bytes];
516         int idx = *((int*)bytes);
517         //NSLog(@"Selecting tab with index %d", idx);
518         [windowController selectTabWithIndex:idx];
519 #endif
520     } else if (UpdateTabBarMsgID == msgid) {
521         [windowController updateTabsWithData:data];
522     } else if (ShowTabBarMsgID == msgid) {
523         [windowController showTabBar:YES];
524     } else if (HideTabBarMsgID == msgid) {
525         [windowController showTabBar:NO];
526     } else if (SetTextDimensionsMsgID == msgid) {
527         const void *bytes = [data bytes];
528         int rows = *((int*)bytes);  bytes += sizeof(int);
529         int cols = *((int*)bytes);  bytes += sizeof(int);
531         [windowController setTextDimensionsWithRows:rows columns:cols];
532     } else if (SetWindowTitleMsgID == msgid) {
533         const void *bytes = [data bytes];
534         int len = *((int*)bytes);  bytes += sizeof(int);
536         NSString *string = [[NSString alloc] initWithBytes:(void*)bytes
537                 length:len encoding:NSUTF8StringEncoding];
539         [[windowController window] setTitle:string];
541         [string release];
542     } else if (AddMenuMsgID == msgid) {
543         NSString *title = nil;
544         const void *bytes = [data bytes];
545         int tag = *((int*)bytes);  bytes += sizeof(int);
546         int parentTag = *((int*)bytes);  bytes += sizeof(int);
547         int len = *((int*)bytes);  bytes += sizeof(int);
548         if (len > 0) {
549             title = [[NSString alloc] initWithBytes:(void*)bytes length:len
550                                            encoding:NSUTF8StringEncoding];
551             bytes += len;
552         }
553         int idx = *((int*)bytes);  bytes += sizeof(int);
555         if (MenuToolbarType == parentTag) {
556             if (!toolbar) {
557                 // NOTE! Each toolbar must have a unique identifier, else each
558                 // window will have the same toolbar.
559                 NSString *ident = [NSString stringWithFormat:@"%d.%d",
560                          (int)self, tag];
561                 toolbar = [[NSToolbar alloc] initWithIdentifier:ident];
563                 [toolbar setShowsBaselineSeparator:NO];
564                 [toolbar setDelegate:self];
565                 [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
566                 [toolbar setSizeMode:NSToolbarSizeModeSmall];
568                 NSWindow *win = [windowController window];
569                 [win setToolbar:toolbar];
571                 // HACK! Redirect the pill button so that we can ask Vim to
572                 // hide the toolbar.
573                 NSButton *pillButton = [win
574                     standardWindowButton:NSWindowToolbarButton];
575                 if (pillButton) {
576                     [pillButton setAction:@selector(toggleToolbar:)];
577                     [pillButton setTarget:windowController];
578                 }
579             }
580         } else if (title) {
581             [self addMenuWithTag:tag parent:parentTag title:title atIndex:idx];
582         }
584         [title release];
585     } else if (AddMenuItemMsgID == msgid) {
586         NSString *title = nil, *tip = nil, *icon = nil, *action = nil;
587         const void *bytes = [data bytes];
588         int tag = *((int*)bytes);  bytes += sizeof(int);
589         int parentTag = *((int*)bytes);  bytes += sizeof(int);
590         int namelen = *((int*)bytes);  bytes += sizeof(int);
591         if (namelen > 0) {
592             title = [[NSString alloc] initWithBytes:(void*)bytes length:namelen
593                                            encoding:NSUTF8StringEncoding];
594             bytes += namelen;
595         }
596         int tiplen = *((int*)bytes);  bytes += sizeof(int);
597         if (tiplen > 0) {
598             tip = [[NSString alloc] initWithBytes:(void*)bytes length:tiplen
599                                            encoding:NSUTF8StringEncoding];
600             bytes += tiplen;
601         }
602         int iconlen = *((int*)bytes);  bytes += sizeof(int);
603         if (iconlen > 0) {
604             icon = [[NSString alloc] initWithBytes:(void*)bytes length:iconlen
605                                            encoding:NSUTF8StringEncoding];
606             bytes += iconlen;
607         }
608         int actionlen = *((int*)bytes);  bytes += sizeof(int);
609         if (actionlen > 0) {
610             action = [[NSString alloc] initWithBytes:(void*)bytes
611                                               length:actionlen
612                                             encoding:NSUTF8StringEncoding];
613             bytes += actionlen;
614         }
615         int idx = *((int*)bytes);  bytes += sizeof(int);
616         if (idx < 0) idx = 0;
617         int key = *((int*)bytes);  bytes += sizeof(int);
618         int mask = *((int*)bytes);  bytes += sizeof(int);
620         NSString *ident = [NSString stringWithFormat:@"%d.%d",
621                 (int)self, parentTag];
622         if (toolbar && [[toolbar identifier] isEqual:ident]) {
623             [self addToolbarItemWithTag:tag label:title tip:tip icon:icon
624                                 atIndex:idx];
625         } else {
626             NSMenu *parent = [self menuForTag:parentTag];
627             [self addMenuItemWithTag:tag parent:parent title:title tip:tip
628                        keyEquivalent:key modifiers:mask action:action
629                              atIndex:idx];
630         }
632         [title release];
633         [tip release];
634         [icon release];
635         [action release];
636     } else if (RemoveMenuItemMsgID == msgid) {
637         const void *bytes = [data bytes];
638         int tag = *((int*)bytes);  bytes += sizeof(int);
640         id item;
641         int idx;
642         if ((item = [self toolbarItemForTag:tag index:&idx])) {
643             [toolbar removeItemAtIndex:idx];
644         } else if ((item = [self menuItemForTag:tag])) {
645             [item retain];
647             if ([item menu] == [NSApp mainMenu] || ![item menu]) {
648                 // NOTE: To be on the safe side we try to remove the item from
649                 // both arrays (it is ok to call removeObject: even if an array
650                 // does not contain the object to remove).
651                 [mainMenuItems removeObject:item];
652                 [popupMenuItems removeObject:item];
653             }
655             if ([item menu])
656                 [[item menu] removeItem:item];
658             [item release];
659         }
661         // Reset cached menu, just to be on the safe side.
662         lastMenuSearched = nil;
663     } else if (EnableMenuItemMsgID == msgid) {
664         const void *bytes = [data bytes];
665         int tag = *((int*)bytes);  bytes += sizeof(int);
666         int state = *((int*)bytes);  bytes += sizeof(int);
668         id item = [self toolbarItemForTag:tag index:NULL];
669         if (!item)
670             item = [self menuItemForTag:tag];
672         [item setEnabled:state];
673     } else if (ShowToolbarMsgID == msgid) {
674         const void *bytes = [data bytes];
675         int enable = *((int*)bytes);  bytes += sizeof(int);
676         int flags = *((int*)bytes);  bytes += sizeof(int);
678         int mode = NSToolbarDisplayModeDefault;
679         if (flags & ToolbarLabelFlag) {
680             mode = flags & ToolbarIconFlag ? NSToolbarDisplayModeIconAndLabel
681                     : NSToolbarDisplayModeLabelOnly;
682         } else if (flags & ToolbarIconFlag) {
683             mode = NSToolbarDisplayModeIconOnly;
684         }
686         int size = flags & ToolbarSizeRegularFlag ? NSToolbarSizeModeRegular
687                 : NSToolbarSizeModeSmall;
689         [windowController showToolbar:enable size:size mode:mode];
690     } else if (CreateScrollbarMsgID == msgid) {
691         const void *bytes = [data bytes];
692         long ident = *((long*)bytes);  bytes += sizeof(long);
693         int type = *((int*)bytes);  bytes += sizeof(int);
695         [windowController createScrollbarWithIdentifier:ident type:type];
696     } else if (DestroyScrollbarMsgID == msgid) {
697         const void *bytes = [data bytes];
698         long ident = *((long*)bytes);  bytes += sizeof(long);
700         [windowController destroyScrollbarWithIdentifier:ident];
701     } else if (ShowScrollbarMsgID == msgid) {
702         const void *bytes = [data bytes];
703         long ident = *((long*)bytes);  bytes += sizeof(long);
704         int visible = *((int*)bytes);  bytes += sizeof(int);
706         [windowController showScrollbarWithIdentifier:ident state:visible];
707     } else if (SetScrollbarPositionMsgID == msgid) {
708         const void *bytes = [data bytes];
709         long ident = *((long*)bytes);  bytes += sizeof(long);
710         int pos = *((int*)bytes);  bytes += sizeof(int);
711         int len = *((int*)bytes);  bytes += sizeof(int);
713         [windowController setScrollbarPosition:pos length:len
714                                     identifier:ident];
715     } else if (SetScrollbarThumbMsgID == msgid) {
716         const void *bytes = [data bytes];
717         long ident = *((long*)bytes);  bytes += sizeof(long);
718         float val = *((float*)bytes);  bytes += sizeof(float);
719         float prop = *((float*)bytes);  bytes += sizeof(float);
721         [windowController setScrollbarThumbValue:val proportion:prop
722                                       identifier:ident];
723     } else if (SetFontMsgID == msgid) {
724         const void *bytes = [data bytes];
725         float size = *((float*)bytes);  bytes += sizeof(float);
726         int len = *((int*)bytes);  bytes += sizeof(int);
727         NSString *name = [[NSString alloc]
728                 initWithBytes:(void*)bytes length:len
729                      encoding:NSUTF8StringEncoding];
730         NSFont *font = [NSFont fontWithName:name size:size];
732         if (font)
733             [windowController setFont:font];
735         [name release];
736     } else if (SetWideFontMsgID == msgid) {
737         const void *bytes = [data bytes];
738         float size = *((float*)bytes);  bytes += sizeof(float);
739         int len = *((int*)bytes);  bytes += sizeof(int);
740         if (len > 0) {
741             NSString *name = [[NSString alloc]
742                     initWithBytes:(void*)bytes length:len
743                          encoding:NSUTF8StringEncoding];
744             NSFont *font = [NSFont fontWithName:name size:size];
745             [windowController setWideFont:font];
747             [name release];
748         } else {
749             [windowController setWideFont:nil];
750         }
751     } else if (SetDefaultColorsMsgID == msgid) {
752         const void *bytes = [data bytes];
753         unsigned bg = *((unsigned*)bytes);  bytes += sizeof(unsigned);
754         unsigned fg = *((unsigned*)bytes);  bytes += sizeof(unsigned);
755         NSColor *back = [NSColor colorWithArgbInt:bg];
756         NSColor *fore = [NSColor colorWithRgbInt:fg];
758         [windowController setDefaultColorsBackground:back foreground:fore];
759     } else if (ExecuteActionMsgID == msgid) {
760         const void *bytes = [data bytes];
761         int len = *((int*)bytes);  bytes += sizeof(int);
762         NSString *actionName = [[NSString alloc]
763                 initWithBytesNoCopy:(void*)bytes
764                              length:len
765                            encoding:NSUTF8StringEncoding
766                        freeWhenDone:NO];
768         SEL sel = NSSelectorFromString(actionName);
769         [NSApp sendAction:sel to:nil from:self];
771         [actionName release];
772     } else if (ShowPopupMenuMsgID == msgid) {
773         const void *bytes = [data bytes];
774         int row = *((int*)bytes);  bytes += sizeof(int);
775         int col = *((int*)bytes);  bytes += sizeof(int);
776         int len = *((int*)bytes);  bytes += sizeof(int);
777         NSString *title = [[NSString alloc]
778                 initWithBytesNoCopy:(void*)bytes
779                              length:len
780                            encoding:NSUTF8StringEncoding
781                        freeWhenDone:NO];
783         NSMenu *menu = [self topLevelMenuForTitle:title];
784         if (menu) {
785             [windowController popupMenu:menu atRow:row column:col];
786         } else {
787             NSLog(@"WARNING: Cannot popup menu with title %@; no such menu.",
788                     title);
789         }
791         [title release];
792     } else if (SetMouseShapeMsgID == msgid) {
793         const void *bytes = [data bytes];
794         int shape = *((int*)bytes);  bytes += sizeof(int);
796         [windowController setMouseShape:shape];
797     } else if (AdjustLinespaceMsgID == msgid) {
798         const void *bytes = [data bytes];
799         int linespace = *((int*)bytes);  bytes += sizeof(int);
801         [windowController adjustLinespace:linespace];
802     } else if (ActivateMsgID == msgid) {
803         //NSLog(@"ActivateMsgID");
804         [NSApp activateIgnoringOtherApps:YES];
805         [[windowController window] makeKeyAndOrderFront:self];
806     } else if (SetServerNameMsgID == msgid) {
807         NSString *name = [[NSString alloc] initWithData:data
808                                                encoding:NSUTF8StringEncoding];
809         [self setServerName:name];
810         [name release];
811     } else if (EnterFullscreenMsgID == msgid) {
812         [windowController enterFullscreen];
813     } else if (LeaveFullscreenMsgID == msgid) {
814         [windowController leaveFullscreen];
815     } else if (BuffersNotModifiedMsgID == msgid) {
816         [[windowController window] setDocumentEdited:NO];
817     } else if (BuffersModifiedMsgID == msgid) {
818         [[windowController window] setDocumentEdited:YES];
819     } else if (SetPreEditPositionMsgID == msgid) {
820         const int *dim = (const int*)[data bytes];
821         [[windowController textView] setPreEditRow:dim[0] column:dim[1]];
822     } else {
823         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
824     }
828 #define MM_DEBUG_DRAWING 0
830 - (void)performBatchDrawWithData:(NSData *)data
832     // TODO!  Move to window controller.
833     MMTextStorage *textStorage = [windowController textStorage];
834     MMTextView *textView = [windowController textView];
835     if (!(textStorage && textView))
836         return;
838     const void *bytes = [data bytes];
839     const void *end = bytes + [data length];
841 #if MM_DEBUG_DRAWING
842     NSLog(@"====> BEGIN %s", _cmd);
843 #endif
844     [textStorage beginEditing];
846     // TODO: Sanity check input
848     while (bytes < end) {
849         int type = *((int*)bytes);  bytes += sizeof(int);
851         if (ClearAllDrawType == type) {
852 #if MM_DEBUG_DRAWING
853             NSLog(@"   Clear all");
854 #endif
855             [textStorage clearAll];
856         } else if (ClearBlockDrawType == type) {
857             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
858             int row1 = *((int*)bytes);  bytes += sizeof(int);
859             int col1 = *((int*)bytes);  bytes += sizeof(int);
860             int row2 = *((int*)bytes);  bytes += sizeof(int);
861             int col2 = *((int*)bytes);  bytes += sizeof(int);
863 #if MM_DEBUG_DRAWING
864             NSLog(@"   Clear block (%d,%d) -> (%d,%d)", row1, col1,
865                     row2,col2);
866 #endif
867             [textStorage clearBlockFromRow:row1 column:col1
868                     toRow:row2 column:col2
869                     color:[NSColor colorWithArgbInt:color]];
870         } else if (DeleteLinesDrawType == type) {
871             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
872             int row = *((int*)bytes);  bytes += sizeof(int);
873             int count = *((int*)bytes);  bytes += sizeof(int);
874             int bot = *((int*)bytes);  bytes += sizeof(int);
875             int left = *((int*)bytes);  bytes += sizeof(int);
876             int right = *((int*)bytes);  bytes += sizeof(int);
878 #if MM_DEBUG_DRAWING
879             NSLog(@"   Delete %d line(s) from %d", count, row);
880 #endif
881             [textStorage deleteLinesFromRow:row lineCount:count
882                     scrollBottom:bot left:left right:right
883                            color:[NSColor colorWithArgbInt:color]];
884         } else if (DrawStringDrawType == type) {
885             int bg = *((int*)bytes);  bytes += sizeof(int);
886             int fg = *((int*)bytes);  bytes += sizeof(int);
887             int sp = *((int*)bytes);  bytes += sizeof(int);
888             int row = *((int*)bytes);  bytes += sizeof(int);
889             int col = *((int*)bytes);  bytes += sizeof(int);
890             int cells = *((int*)bytes);  bytes += sizeof(int);
891             int flags = *((int*)bytes);  bytes += sizeof(int);
892             int len = *((int*)bytes);  bytes += sizeof(int);
893             NSString *string = [[NSString alloc]
894                     initWithBytesNoCopy:(void*)bytes
895                                  length:len
896                                encoding:NSUTF8StringEncoding
897                            freeWhenDone:NO];
898             bytes += len;
900 #if MM_DEBUG_DRAWING
901             NSLog(@"   Draw string at (%d,%d) length=%d flags=%d fg=0x%x "
902                     "bg=0x%x sp=0x%x (%@)", row, col, len, flags, fg, bg, sp,
903                     len > 0 ? [string substringToIndex:1] : @"");
904 #endif
905             // NOTE: If this is a call to draw the (block) cursor, then cancel
906             // any previous request to draw the insertion point, or it might
907             // get drawn as well.
908             if (flags & DRAW_CURSOR) {
909                 [textView setShouldDrawInsertionPoint:NO];
910                 //NSColor *color = [NSColor colorWithRgbInt:bg];
911                 //[textView drawInsertionPointAtRow:row column:col
912                 //                            shape:MMInsertionPointBlock
913                 //                            color:color];
914             }
916             [textStorage drawString:string
917                               atRow:row column:col cells:cells
918                           withFlags:flags
919                     foregroundColor:[NSColor colorWithRgbInt:fg]
920                     backgroundColor:[NSColor colorWithArgbInt:bg]
921                        specialColor:[NSColor colorWithRgbInt:sp]];
923             [string release];
924         } else if (InsertLinesDrawType == type) {
925             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
926             int row = *((int*)bytes);  bytes += sizeof(int);
927             int count = *((int*)bytes);  bytes += sizeof(int);
928             int bot = *((int*)bytes);  bytes += sizeof(int);
929             int left = *((int*)bytes);  bytes += sizeof(int);
930             int right = *((int*)bytes);  bytes += sizeof(int);
932 #if MM_DEBUG_DRAWING
933             NSLog(@"   Insert %d line(s) at row %d", count, row);
934 #endif
935             [textStorage insertLinesAtRow:row lineCount:count
936                              scrollBottom:bot left:left right:right
937                                     color:[NSColor colorWithArgbInt:color]];
938         } else if (DrawCursorDrawType == type) {
939             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
940             int row = *((int*)bytes);  bytes += sizeof(int);
941             int col = *((int*)bytes);  bytes += sizeof(int);
942             int shape = *((int*)bytes);  bytes += sizeof(int);
943             int percent = *((int*)bytes);  bytes += sizeof(int);
945 #if MM_DEBUG_DRAWING
946             NSLog(@"   Draw cursor at (%d,%d)", row, col);
947 #endif
948             [textView drawInsertionPointAtRow:row column:col shape:shape
949                                      fraction:percent
950                                         color:[NSColor colorWithRgbInt:color]];
951         } else {
952             NSLog(@"WARNING: Unknown draw type (type=%d)", type);
953         }
954     }
956     [textStorage endEditing];
958     // NOTE: During resizing, Cocoa only sends draw messages before Vim's rows
959     // and columns are changed (due to ipc delays). Force a redraw here.
960     [[windowController vimView] displayIfNeeded];
962 #if MM_DEBUG_DRAWING
963     NSLog(@"<==== END   %s", _cmd);
964 #endif
967 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
968                 context:(void *)context
970     NSString *string = (code == NSOKButton) ? [panel filename] : nil;
971     @try {
972         [backendProxy setDialogReturn:string];
973     }
974     @catch (NSException *e) {
975         NSLog(@"Exception caught in %s %@", _cmd, e);
976     }
979 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context
981     NSArray *ret = nil;
983     code = code - NSAlertFirstButtonReturn + 1;
985     if ([alert isKindOfClass:[MMAlert class]] && [alert textField]) {
986         ret = [NSArray arrayWithObjects:[NSNumber numberWithInt:code],
987             [[alert textField] stringValue], nil];
988     } else {
989         ret = [NSArray arrayWithObject:[NSNumber numberWithInt:code]];
990     }
992     @try {
993         [backendProxy setDialogReturn:ret];
994     }
995     @catch (NSException *e) {
996         NSLog(@"Exception caught in %s %@", _cmd, e);
997     }
1000 - (NSMenuItem *)recurseMenuItemForTag:(int)tag rootMenu:(NSMenu *)root
1002     if (root) {
1003         NSMenuItem *item = [root itemWithTag:tag];
1004         if (item) {
1005             lastMenuSearched = root;
1006             return item;
1007         }
1009         NSArray *items = [root itemArray];
1010         unsigned i, count = [items count];
1011         for (i = 0; i < count; ++i) {
1012             item = [items objectAtIndex:i];
1013             if ([item hasSubmenu]) {
1014                 item = [self recurseMenuItemForTag:tag
1015                                           rootMenu:[item submenu]];
1016                 if (item) {
1017                     lastMenuSearched = [item submenu];
1018                     return item;
1019                 }
1020             }
1021         }
1022     }
1024     return nil;
1027 - (NSMenuItem *)menuItemForTag:(int)tag
1029     // First search the same menu that was search last time this method was
1030     // called.  Since this method is often called for each menu item in a
1031     // menu this can significantly improve search times.
1032     if (lastMenuSearched) {
1033         NSMenuItem *item = [self recurseMenuItemForTag:tag
1034                                               rootMenu:lastMenuSearched];
1035         if (item) return item;
1036     }
1038     // Search the main menu.
1039     int i, count = [mainMenuItems count];
1040     for (i = 0; i < count; ++i) {
1041         NSMenuItem *item = [mainMenuItems objectAtIndex:i];
1042         if ([item tag] == tag) return item;
1043         item = [self recurseMenuItemForTag:tag rootMenu:[item submenu]];
1044         if (item) {
1045             lastMenuSearched = [item submenu];
1046             return item;
1047         }
1048     }
1050     // Search the popup menus.
1051     count = [popupMenuItems count];
1052     for (i = 0; i < count; ++i) {
1053         NSMenuItem *item = [popupMenuItems objectAtIndex:i];
1054         if ([item tag] == tag) return item;
1055         item = [self recurseMenuItemForTag:tag rootMenu:[item submenu]];
1056         if (item) {
1057             lastMenuSearched = [item submenu];
1058             return item;
1059         }
1060     }
1062     return nil;
1065 - (NSMenu *)menuForTag:(int)tag
1067     return [[self menuItemForTag:tag] submenu];
1070 - (NSMenu *)topLevelMenuForTitle:(NSString *)title
1072     // Search only the top-level menus.
1074     unsigned i, count = [popupMenuItems count];
1075     for (i = 0; i < count; ++i) {
1076         NSMenuItem *item = [popupMenuItems objectAtIndex:i];
1077         if ([title isEqual:[item title]])
1078             return [item submenu];
1079     }
1081     count = [mainMenuItems count];
1082     for (i = 0; i < count; ++i) {
1083         NSMenuItem *item = [mainMenuItems objectAtIndex:i];
1084         if ([title isEqual:[item title]])
1085             return [item submenu];
1086     }
1088     return nil;
1091 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
1092                atIndex:(int)idx
1094     NSMenu *parent = [self menuForTag:parentTag];
1095     NSMenuItem *item = [[NSMenuItem alloc] init];
1096     NSMenu *menu = [[NSMenu alloc] initWithTitle:title];
1098     [menu setAutoenablesItems:NO];
1099     [item setTag:tag];
1100     [item setTitle:title];
1101     [item setSubmenu:menu];
1103     if (parent) {
1104         if ([parent numberOfItems] <= idx) {
1105             [parent addItem:item];
1106         } else {
1107             [parent insertItem:item atIndex:idx];
1108         }
1109     } else {
1110         NSMutableArray *items = (MenuPopupType == parentTag)
1111             ? popupMenuItems : mainMenuItems;
1112         if ([items count] <= idx) {
1113             [items addObject:item];
1114         } else {
1115             [items insertObject:item atIndex:idx];
1116         }
1118         shouldUpdateMainMenu = (MenuPopupType != parentTag);
1119     }
1121     [item release];
1122     [menu release];
1125 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
1126                      title:(NSString *)title tip:(NSString *)tip
1127              keyEquivalent:(int)key modifiers:(int)mask
1128                     action:(NSString *)action atIndex:(int)idx
1130     if (parent) {
1131         NSMenuItem *item = nil;
1132         if (!title || ([title hasPrefix:@"-"] && [title hasSuffix:@"-"])) {
1133             item = [NSMenuItem separatorItem];
1134         } else {
1135             item = [[[NSMenuItem alloc] init] autorelease];
1136             [item setTitle:title];
1137             // TODO: Check that 'action' is a valid action (nothing will happen
1138             // if it isn't, but it would be nice with a warning).
1139             if (action) [item setAction:NSSelectorFromString(action)];
1140             else        [item setAction:@selector(vimMenuItemAction:)];
1141             if (tip) [item setToolTip:tip];
1143             if (key != 0) {
1144                 NSString *keyString =
1145                     [NSString stringWithFormat:@"%C", key];
1146                 [item setKeyEquivalent:keyString];
1147                 [item setKeyEquivalentModifierMask:mask];
1148             }
1149         }
1151         // NOTE!  The tag is used to idenfity which menu items were
1152         // added by Vim (tag != 0) and which were added by the AppKit
1153         // (tag == 0).
1154         [item setTag:tag];
1156         if ([parent numberOfItems] <= idx) {
1157             [parent addItem:item];
1158         } else {
1159             [parent insertItem:item atIndex:idx];
1160         }
1161     } else {
1162         NSLog(@"WARNING: Menu item '%@' (tag=%d) has no parent.", title, tag);
1163     }
1166 - (void)updateMainMenu
1168     NSMenu *mainMenu = [NSApp mainMenu];
1170     // Stop NSApp from updating the Window menu.
1171     [NSApp setWindowsMenu:nil];
1173     // Remove all menus from main menu (except the MacVim menu).
1174     int i, count = [mainMenu numberOfItems];
1175     for (i = count-1; i > 0; --i) {
1176         [mainMenu removeItemAtIndex:i];
1177     }
1179     // Add menus from 'mainMenuItems' to main menu.
1180     count = [mainMenuItems count];
1181     for (i = 0; i < count; ++i) {
1182         [mainMenu addItem:[mainMenuItems objectAtIndex:i]];
1183     }
1185     // Set the new Window menu.
1186     // TODO!  Need to look for 'Window' in all localized languages.
1187     NSMenu *windowMenu = [[mainMenu itemWithTitle:@"Window"] submenu];
1188     if (windowMenu) {
1189         // Remove all AppKit owned menu items (tag == 0); they will be added
1190         // again when setWindowsMenu: is called.
1191         count = [windowMenu numberOfItems];
1192         for (i = count-1; i >= 0; --i) {
1193             NSMenuItem *item = [windowMenu itemAtIndex:i];
1194             if (![item tag]) {
1195                 [windowMenu removeItem:item];
1196             }
1197         }
1199         [NSApp setWindowsMenu:windowMenu];
1200     }
1202     shouldUpdateMainMenu = NO;
1205 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index
1207     if (!toolbar) return nil;
1209     NSArray *items = [toolbar items];
1210     int i, count = [items count];
1211     for (i = 0; i < count; ++i) {
1212         NSToolbarItem *item = [items objectAtIndex:i];
1213         if ([item tag] == tag) {
1214             if (index) *index = i;
1215             return item;
1216         }
1217     }
1219     return nil;
1222 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
1223         toolTip:(NSString *)tip icon:(NSString *)icon
1225     // If the item corresponds to a separator then do nothing, since it is
1226     // already defined by Cocoa.
1227     if (!title || [title isEqual:NSToolbarSeparatorItemIdentifier]
1228                || [title isEqual:NSToolbarSpaceItemIdentifier]
1229                || [title isEqual:NSToolbarFlexibleSpaceItemIdentifier])
1230         return;
1232     NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:title];
1233     [item setTag:tag];
1234     [item setLabel:title];
1235     [item setToolTip:tip];
1236     [item setAction:@selector(vimMenuItemAction:)];
1237     [item setAutovalidates:NO];
1239     NSImage *img = [NSImage imageNamed:icon];
1240     if (!img) {
1241         NSLog(@"WARNING: Could not find image with name '%@' to use as toolbar"
1242                " image for identifier '%@';"
1243                " using default toolbar icon '%@' instead.",
1244                icon, title, MMDefaultToolbarImageName);
1246         img = [NSImage imageNamed:MMDefaultToolbarImageName];
1247     }
1249     [item setImage:img];
1251     [toolbarItemDict setObject:item forKey:title];
1253     [item release];
1256 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label tip:(NSString
1257                    *)tip icon:(NSString *)icon atIndex:(int)idx
1259     if (!toolbar) return;
1261     // Check for separator items.
1262     if (!label) {
1263         label = NSToolbarSeparatorItemIdentifier;
1264     } else if ([label length] >= 2 && [label hasPrefix:@"-"]
1265                                    && [label hasSuffix:@"-"]) {
1266         // The label begins and ends with '-'; decided which kind of separator
1267         // item it is by looking at the prefix.
1268         if ([label hasPrefix:@"-space"]) {
1269             label = NSToolbarSpaceItemIdentifier;
1270         } else if ([label hasPrefix:@"-flexspace"]) {
1271             label = NSToolbarFlexibleSpaceItemIdentifier;
1272         } else {
1273             label = NSToolbarSeparatorItemIdentifier;
1274         }
1275     }
1277     [self addToolbarItemToDictionaryWithTag:tag label:label toolTip:tip
1278                                        icon:icon];
1280     int maxIdx = [[toolbar items] count];
1281     if (maxIdx < idx) idx = maxIdx;
1283     [toolbar insertItemWithItemIdentifier:label atIndex:idx];
1286 - (void)connectionDidDie:(NSNotification *)notification
1288     //NSLog(@"%@ %s%@", [self className], _cmd, notification);
1290     [self cleanup];
1292     // NOTE!  This causes the call to removeVimController: to be delayed.
1293     [[NSApp delegate]
1294             performSelectorOnMainThread:@selector(removeVimController:)
1295                              withObject:self waitUntilDone:NO];
1298 - (NSString *)description
1300     return [NSString stringWithFormat:@"%@ : isInitialized=%d inProcessCommandQueue=%d mainMenuItems=%@ popupMenuItems=%@ toolbar=%@", [self className], isInitialized, inProcessCommandQueue, mainMenuItems, popupMenuItems, toolbar];
1303 #if MM_RESEND_LAST_FAILURE
1304 - (void)resendTimerFired:(NSTimer *)timer
1306     int msgid = resendMsgid;
1307     NSData *data = nil;
1309     [resendTimer release];
1310     resendTimer = nil;
1312     if (!isInitialized)
1313         return;
1315     if (resendData)
1316         data = [resendData copy];
1318     //NSLog(@"Resending message: %s", MessageStrings[msgid]);
1319     [self sendMessage:msgid data:data];
1321 #endif
1323 @end // MMVimController (Private)
1327 @implementation NSColor (MMProtocol)
1329 + (NSColor *)colorWithRgbInt:(unsigned)rgb
1331     float r = ((rgb>>16) & 0xff)/255.0f;
1332     float g = ((rgb>>8) & 0xff)/255.0f;
1333     float b = (rgb & 0xff)/255.0f;
1335     return [NSColor colorWithCalibratedRed:r green:g blue:b alpha:1.0f];
1338 + (NSColor *)colorWithArgbInt:(unsigned)argb
1340     float a = ((argb>>24) & 0xff)/255.0f;
1341     float r = ((argb>>16) & 0xff)/255.0f;
1342     float g = ((argb>>8) & 0xff)/255.0f;
1343     float b = (argb & 0xff)/255.0f;
1345     return [NSColor colorWithCalibratedRed:r green:g blue:b alpha:a];
1348 @end // NSColor (MMProtocol)
1352 @implementation MMAlert
1353 - (void)dealloc
1355     [textField release];
1356     [super dealloc];
1359 - (void)setTextFieldString:(NSString *)textFieldString
1361     [textField release];
1362     textField = [[NSTextField alloc] init];
1363     [textField setStringValue:textFieldString];
1366 - (NSTextField *)textField
1368     return textField;
1371 - (void)setInformativeText:(NSString *)text
1373     if (textField) {
1374         // HACK! Add some space for the text field.
1375         [super setInformativeText:[text stringByAppendingString:@"\n\n\n"]];
1376     } else {
1377         [super setInformativeText:text];
1378     }
1381 - (void)beginSheetModalForWindow:(NSWindow *)window
1382                    modalDelegate:(id)delegate
1383                   didEndSelector:(SEL)didEndSelector
1384                      contextInfo:(void *)contextInfo
1386     [super beginSheetModalForWindow:window
1387                       modalDelegate:delegate
1388                      didEndSelector:didEndSelector
1389                         contextInfo:contextInfo];
1391     // HACK! Place the input text field at the bottom of the informative text
1392     // (which has been made a bit larger by adding newline characters).
1393     NSView *contentView = [[self window] contentView];
1394     NSRect rect = [contentView frame];
1395     rect.origin.y = rect.size.height;
1397     NSArray *subviews = [contentView subviews];
1398     unsigned i, count = [subviews count];
1399     for (i = 0; i < count; ++i) {
1400         NSView *view = [subviews objectAtIndex:i];
1401         if ([view isKindOfClass:[NSTextField class]]
1402                 && [view frame].origin.y < rect.origin.y) {
1403             // NOTE: The informative text field is the lowest NSTextField in
1404             // the alert dialog.
1405             rect = [view frame];
1406         }
1407     }
1409     rect.size.height = MMAlertTextFieldHeight;
1410     [textField setFrame:rect];
1411     [contentView addSubview:textField];
1412     [textField becomeFirstResponder];
1415 @end // MMAlert