More file opening options (plus quickstart feature)
[MacVim.git] / src / MacVim / MMVimController.m
blob9cb7797e6f2abd17cb8d616471c347c645499c57
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 "MMAppController.h"
27 #import "MMAtsuiTextView.h"
28 #import "MMTextView.h"
29 #import "MMVimController.h"
30 #import "MMVimView.h"
31 #import "MMWindowController.h"
32 #import "Miscellaneous.h"
34 #ifdef MM_ENABLE_PLUGINS
35 #import "MMPlugInManager.h"
36 #endif
38 static NSString *MMDefaultToolbarImageName = @"Attention";
39 static int MMAlertTextFieldHeight = 22;
41 // NOTE: By default a message sent to the backend will be dropped if it cannot
42 // be delivered instantly; otherwise there is a possibility that MacVim will
43 // 'beachball' while waiting to deliver DO messages to an unresponsive Vim
44 // process.  This means that you cannot rely on any message sent with
45 // sendMessage: to actually reach Vim.
46 static NSTimeInterval MMBackendProxyRequestTimeout = 0;
48 // Timeout used for setDialogReturn:.
49 static NSTimeInterval MMSetDialogReturnTimeout = 1.0;
51 // Maximum number of items in the receiveQueue.  (It is hard to predict what
52 // consequences changing this number will have.)
53 static int MMReceiveQueueCap = 100;
55 static BOOL isUnsafeMessage(int msgid);
58 @interface MMAlert : NSAlert {
59     NSTextField *textField;
61 - (void)setTextFieldString:(NSString *)textFieldString;
62 - (NSTextField *)textField;
63 @end
66 @interface MMVimController (Private)
67 - (void)doProcessCommandQueue:(NSArray *)queue;
68 - (void)handleMessage:(int)msgid data:(NSData *)data;
69 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
70                 context:(void *)context;
71 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context;
72 - (NSMenuItem *)menuItemForDescriptor:(NSArray *)desc;
73 - (NSMenu *)parentMenuForDescriptor:(NSArray *)desc;
74 - (NSMenu *)topLevelMenuForTitle:(NSString *)title;
75 - (void)addMenuWithDescriptor:(NSArray *)desc atIndex:(int)index;
76 - (void)addMenuItemWithDescriptor:(NSArray *)desc
77                           atIndex:(int)index
78                               tip:(NSString *)tip
79                              icon:(NSString *)icon
80                     keyEquivalent:(NSString *)keyEquivalent
81                      modifierMask:(int)modifierMask
82                            action:(NSString *)action
83                       isAlternate:(BOOL)isAlternate;
84 - (void)removeMenuItemWithDescriptor:(NSArray *)desc;
85 - (void)enableMenuItemWithDescriptor:(NSArray *)desc state:(BOOL)on;
86 - (void)addToolbarItemToDictionaryWithLabel:(NSString *)title
87         toolTip:(NSString *)tip icon:(NSString *)icon;
88 - (void)addToolbarItemWithLabel:(NSString *)label
89                           tip:(NSString *)tip icon:(NSString *)icon
90                       atIndex:(int)idx;
91 - (void)popupMenuWithDescriptor:(NSArray *)desc
92                           atRow:(NSNumber *)row
93                          column:(NSNumber *)col;
94 - (void)popupMenuWithAttributes:(NSDictionary *)attrs;
95 - (void)connectionDidDie:(NSNotification *)notification;
96 - (void)scheduleClose;
97 @end
102 @implementation MMVimController
104 - (id)initWithBackend:(id)backend pid:(int)processIdentifier
106     if ((self = [super init])) {
107         windowController =
108             [[MMWindowController alloc] initWithVimController:self];
109         backendProxy = [backend retain];
110         sendQueue = [NSMutableArray new];
111         receiveQueue = [NSMutableArray new];
112         popupMenuItems = [[NSMutableArray alloc] init];
113         toolbarItemDict = [[NSMutableDictionary alloc] init];
114         pid = processIdentifier;
116         NSConnection *connection = [backendProxy connectionForProxy];
118         // TODO: Check that this will not set the timeout for the root proxy
119         // (in MMAppController).
120         [connection setRequestTimeout:MMBackendProxyRequestTimeout];
122         [[NSNotificationCenter defaultCenter] addObserver:self
123                 selector:@selector(connectionDidDie:)
124                     name:NSConnectionDidDieNotification object:connection];
126         // Set up a main menu with only a "MacVim" menu (copied from a template
127         // which itself is set up in MainMenu.nib).  The main menu is populated
128         // by Vim later on.
129         mainMenu = [[NSMenu alloc] initWithTitle:@"MainMenu"];
130         NSMenuItem *appMenuItem = [[MMAppController sharedInstance]
131                                             appMenuItemTemplate];
132         appMenuItem = [[appMenuItem copy] autorelease];
134         // Note: If the title of the application menu is anything but what
135         // CFBundleName says then the application menu will not be typeset in
136         // boldface for some reason.  (It should already be set when we copy
137         // from the default main menu, but this is not the case for some
138         // reason.)
139         NSString *appName = [[NSBundle mainBundle]
140                 objectForInfoDictionaryKey:@"CFBundleName"];
141         [appMenuItem setTitle:appName];
143         [mainMenu addItem:appMenuItem];
145 #ifdef MM_ENABLE_PLUGINS
146         instanceMediator = [[MMPlugInInstanceMediator alloc] initWithVimController:self];
147 #endif
149         isInitialized = YES;
150     }
152     return self;
155 - (void)dealloc
157     LOG_DEALLOC
159     isInitialized = NO;
161 #ifdef MM_ENABLE_PLUGINS
162     [instanceMediator release]; instanceMediator = nil;
163 #endif
165     [serverName release];  serverName = nil;
166     [backendProxy release];  backendProxy = nil;
167     [sendQueue release];  sendQueue = nil;
168     [receiveQueue release];  receiveQueue = nil;
170     [toolbarItemDict release];  toolbarItemDict = nil;
171     [toolbar release];  toolbar = nil;
172     [popupMenuItems release];  popupMenuItems = nil;
173     [windowController release];  windowController = nil;
175     [vimState release];  vimState = nil;
176     [mainMenu release];  mainMenu = nil;
178     [super dealloc];
181 - (MMWindowController *)windowController
183     return windowController;
186 #ifdef MM_ENABLE_PLUGINS
187 - (MMPlugInInstanceMediator *)instanceMediator
189     return instanceMediator;
191 #endif
193 - (NSDictionary *)vimState
195     return vimState;
198 - (NSMenu *)mainMenu
200     return mainMenu;
203 - (void)setServerName:(NSString *)name
205     if (name != serverName) {
206         [serverName release];
207         serverName = [name copy];
208     }
211 - (NSString *)serverName
213     return serverName;
216 - (int)pid
218     return pid;
221 - (void)dropFiles:(NSArray *)filenames forceOpen:(BOOL)force
223     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
225     // Default to opening in tabs if layout is invalid or set to "windows".
226     int layout = [ud integerForKey:MMOpenLayoutKey];
227     if (layout < 0 || layout > MMLayoutTabs)
228         layout = MMLayoutTabs;
230     NSDictionary *args = [NSDictionary dictionaryWithObjectsAndKeys:
231             [NSNumber numberWithInt:layout],    @"layout",
232             filenames,                          @"filenames",
233             [NSNumber numberWithBool:force],    @"forceOpen",
234             nil];
236     [self sendMessage:DropFilesMsgID data:[args dictionaryAsData]];
239 - (void)dropString:(NSString *)string
241     int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
242     if (len > 0) {
243         NSMutableData *data = [NSMutableData data];
245         [data appendBytes:&len length:sizeof(int)];
246         [data appendBytes:[string UTF8String] length:len];
248         [self sendMessage:DropStringMsgID data:data];
249     }
252 - (void)passArguments:(NSDictionary *)args
254     if (!args) return;
256     [self sendMessage:OpenWithArgumentsMsgID data:[args dictionaryAsData]];
259 - (void)sendMessage:(int)msgid data:(NSData *)data
261     //NSLog(@"sendMessage:%s (isInitialized=%d inProcessCommandQueue=%d)",
262     //        MessageStrings[msgid], isInitialized, inProcessCommandQueue);
264     if (!isInitialized) return;
266     if (inProcessCommandQueue) {
267         //NSLog(@"In process command queue; delaying message send.");
268         [sendQueue addObject:[NSNumber numberWithInt:msgid]];
269         if (data)
270             [sendQueue addObject:data];
271         else
272             [sendQueue addObject:[NSNull null]];
273         return;
274     }
276     @try {
277         [backendProxy processInput:msgid data:data];
278     }
279     @catch (NSException *e) {
280         //NSLog(@"%@ %s Exception caught during DO call: %@",
281         //        [self className], _cmd, e);
282     }
285 - (BOOL)sendMessageNow:(int)msgid data:(NSData *)data
286                timeout:(NSTimeInterval)timeout
288     // Send a message with a timeout.  USE WITH EXTREME CAUTION!  Sending
289     // messages in rapid succession with a timeout may cause MacVim to beach
290     // ball forever.  In almost all circumstances sendMessage:data: should be
291     // used instead.
293     if (!isInitialized || inProcessCommandQueue)
294         return NO;
296     if (timeout < 0) timeout = 0;
298     BOOL sendOk = YES;
299     NSConnection *conn = [backendProxy connectionForProxy];
300     NSTimeInterval oldTimeout = [conn requestTimeout];
302     [conn setRequestTimeout:timeout];
304     @try {
305         [backendProxy processInput:msgid data:data];
306     }
307     @catch (NSException *e) {
308         sendOk = NO;
309     }
310     @finally {
311         [conn setRequestTimeout:oldTimeout];
312     }
314     return sendOk;
317 - (void)addVimInput:(NSString *)string
319     // This is a very general method of adding input to the Vim process.  It is
320     // basically the same as calling remote_send() on the process (see
321     // ':h remote_send').
322     if (string) {
323         NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
324         [self sendMessage:AddInputMsgID data:data];
325     }
328 - (NSString *)evaluateVimExpression:(NSString *)expr
330     NSString *eval = nil;
332     @try {
333         eval = [backendProxy evaluateExpression:expr];
334     }
335     @catch (NSException *ex) { /* do nothing */ }
337     return eval;
340 - (id)evaluateVimExpressionCocoa:(NSString *)expr errorString:(NSString **)errstr
342     id eval = nil;
344     @try {
345         eval = [backendProxy evaluateExpressionCocoa:expr
346                                          errorString:errstr];
347     } @catch (NSException *ex) {
348         *errstr = [ex reason];
349     }
351     return eval;
354 - (id)backendProxy
356     return backendProxy;
359 - (void)cleanup
361     if (!isInitialized) return;
363     isInitialized = NO;
364     [toolbar setDelegate:nil];
365     [[NSNotificationCenter defaultCenter] removeObserver:self];
366     //[[backendProxy connectionForProxy] invalidate];
367     //[windowController close];
368     [windowController cleanup];
371 - (oneway void)showSavePanelWithAttributes:(in bycopy NSDictionary *)attr
373     if (!isInitialized) return;
375     BOOL inDefaultMode = [[[NSRunLoop currentRunLoop] currentMode]
376                                         isEqual:NSDefaultRunLoopMode];
377     if (!inDefaultMode) {
378         // Delay call until run loop is in default mode.
379         [self performSelectorOnMainThread:
380                                         @selector(showSavePanelWithAttributes:)
381                                withObject:attr
382                             waitUntilDone:NO
383                                     modes:[NSArray arrayWithObject:
384                                            NSDefaultRunLoopMode]];
385         return;
386     }
388     NSString *dir = [attr objectForKey:@"dir"];
389     BOOL saving = [[attr objectForKey:@"saving"] boolValue];
391     if (!dir) {
392         // 'dir == nil' means: set dir to the pwd of the Vim process, or let
393         // open dialog decide (depending on the below user default).
394         BOOL trackPwd = [[NSUserDefaults standardUserDefaults]
395                 boolForKey:MMDialogsTrackPwdKey];
396         if (trackPwd)
397             dir = [vimState objectForKey:@"pwd"];
398     }
400     if (saving) {
401         [[NSSavePanel savePanel] beginSheetForDirectory:dir file:nil
402                 modalForWindow:[windowController window]
403                  modalDelegate:self
404                 didEndSelector:@selector(savePanelDidEnd:code:context:)
405                    contextInfo:NULL];
406     } else {
407         NSOpenPanel *panel = [NSOpenPanel openPanel];
408         [panel setAllowsMultipleSelection:NO];
409         [panel setAccessoryView:openPanelAccessoryView()];
411         [panel beginSheetForDirectory:dir file:nil types:nil
412                 modalForWindow:[windowController window]
413                  modalDelegate:self
414                 didEndSelector:@selector(savePanelDidEnd:code:context:)
415                    contextInfo:NULL];
416     }
419 - (oneway void)presentDialogWithAttributes:(in bycopy NSDictionary *)attr
421     if (!isInitialized) return;
423     BOOL inDefaultMode = [[[NSRunLoop currentRunLoop] currentMode]
424                                         isEqual:NSDefaultRunLoopMode];
425     if (!inDefaultMode) {
426         // Delay call until run loop is in default mode.
427         [self performSelectorOnMainThread:
428                                         @selector(presentDialogWithAttributes:)
429                                withObject:attr
430                             waitUntilDone:NO
431                                     modes:[NSArray arrayWithObject:
432                                            NSDefaultRunLoopMode]];
433         return;
434     }
436     NSArray *buttonTitles = [attr objectForKey:@"buttonTitles"];
437     if (!(buttonTitles && [buttonTitles count])) return;
439     int style = [[attr objectForKey:@"alertStyle"] intValue];
440     NSString *message = [attr objectForKey:@"messageText"];
441     NSString *text = [attr objectForKey:@"informativeText"];
442     NSString *textFieldString = [attr objectForKey:@"textFieldString"];
443     MMAlert *alert = [[MMAlert alloc] init];
445     // NOTE! This has to be done before setting the informative text.
446     if (textFieldString)
447         [alert setTextFieldString:textFieldString];
449     [alert setAlertStyle:style];
451     if (message) {
452         [alert setMessageText:message];
453     } else {
454         // If no message text is specified 'Alert' is used, which we don't
455         // want, so set an empty string as message text.
456         [alert setMessageText:@""];
457     }
459     if (text) {
460         [alert setInformativeText:text];
461     } else if (textFieldString) {
462         // Make sure there is always room for the input text field.
463         [alert setInformativeText:@""];
464     }
466     unsigned i, count = [buttonTitles count];
467     for (i = 0; i < count; ++i) {
468         NSString *title = [buttonTitles objectAtIndex:i];
469         // NOTE: The title of the button may contain the character '&' to
470         // indicate that the following letter should be the key equivalent
471         // associated with the button.  Extract this letter and lowercase it.
472         NSString *keyEquivalent = nil;
473         NSRange hotkeyRange = [title rangeOfString:@"&"];
474         if (NSNotFound != hotkeyRange.location) {
475             if ([title length] > NSMaxRange(hotkeyRange)) {
476                 NSRange keyEquivRange = NSMakeRange(hotkeyRange.location+1, 1);
477                 keyEquivalent = [[title substringWithRange:keyEquivRange]
478                     lowercaseString];
479             }
481             NSMutableString *string = [NSMutableString stringWithString:title];
482             [string deleteCharactersInRange:hotkeyRange];
483             title = string;
484         }
486         [alert addButtonWithTitle:title];
488         // Set key equivalent for the button, but only if NSAlert hasn't
489         // already done so.  (Check the documentation for
490         // - [NSAlert addButtonWithTitle:] to see what key equivalents are
491         // automatically assigned.)
492         NSButton *btn = [[alert buttons] lastObject];
493         if ([[btn keyEquivalent] length] == 0 && keyEquivalent) {
494             [btn setKeyEquivalent:keyEquivalent];
495         }
496     }
498     [alert beginSheetModalForWindow:[windowController window]
499                       modalDelegate:self
500                      didEndSelector:@selector(alertDidEnd:code:context:)
501                         contextInfo:NULL];
503     [alert release];
506 - (oneway void)processCommandQueue:(in bycopy NSArray *)queue
508     if (!isInitialized) return;
510     if (inProcessCommandQueue) {
511         // NOTE!  If a synchronous DO call is made during
512         // doProcessCommandQueue: below it may happen that this method is
513         // called a second time while the synchronous message is waiting for a
514         // reply (could also happen if doProcessCommandQueue: enters a modal
515         // loop, see comment below).  Since this method cannot be considered
516         // reentrant, we queue the input and return immediately.
517         //
518         // If doProcessCommandQueue: enters a modal loop (happens e.g. on
519         // ShowPopupMenuMsgID) then the receiveQueue could grow to become
520         // arbitrarily large because DO calls still get processed.  To avoid
521         // this we set a cap on the size of the queue and simply clear it if it
522         // becomes too large.  (That is messages will be dropped and hence Vim
523         // and MacVim will at least temporarily be out of sync.)
524         if ([receiveQueue count] >= MMReceiveQueueCap)
525             [receiveQueue removeAllObjects];
527         [receiveQueue addObject:queue];
528         return;
529     }
531     inProcessCommandQueue = YES;
532     [self doProcessCommandQueue:queue];
534     int i;
535     for (i = 0; i < [receiveQueue count]; ++i) {
536         // Note that doProcessCommandQueue: may cause the receiveQueue to grow
537         // or get cleared (due to cap being hit).  Make sure to retain the item
538         // to process or it may get released from under us.
539         NSArray *q = [[receiveQueue objectAtIndex:i] retain];
540         [self doProcessCommandQueue:q];
541         [q release];
542     }
544     // We assume that the remaining calls make no synchronous DO calls.  If
545     // that did happen anyway, the command queue could get processed out of
546     // order.
548     if ([sendQueue count] > 0) {
549         @try {
550             [backendProxy processInputAndData:sendQueue];
551         }
552         @catch (NSException *e) {
553             // Connection timed out, just ignore this.
554             //NSLog(@"WARNING! Connection timed out in %s", _cmd);
555         }
557         [sendQueue removeAllObjects];
558     }
560     [windowController processCommandQueueDidFinish];
561     [receiveQueue removeAllObjects];
562     inProcessCommandQueue = NO;
565 - (NSToolbarItem *)toolbar:(NSToolbar *)theToolbar
566     itemForItemIdentifier:(NSString *)itemId
567     willBeInsertedIntoToolbar:(BOOL)flag
569     NSToolbarItem *item = [toolbarItemDict objectForKey:itemId];
570     if (!item) {
571         NSLog(@"WARNING:  No toolbar item with id '%@'", itemId);
572     }
574     return item;
577 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)theToolbar
579     return nil;
582 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)theToolbar
584     return nil;
587 @end // MMVimController
591 @implementation MMVimController (Private)
593 - (void)doProcessCommandQueue:(NSArray *)queue
595     NSMutableArray *delayQueue = nil;
597     @try {
598         unsigned i, count = [queue count];
599         if (count % 2) {
600             NSLog(@"WARNING: Uneven number of components (%d) in command "
601                     "queue.  Skipping...", count);
602             return;
603         }
605         //NSLog(@"======== %s BEGIN ========", _cmd);
606         for (i = 0; i < count; i += 2) {
607             NSData *value = [queue objectAtIndex:i];
608             NSData *data = [queue objectAtIndex:i+1];
610             int msgid = *((int*)[value bytes]);
611             //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
613             BOOL inDefaultMode = [[[NSRunLoop currentRunLoop] currentMode]
614                                                 isEqual:NSDefaultRunLoopMode];
615             if (!inDefaultMode && isUnsafeMessage(msgid)) {
616                 // NOTE: Because we listen to DO messages in 'event tracking'
617                 // mode we have to take extra care when doing things like
618                 // releasing view items (and other Cocoa objects).  Messages
619                 // that may be potentially "unsafe" are delayed until the run
620                 // loop is back to default mode at which time they are safe to
621                 // call again.
622                 //   A problem with this approach is that it is hard to
623                 // classify which messages are unsafe.  As a rule of thumb, if
624                 // a message may release an object used by the Cocoa framework
625                 // (e.g. views) then the message should be considered unsafe.
626                 //   Delaying messages may have undesired side-effects since it
627                 // means that messages may not be processed in the order Vim
628                 // sent them, so beware.
629                 if (!delayQueue)
630                     delayQueue = [NSMutableArray array];
632                 //NSLog(@"Adding unsafe message '%s' to delay queue (mode=%@)",
633                 //        MessageStrings[msgid],
634                 //        [[NSRunLoop currentRunLoop] currentMode]);
635                 [delayQueue addObject:value];
636                 [delayQueue addObject:data];
637             } else {
638                 [self handleMessage:msgid data:data];
639             }
640         }
641         //NSLog(@"======== %s  END  ========", _cmd);
642     }
643     @catch (NSException *e) {
644         NSLog(@"Exception caught whilst processing command queue: %@", e);
645     }
647     if (delayQueue) {
648         //NSLog(@"    Flushing delay queue (%d items)", [delayQueue count]/2);
649         [self performSelectorOnMainThread:@selector(processCommandQueue:)
650                                withObject:delayQueue
651                             waitUntilDone:NO
652                                     modes:[NSArray arrayWithObject:
653                                            NSDefaultRunLoopMode]];
654     }
657 - (void)handleMessage:(int)msgid data:(NSData *)data
659     //if (msgid != AddMenuMsgID && msgid != AddMenuItemMsgID &&
660     //        msgid != EnableMenuItemMsgID)
661     //    NSLog(@"%@ %s%s", [self className], _cmd, MessageStrings[msgid]);
663     if (OpenVimWindowMsgID == msgid) {
664         [windowController openWindow];
665     } else if (BatchDrawMsgID == msgid) {
666         [[[windowController vimView] textView] performBatchDrawWithData:data];
667     } else if (SelectTabMsgID == msgid) {
668 #if 0   // NOTE: Tab selection is done inside updateTabsWithData:.
669         const void *bytes = [data bytes];
670         int idx = *((int*)bytes);
671         //NSLog(@"Selecting tab with index %d", idx);
672         [windowController selectTabWithIndex:idx];
673 #endif
674     } else if (UpdateTabBarMsgID == msgid) {
675         [windowController updateTabsWithData:data];
676     } else if (ShowTabBarMsgID == msgid) {
677         [windowController showTabBar:YES];
678     } else if (HideTabBarMsgID == msgid) {
679         [windowController showTabBar:NO];
680     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid) {
681         const void *bytes = [data bytes];
682         int rows = *((int*)bytes);  bytes += sizeof(int);
683         int cols = *((int*)bytes);  bytes += sizeof(int);
685         [windowController setTextDimensionsWithRows:rows columns:cols
686                                                live:(LiveResizeMsgID==msgid)];
687     } else if (SetWindowTitleMsgID == msgid) {
688         const void *bytes = [data bytes];
689         int len = *((int*)bytes);  bytes += sizeof(int);
691         NSString *string = [[NSString alloc] initWithBytes:(void*)bytes
692                 length:len encoding:NSUTF8StringEncoding];
694         // While in live resize the window title displays the dimensions of the
695         // window so don't clobber this with a spurious "set title" message
696         // from Vim.
697         if (![[windowController vimView] inLiveResize])
698             [windowController setTitle:string];
700         [string release];
701     } else if (SetDocumentFilenameMsgID == msgid) {
702         const void *bytes = [data bytes];
703         int len = *((int*)bytes);  bytes += sizeof(int);
705         if (len > 0) {
706             NSString *filename = [[NSString alloc] initWithBytes:(void*)bytes
707                     length:len encoding:NSUTF8StringEncoding];
709             [windowController setDocumentFilename:filename];
711             [filename release];
712         } else {
713             [windowController setDocumentFilename:@""];
714         }
715     } else if (AddMenuMsgID == msgid) {
716         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
717         [self addMenuWithDescriptor:[attrs objectForKey:@"descriptor"]
718                 atIndex:[[attrs objectForKey:@"index"] intValue]];
719     } else if (AddMenuItemMsgID == msgid) {
720         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
721         [self addMenuItemWithDescriptor:[attrs objectForKey:@"descriptor"]
722                       atIndex:[[attrs objectForKey:@"index"] intValue]
723                           tip:[attrs objectForKey:@"tip"]
724                          icon:[attrs objectForKey:@"icon"]
725                 keyEquivalent:[attrs objectForKey:@"keyEquivalent"]
726                  modifierMask:[[attrs objectForKey:@"modifierMask"] intValue]
727                        action:[attrs objectForKey:@"action"]
728                   isAlternate:[[attrs objectForKey:@"isAlternate"] boolValue]];
729     } else if (RemoveMenuItemMsgID == msgid) {
730         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
731         [self removeMenuItemWithDescriptor:[attrs objectForKey:@"descriptor"]];
732     } else if (EnableMenuItemMsgID == msgid) {
733         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
734         [self enableMenuItemWithDescriptor:[attrs objectForKey:@"descriptor"]
735                 state:[[attrs objectForKey:@"enable"] boolValue]];
736     } else if (ShowToolbarMsgID == msgid) {
737         const void *bytes = [data bytes];
738         int enable = *((int*)bytes);  bytes += sizeof(int);
739         int flags = *((int*)bytes);  bytes += sizeof(int);
741         int mode = NSToolbarDisplayModeDefault;
742         if (flags & ToolbarLabelFlag) {
743             mode = flags & ToolbarIconFlag ? NSToolbarDisplayModeIconAndLabel
744                     : NSToolbarDisplayModeLabelOnly;
745         } else if (flags & ToolbarIconFlag) {
746             mode = NSToolbarDisplayModeIconOnly;
747         }
749         int size = flags & ToolbarSizeRegularFlag ? NSToolbarSizeModeRegular
750                 : NSToolbarSizeModeSmall;
752         [windowController showToolbar:enable size:size mode:mode];
753     } else if (CreateScrollbarMsgID == msgid) {
754         const void *bytes = [data bytes];
755         long ident = *((long*)bytes);  bytes += sizeof(long);
756         int type = *((int*)bytes);  bytes += sizeof(int);
758         [windowController createScrollbarWithIdentifier:ident type:type];
759     } else if (DestroyScrollbarMsgID == msgid) {
760         const void *bytes = [data bytes];
761         long ident = *((long*)bytes);  bytes += sizeof(long);
763         [windowController destroyScrollbarWithIdentifier:ident];
764     } else if (ShowScrollbarMsgID == msgid) {
765         const void *bytes = [data bytes];
766         long ident = *((long*)bytes);  bytes += sizeof(long);
767         int visible = *((int*)bytes);  bytes += sizeof(int);
769         [windowController showScrollbarWithIdentifier:ident state:visible];
770     } else if (SetScrollbarPositionMsgID == msgid) {
771         const void *bytes = [data bytes];
772         long ident = *((long*)bytes);  bytes += sizeof(long);
773         int pos = *((int*)bytes);  bytes += sizeof(int);
774         int len = *((int*)bytes);  bytes += sizeof(int);
776         [windowController setScrollbarPosition:pos length:len
777                                     identifier:ident];
778     } else if (SetScrollbarThumbMsgID == msgid) {
779         const void *bytes = [data bytes];
780         long ident = *((long*)bytes);  bytes += sizeof(long);
781         float val = *((float*)bytes);  bytes += sizeof(float);
782         float prop = *((float*)bytes);  bytes += sizeof(float);
784         [windowController setScrollbarThumbValue:val proportion:prop
785                                       identifier:ident];
786     } else if (SetFontMsgID == msgid) {
787         const void *bytes = [data bytes];
788         float size = *((float*)bytes);  bytes += sizeof(float);
789         int len = *((int*)bytes);  bytes += sizeof(int);
790         NSString *name = [[NSString alloc]
791                 initWithBytes:(void*)bytes length:len
792                      encoding:NSUTF8StringEncoding];
793         NSFont *font = [NSFont fontWithName:name size:size];
795         if (font)
796             [windowController setFont:font];
798         [name release];
799     } else if (SetWideFontMsgID == msgid) {
800         const void *bytes = [data bytes];
801         float size = *((float*)bytes);  bytes += sizeof(float);
802         int len = *((int*)bytes);  bytes += sizeof(int);
803         if (len > 0) {
804             NSString *name = [[NSString alloc]
805                     initWithBytes:(void*)bytes length:len
806                          encoding:NSUTF8StringEncoding];
807             NSFont *font = [NSFont fontWithName:name size:size];
808             [windowController setWideFont:font];
810             [name release];
811         } else {
812             [windowController setWideFont:nil];
813         }
814     } else if (SetDefaultColorsMsgID == msgid) {
815         const void *bytes = [data bytes];
816         unsigned bg = *((unsigned*)bytes);  bytes += sizeof(unsigned);
817         unsigned fg = *((unsigned*)bytes);  bytes += sizeof(unsigned);
818         NSColor *back = [NSColor colorWithArgbInt:bg];
819         NSColor *fore = [NSColor colorWithRgbInt:fg];
821         [windowController setDefaultColorsBackground:back foreground:fore];
822     } else if (ExecuteActionMsgID == msgid) {
823         const void *bytes = [data bytes];
824         int len = *((int*)bytes);  bytes += sizeof(int);
825         NSString *actionName = [[NSString alloc]
826                 initWithBytes:(void*)bytes length:len
827                      encoding:NSUTF8StringEncoding];
829         SEL sel = NSSelectorFromString(actionName);
830         [NSApp sendAction:sel to:nil from:self];
832         [actionName release];
833     } else if (ShowPopupMenuMsgID == msgid) {
834         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
836         // The popup menu enters a modal loop so delay this call so that we
837         // don't block inside processCommandQueue:.
838         [self performSelectorOnMainThread:@selector(popupMenuWithAttributes:)
839                              withObject:attrs
840                           waitUntilDone:NO
841                                   modes:[NSArray arrayWithObject:
842                                          NSDefaultRunLoopMode]];
843     } else if (SetMouseShapeMsgID == msgid) {
844         const void *bytes = [data bytes];
845         int shape = *((int*)bytes);  bytes += sizeof(int);
847         [windowController setMouseShape:shape];
848     } else if (AdjustLinespaceMsgID == msgid) {
849         const void *bytes = [data bytes];
850         int linespace = *((int*)bytes);  bytes += sizeof(int);
852         [windowController adjustLinespace:linespace];
853     } else if (ActivateMsgID == msgid) {
854         //NSLog(@"ActivateMsgID");
855         [NSApp activateIgnoringOtherApps:YES];
856         [[windowController window] makeKeyAndOrderFront:self];
857     } else if (SetServerNameMsgID == msgid) {
858         NSString *name = [[NSString alloc] initWithData:data
859                                                encoding:NSUTF8StringEncoding];
860         [self setServerName:name];
861         [name release];
862     } else if (EnterFullscreenMsgID == msgid) {
863         const void *bytes = [data bytes];
864         int fuoptions = *((int*)bytes); bytes += sizeof(int);
865         int bg = *((int*)bytes);
866         NSColor *back = [NSColor colorWithArgbInt:bg];
868         [windowController enterFullscreen:fuoptions backgroundColor:back];
869     } else if (LeaveFullscreenMsgID == msgid) {
870         [windowController leaveFullscreen];
871     } else if (BuffersNotModifiedMsgID == msgid) {
872         [windowController setBuffersModified:NO];
873     } else if (BuffersModifiedMsgID == msgid) {
874         [windowController setBuffersModified:YES];
875     } else if (SetPreEditPositionMsgID == msgid) {
876         const int *dim = (const int*)[data bytes];
877         [[[windowController vimView] textView] setPreEditRow:dim[0]
878                                                       column:dim[1]];
879     } else if (EnableAntialiasMsgID == msgid) {
880         [[[windowController vimView] textView] setAntialias:YES];
881     } else if (DisableAntialiasMsgID == msgid) {
882         [[[windowController vimView] textView] setAntialias:NO];
883     } else if (SetVimStateMsgID == msgid) {
884         NSDictionary *dict = [NSDictionary dictionaryWithData:data];
885         if (dict) {
886             [vimState release];
887             vimState = [dict retain];
888         }
889     } else if (CloseWindowMsgID == msgid) {
890         [self scheduleClose];
891     // IMPORTANT: When adding a new message, make sure to update
892     // isUnsafeMessage() if necessary!
893     } else {
894         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
895     }
898 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
899                 context:(void *)context
901     NSString *path = (code == NSOKButton) ? [panel filename] : nil;
903     // NOTE! setDialogReturn: is a synchronous call so set a proper timeout to
904     // avoid waiting forever for it to finish.  We make this a synchronous call
905     // so that we can be fairly certain that Vim doesn't think the dialog box
906     // is still showing when MacVim has in fact already dismissed it.
907     NSConnection *conn = [backendProxy connectionForProxy];
908     NSTimeInterval oldTimeout = [conn requestTimeout];
909     [conn setRequestTimeout:MMSetDialogReturnTimeout];
911     @try {
912         [backendProxy setDialogReturn:path];
914         // Add file to the "Recent Files" menu (this ensures that files that
915         // are opened/saved from a :browse command are added to this menu).
916         if (path)
917             [[NSDocumentController sharedDocumentController]
918                     noteNewRecentFilePath:path];
919     }
920     @catch (NSException *e) {
921         NSLog(@"Exception caught in %s %@", _cmd, e);
922     }
923     @finally {
924         [conn setRequestTimeout:oldTimeout];
925     }
928 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context
930     NSArray *ret = nil;
932     code = code - NSAlertFirstButtonReturn + 1;
934     if ([alert isKindOfClass:[MMAlert class]] && [alert textField]) {
935         ret = [NSArray arrayWithObjects:[NSNumber numberWithInt:code],
936             [[alert textField] stringValue], nil];
937     } else {
938         ret = [NSArray arrayWithObject:[NSNumber numberWithInt:code]];
939     }
941     @try {
942         [backendProxy setDialogReturn:ret];
943     }
944     @catch (NSException *e) {
945         NSLog(@"Exception caught in %s %@", _cmd, e);
946     }
949 - (NSMenuItem *)menuItemForDescriptor:(NSArray *)desc
951     if (!(desc && [desc count] > 0)) return nil;
953     NSString *rootName = [desc objectAtIndex:0];
954     NSArray *rootItems = [rootName hasPrefix:@"PopUp"] ? popupMenuItems
955                                                        : [mainMenu itemArray];
957     NSMenuItem *item = nil;
958     int i, count = [rootItems count];
959     for (i = 0; i < count; ++i) {
960         item = [rootItems objectAtIndex:i];
961         if ([[item title] isEqual:rootName])
962             break;
963     }
965     if (i == count) return nil;
967     count = [desc count];
968     for (i = 1; i < count; ++i) {
969         item = [[item submenu] itemWithTitle:[desc objectAtIndex:i]];
970         if (!item) return nil;
971     }
973     return item;
976 - (NSMenu *)parentMenuForDescriptor:(NSArray *)desc
978     if (!(desc && [desc count] > 0)) return nil;
980     NSString *rootName = [desc objectAtIndex:0];
981     NSArray *rootItems = [rootName hasPrefix:@"PopUp"] ? popupMenuItems
982                                                        : [mainMenu itemArray];
984     NSMenu *menu = nil;
985     int i, count = [rootItems count];
986     for (i = 0; i < count; ++i) {
987         NSMenuItem *item = [rootItems objectAtIndex:i];
988         if ([[item title] isEqual:rootName]) {
989             menu = [item submenu];
990             break;
991         }
992     }
994     if (!menu) return nil;
996     count = [desc count] - 1;
997     for (i = 1; i < count; ++i) {
998         NSMenuItem *item = [menu itemWithTitle:[desc objectAtIndex:i]];
999         menu = [item submenu];
1000         if (!menu) return nil;
1001     }
1003     return menu;
1006 - (NSMenu *)topLevelMenuForTitle:(NSString *)title
1008     // Search only the top-level menus.
1010     unsigned i, count = [popupMenuItems count];
1011     for (i = 0; i < count; ++i) {
1012         NSMenuItem *item = [popupMenuItems objectAtIndex:i];
1013         if ([title isEqual:[item title]])
1014             return [item submenu];
1015     }
1017     count = [mainMenu numberOfItems];
1018     for (i = 0; i < count; ++i) {
1019         NSMenuItem *item = [mainMenu itemAtIndex:i];
1020         if ([title isEqual:[item title]])
1021             return [item submenu];
1022     }
1024     return nil;
1027 - (void)addMenuWithDescriptor:(NSArray *)desc atIndex:(int)idx
1029     if (!(desc && [desc count] > 0 && idx >= 0)) return;
1031     NSString *rootName = [desc objectAtIndex:0];
1032     if ([rootName isEqual:@"ToolBar"]) {
1033         // The toolbar only has one menu, we take this as a hint to create a
1034         // toolbar, then we return.
1035         if (!toolbar) {
1036             // NOTE! Each toolbar must have a unique identifier, else each
1037             // window will have the same toolbar.
1038             NSString *ident = [NSString stringWithFormat:@"%d", (int)self];
1039             toolbar = [[NSToolbar alloc] initWithIdentifier:ident];
1041             [toolbar setShowsBaselineSeparator:NO];
1042             [toolbar setDelegate:self];
1043             [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
1044             [toolbar setSizeMode:NSToolbarSizeModeSmall];
1046             [windowController setToolbar:toolbar];
1047         }
1049         return;
1050     }
1052     // This is either a main menu item or a popup menu item.
1053     NSString *title = [desc lastObject];
1054     NSMenuItem *item = [[NSMenuItem alloc] init];
1055     NSMenu *menu = [[NSMenu alloc] initWithTitle:title];
1057     [item setTitle:title];
1058     [item setSubmenu:menu];
1060     NSMenu *parent = [self parentMenuForDescriptor:desc];
1061     if (!parent && [rootName hasPrefix:@"PopUp"]) {
1062         if ([popupMenuItems count] <= idx) {
1063             [popupMenuItems addObject:item];
1064         } else {
1065             [popupMenuItems insertObject:item atIndex:idx];
1066         }
1067     } else {
1068         // If descriptor has no parent and its not a popup (or toolbar) menu,
1069         // then it must belong to main menu.
1070         if (!parent) parent = mainMenu;
1072         if ([parent numberOfItems] <= idx) {
1073             [parent addItem:item];
1074         } else {
1075             [parent insertItem:item atIndex:idx];
1076         }
1077     }
1079     [item release];
1080     [menu release];
1083 - (void)addMenuItemWithDescriptor:(NSArray *)desc
1084                           atIndex:(int)idx
1085                               tip:(NSString *)tip
1086                              icon:(NSString *)icon
1087                     keyEquivalent:(NSString *)keyEquivalent
1088                      modifierMask:(int)modifierMask
1089                            action:(NSString *)action
1090                       isAlternate:(BOOL)isAlternate
1092     if (!(desc && [desc count] > 1 && idx >= 0)) return;
1094     NSString *title = [desc lastObject];
1095     NSString *rootName = [desc objectAtIndex:0];
1097     if ([rootName isEqual:@"ToolBar"]) {
1098         if (toolbar && [desc count] == 2)
1099             [self addToolbarItemWithLabel:title tip:tip icon:icon atIndex:idx];
1100         return;
1101     }
1103     NSMenu *parent = [self parentMenuForDescriptor:desc];
1104     if (!parent) {
1105         NSLog(@"WARNING: Menu item '%@' has no parent",
1106                 [desc componentsJoinedByString:@"->"]);
1107         return;
1108     }
1110     NSMenuItem *item = nil;
1111     if (0 == [title length]
1112             || ([title hasPrefix:@"-"] && [title hasSuffix:@"-"])) {
1113         item = [NSMenuItem separatorItem];
1114         [item setTitle:title];
1115     } else {
1116         item = [[[NSMenuItem alloc] init] autorelease];
1117         [item setTitle:title];
1119         // Note: It is possible to set the action to a message that "doesn't
1120         // exist" without problems.  We take advantage of this when adding
1121         // "dummy items" e.g. when dealing with the "Recent Files" menu (in
1122         // which case a recentFilesDummy: action is set, although it is never
1123         // used).
1124         if ([action length] > 0)
1125             [item setAction:NSSelectorFromString(action)];
1126         else
1127             [item setAction:@selector(vimMenuItemAction:)];
1128         if ([tip length] > 0) [item setToolTip:tip];
1129         if ([keyEquivalent length] > 0) {
1130             [item setKeyEquivalent:keyEquivalent];
1131             [item setKeyEquivalentModifierMask:modifierMask];
1132         }
1133         [item setAlternate:isAlternate];
1135         // The tag is used to indicate whether Vim thinks a menu item should be
1136         // enabled or disabled.  By default Vim thinks menu items are enabled.
1137         [item setTag:1];
1138     }
1140     if ([parent numberOfItems] <= idx) {
1141         [parent addItem:item];
1142     } else {
1143         [parent insertItem:item atIndex:idx];
1144     }
1147 - (void)removeMenuItemWithDescriptor:(NSArray *)desc
1149     if (!(desc && [desc count] > 0)) return;
1151     NSString *title = [desc lastObject];
1152     NSString *rootName = [desc objectAtIndex:0];
1153     if ([rootName isEqual:@"ToolBar"]) {
1154         if (toolbar) {
1155             // Only remove toolbar items, never actually remove the toolbar
1156             // itself or strange things may happen.
1157             if ([desc count] == 2) {
1158                 int idx = [toolbar indexOfItemWithItemIdentifier:title];
1159                 if (idx != NSNotFound)
1160                     [toolbar removeItemAtIndex:idx];
1161             }
1162         }
1163         return;
1164     }
1166     NSMenuItem *item = [self menuItemForDescriptor:desc];
1167     if (!item) {
1168         NSLog(@"Failed to remove menu item, descriptor not found: %@",
1169                 [desc componentsJoinedByString:@"->"]);
1170         return;
1171     }
1173     [item retain];
1175     if ([item menu] == [NSApp mainMenu] || ![item menu]) {
1176         // NOTE: To be on the safe side we try to remove the item from
1177         // both arrays (it is ok to call removeObject: even if an array
1178         // does not contain the object to remove).
1179         [popupMenuItems removeObject:item];
1180     }
1182     if ([item menu])
1183         [[item menu] removeItem:item];
1185     [item release];
1188 - (void)enableMenuItemWithDescriptor:(NSArray *)desc state:(BOOL)on
1190     if (!(desc && [desc count] > 0)) return;
1192     /*NSLog(@"%sable item %@", on ? "En" : "Dis",
1193             [desc componentsJoinedByString:@"->"]);*/
1195     NSString *rootName = [desc objectAtIndex:0];
1196     if ([rootName isEqual:@"ToolBar"]) {
1197         if (toolbar && [desc count] == 2) {
1198             NSString *title = [desc lastObject];
1199             [[toolbar itemWithItemIdentifier:title] setEnabled:on];
1200         }
1201     } else {
1202         // Use tag to set whether item is enabled or disabled instead of
1203         // calling setEnabled:.  This way the menus can autoenable themselves
1204         // but at the same time Vim can set if a menu is enabled whenever it
1205         // wants to.
1206         [[self menuItemForDescriptor:desc] setTag:on];
1207     }
1210 - (void)addToolbarItemToDictionaryWithLabel:(NSString *)title
1211                                     toolTip:(NSString *)tip
1212                                        icon:(NSString *)icon
1214     // If the item corresponds to a separator then do nothing, since it is
1215     // already defined by Cocoa.
1216     if (!title || [title isEqual:NSToolbarSeparatorItemIdentifier]
1217                || [title isEqual:NSToolbarSpaceItemIdentifier]
1218                || [title isEqual:NSToolbarFlexibleSpaceItemIdentifier])
1219         return;
1221     NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:title];
1222     [item setLabel:title];
1223     [item setToolTip:tip];
1224     [item setAction:@selector(vimToolbarItemAction:)];
1225     [item setAutovalidates:NO];
1227     NSImage *img = [NSImage imageNamed:icon];
1228     if (!img)
1229         img = [[[NSImage alloc] initByReferencingFile:icon] autorelease];
1230     if (!img) {
1231         NSLog(@"WARNING: Could not find image with name '%@' to use as toolbar"
1232                " image for identifier '%@';"
1233                " using default toolbar icon '%@' instead.",
1234                icon, title, MMDefaultToolbarImageName);
1236         img = [NSImage imageNamed:MMDefaultToolbarImageName];
1237     }
1239     [item setImage:img];
1241     [toolbarItemDict setObject:item forKey:title];
1243     [item release];
1246 - (void)addToolbarItemWithLabel:(NSString *)label
1247                             tip:(NSString *)tip
1248                            icon:(NSString *)icon
1249                         atIndex:(int)idx
1251     if (!toolbar) return;
1253     // Check for separator items.
1254     if (!label) {
1255         label = NSToolbarSeparatorItemIdentifier;
1256     } else if ([label length] >= 2 && [label hasPrefix:@"-"]
1257                                    && [label hasSuffix:@"-"]) {
1258         // The label begins and ends with '-'; decided which kind of separator
1259         // item it is by looking at the prefix.
1260         if ([label hasPrefix:@"-space"]) {
1261             label = NSToolbarSpaceItemIdentifier;
1262         } else if ([label hasPrefix:@"-flexspace"]) {
1263             label = NSToolbarFlexibleSpaceItemIdentifier;
1264         } else {
1265             label = NSToolbarSeparatorItemIdentifier;
1266         }
1267     }
1269     [self addToolbarItemToDictionaryWithLabel:label toolTip:tip icon:icon];
1271     int maxIdx = [[toolbar items] count];
1272     if (maxIdx < idx) idx = maxIdx;
1274     [toolbar insertItemWithItemIdentifier:label atIndex:idx];
1277 - (void)popupMenuWithDescriptor:(NSArray *)desc
1278                           atRow:(NSNumber *)row
1279                          column:(NSNumber *)col
1281     NSMenu *menu = [[self menuItemForDescriptor:desc] submenu];
1282     if (!menu) return;
1284     id textView = [[windowController vimView] textView];
1285     NSPoint pt;
1286     if (row && col) {
1287         // TODO: Let textView convert (row,col) to NSPoint.
1288         int r = [row intValue];
1289         int c = [col intValue];
1290         NSSize cellSize = [textView cellSize];
1291         pt = NSMakePoint((c+1)*cellSize.width, (r+1)*cellSize.height);
1292         pt = [textView convertPoint:pt toView:nil];
1293     } else {
1294         pt = [[windowController window] mouseLocationOutsideOfEventStream];
1295     }
1297     NSEvent *event = [NSEvent mouseEventWithType:NSRightMouseDown
1298                            location:pt
1299                       modifierFlags:0
1300                           timestamp:0
1301                        windowNumber:[[windowController window] windowNumber]
1302                             context:nil
1303                         eventNumber:0
1304                          clickCount:0
1305                            pressure:1.0];
1307     [NSMenu popUpContextMenu:menu withEvent:event forView:textView];
1310 - (void)popupMenuWithAttributes:(NSDictionary *)attrs
1312     if (!attrs) return;
1314     [self popupMenuWithDescriptor:[attrs objectForKey:@"descriptor"]
1315                             atRow:[attrs objectForKey:@"row"]
1316                            column:[attrs objectForKey:@"column"]];
1319 - (void)connectionDidDie:(NSNotification *)notification
1321     //NSLog(@"%@ %s%@", [self className], _cmd, notification);
1322     [self scheduleClose];
1325 - (void)scheduleClose
1327     // NOTE!  This message can arrive at pretty much anytime, e.g. while
1328     // the run loop is the 'event tracking' mode.  This means that Cocoa may
1329     // well be in the middle of processing some message while this message is
1330     // received.  If we were to remove the vim controller straight away we may
1331     // free objects that Cocoa is currently using (e.g. view objects).  The
1332     // following call ensures that the vim controller is not released until the
1333     // run loop is back in the 'default' mode.
1334     [[MMAppController sharedInstance]
1335             performSelectorOnMainThread:@selector(removeVimController:)
1336                              withObject:self
1337                           waitUntilDone:NO
1338                                   modes:[NSArray arrayWithObject:
1339                                          NSDefaultRunLoopMode]];
1342 @end // MMVimController (Private)
1347 @implementation MMAlert
1348 - (void)dealloc
1350     [textField release];  textField = nil;
1351     [super dealloc];
1354 - (void)setTextFieldString:(NSString *)textFieldString
1356     [textField release];
1357     textField = [[NSTextField alloc] init];
1358     [textField setStringValue:textFieldString];
1361 - (NSTextField *)textField
1363     return textField;
1366 - (void)setInformativeText:(NSString *)text
1368     if (textField) {
1369         // HACK! Add some space for the text field.
1370         [super setInformativeText:[text stringByAppendingString:@"\n\n\n"]];
1371     } else {
1372         [super setInformativeText:text];
1373     }
1376 - (void)beginSheetModalForWindow:(NSWindow *)window
1377                    modalDelegate:(id)delegate
1378                   didEndSelector:(SEL)didEndSelector
1379                      contextInfo:(void *)contextInfo
1381     [super beginSheetModalForWindow:window
1382                       modalDelegate:delegate
1383                      didEndSelector:didEndSelector
1384                         contextInfo:contextInfo];
1386     // HACK! Place the input text field at the bottom of the informative text
1387     // (which has been made a bit larger by adding newline characters).
1388     NSView *contentView = [[self window] contentView];
1389     NSRect rect = [contentView frame];
1390     rect.origin.y = rect.size.height;
1392     NSArray *subviews = [contentView subviews];
1393     unsigned i, count = [subviews count];
1394     for (i = 0; i < count; ++i) {
1395         NSView *view = [subviews objectAtIndex:i];
1396         if ([view isKindOfClass:[NSTextField class]]
1397                 && [view frame].origin.y < rect.origin.y) {
1398             // NOTE: The informative text field is the lowest NSTextField in
1399             // the alert dialog.
1400             rect = [view frame];
1401         }
1402     }
1404     rect.size.height = MMAlertTextFieldHeight;
1405     [textField setFrame:rect];
1406     [contentView addSubview:textField];
1407     [textField becomeFirstResponder];
1410 @end // MMAlert
1415     static BOOL
1416 isUnsafeMessage(int msgid)
1418     // Messages that may release Cocoa objects must be added to this list.  For
1419     // example, UpdateTabBarMsgID may delete NSTabViewItem objects so it goes
1420     // on this list.
1421     static int unsafeMessages[] = { // REASON MESSAGE IS ON THIS LIST:
1422         OpenVimWindowMsgID,         //   Changes lots of state
1423         UpdateTabBarMsgID,          //   May delete NSTabViewItem
1424         RemoveMenuItemMsgID,        //   Deletes NSMenuItem
1425         DestroyScrollbarMsgID,      //   Deletes NSScroller
1426         ExecuteActionMsgID,         //   Impossible to predict
1427         ShowPopupMenuMsgID,         //   Enters modal loop
1428         ActivateMsgID,              //   ?
1429         EnterFullscreenMsgID,       //   Modifies delegate of window controller
1430         LeaveFullscreenMsgID,       //   Modifies delegate of window controller
1431     };
1433     int i, count = sizeof(unsafeMessages)/sizeof(unsafeMessages[0]);
1434     for (i = 0; i < count; ++i)
1435         if (msgid == unsafeMessages[i])
1436             return YES;
1438     return NO;