1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
3 * VIM - Vi IMproved by Bram Moolenaar
4 * MacVim GUI port by Bjorn Winckler
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.
13 * Coordinates input/output to/from backend. Each MMBackend communicates
14 * directly with a MMVimController.
16 * MMVimController does not deal with visual presentation. Essentially it
17 * should be able to run with no window present.
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 strings 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.
26 #import "MMAppController.h"
27 #import "MMAtsuiTextView.h"
28 #import "MMFindReplaceController.h"
29 #import "MMTextView.h"
30 #import "MMVimController.h"
32 #import "MMWindowController.h"
33 #import "Miscellaneous.h"
35 #ifdef MM_ENABLE_PLUGINS
36 #import "MMPlugInManager.h"
39 static NSString *MMDefaultToolbarImageName = @"Attention";
40 static int MMAlertTextFieldHeight = 22;
42 // NOTE: By default a message sent to the backend will be dropped if it cannot
43 // be delivered instantly; otherwise there is a possibility that MacVim will
44 // 'beachball' while waiting to deliver DO messages to an unresponsive Vim
45 // process. This means that you cannot rely on any message sent with
46 // sendMessage: to actually reach Vim.
47 static NSTimeInterval MMBackendProxyRequestTimeout = 0;
49 // Timeout used for setDialogReturn:.
50 static NSTimeInterval MMSetDialogReturnTimeout = 1.0;
52 // Maximum number of items in the receiveQueue. (It is hard to predict what
53 // consequences changing this number will have.)
54 static int MMReceiveQueueCap = 100;
56 static BOOL isUnsafeMessage(int msgid);
59 @interface MMAlert : NSAlert {
60 NSTextField *textField;
62 - (void)setTextFieldString:(NSString *)textFieldString;
63 - (NSTextField *)textField;
67 @interface MMVimController (Private)
68 - (void)doProcessCommandQueue:(NSArray *)queue;
69 - (void)handleMessage:(int)msgid data:(NSData *)data;
70 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
71 context:(void *)context;
72 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context;
73 - (NSMenuItem *)menuItemForDescriptor:(NSArray *)desc;
74 - (NSMenu *)parentMenuForDescriptor:(NSArray *)desc;
75 - (NSMenu *)topLevelMenuForTitle:(NSString *)title;
76 - (void)addMenuWithDescriptor:(NSArray *)desc atIndex:(int)index;
77 - (void)addMenuItemWithDescriptor:(NSArray *)desc
81 keyEquivalent:(NSString *)keyEquivalent
82 modifierMask:(int)modifierMask
83 action:(NSString *)action
84 isAlternate:(BOOL)isAlternate;
85 - (void)removeMenuItemWithDescriptor:(NSArray *)desc;
86 - (void)enableMenuItemWithDescriptor:(NSArray *)desc state:(BOOL)on;
87 - (void)addToolbarItemToDictionaryWithLabel:(NSString *)title
88 toolTip:(NSString *)tip icon:(NSString *)icon;
89 - (void)addToolbarItemWithLabel:(NSString *)label
90 tip:(NSString *)tip icon:(NSString *)icon
92 - (void)popupMenuWithDescriptor:(NSArray *)desc
94 column:(NSNumber *)col;
95 - (void)popupMenuWithAttributes:(NSDictionary *)attrs;
96 - (void)connectionDidDie:(NSNotification *)notification;
97 - (void)scheduleClose;
103 @implementation MMVimController
105 - (id)initWithBackend:(id)backend pid:(int)processIdentifier
107 if (!(self = [super init]))
111 [[MMWindowController alloc] initWithVimController:self];
112 backendProxy = [backend retain];
113 sendQueue = [NSMutableArray new];
114 receiveQueue = [NSMutableArray new];
115 popupMenuItems = [[NSMutableArray alloc] init];
116 toolbarItemDict = [[NSMutableDictionary alloc] init];
117 pid = processIdentifier;
118 creationDate = [[NSDate alloc] init];
120 NSConnection *connection = [backendProxy connectionForProxy];
122 // TODO: Check that this will not set the timeout for the root proxy
123 // (in MMAppController).
124 [connection setRequestTimeout:MMBackendProxyRequestTimeout];
126 [[NSNotificationCenter defaultCenter] addObserver:self
127 selector:@selector(connectionDidDie:)
128 name:NSConnectionDidDieNotification object:connection];
130 // Set up a main menu with only a "MacVim" menu (copied from a template
131 // which itself is set up in MainMenu.nib). The main menu is populated
133 mainMenu = [[NSMenu alloc] initWithTitle:@"MainMenu"];
134 NSMenuItem *appMenuItem = [[MMAppController sharedInstance]
135 appMenuItemTemplate];
136 appMenuItem = [[appMenuItem copy] autorelease];
138 // Note: If the title of the application menu is anything but what
139 // CFBundleName says then the application menu will not be typeset in
140 // boldface for some reason. (It should already be set when we copy
141 // from the default main menu, but this is not the case for some
143 NSString *appName = [[NSBundle mainBundle]
144 objectForInfoDictionaryKey:@"CFBundleName"];
145 [appMenuItem setTitle:appName];
147 [mainMenu addItem:appMenuItem];
149 #ifdef MM_ENABLE_PLUGINS
150 instanceMediator = [[MMPlugInInstanceMediator alloc]
151 initWithVimController:self];
165 #ifdef MM_ENABLE_PLUGINS
166 [instanceMediator release]; instanceMediator = nil;
169 [serverName release]; serverName = nil;
170 [backendProxy release]; backendProxy = nil;
171 [sendQueue release]; sendQueue = nil;
172 [receiveQueue release]; receiveQueue = nil;
174 [toolbarItemDict release]; toolbarItemDict = nil;
175 [toolbar release]; toolbar = nil;
176 [popupMenuItems release]; popupMenuItems = nil;
177 [windowController release]; windowController = nil;
179 [vimState release]; vimState = nil;
180 [mainMenu release]; mainMenu = nil;
181 [creationDate release]; creationDate = nil;
186 - (MMWindowController *)windowController
188 return windowController;
191 #ifdef MM_ENABLE_PLUGINS
192 - (MMPlugInInstanceMediator *)instanceMediator
194 return instanceMediator;
198 - (NSDictionary *)vimState
213 - (void)setIsPreloading:(BOOL)yn
218 - (NSDate *)creationDate
223 - (void)setServerName:(NSString *)name
225 if (name != serverName) {
226 [serverName release];
227 serverName = [name copy];
231 - (NSString *)serverName
241 - (void)dropFiles:(NSArray *)filenames forceOpen:(BOOL)force
243 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
245 // Default to opening in tabs if layout is invalid or set to "windows".
246 int layout = [ud integerForKey:MMOpenLayoutKey];
247 if (layout < 0 || layout > MMLayoutTabs)
248 layout = MMLayoutTabs;
250 NSDictionary *args = [NSDictionary dictionaryWithObjectsAndKeys:
251 [NSNumber numberWithInt:layout], @"layout",
252 filenames, @"filenames",
253 [NSNumber numberWithBool:force], @"forceOpen",
256 [self sendMessage:DropFilesMsgID data:[args dictionaryAsData]];
259 - (void)file:(NSString *)filename draggedToTabAtIndex:(NSUInteger)tabIndex
261 NSString *fnEsc = [filename stringByEscapingSpecialFilenameCharacters];
262 NSString *input = [NSString stringWithFormat:@"<C-\\><C-N>:silent "
264 "edit! %@<CR>", tabIndex + 1, fnEsc];
265 [self addVimInput:input];
268 - (void)filesDraggedToTabBar:(NSArray *)filenames
270 NSUInteger i, count = [filenames count];
271 NSMutableString *input = [NSMutableString stringWithString:@"<C-\\><C-N>"
272 ":silent! tabnext 9999"];
273 for (i = 0; i < count; i++) {
274 NSString *fn = [filenames objectAtIndex:i];
275 NSString *fnEsc = [fn stringByEscapingSpecialFilenameCharacters];
276 [input appendFormat:@"|tabedit %@", fnEsc];
278 [input appendString:@"<CR>"];
279 [self addVimInput:input];
282 - (void)dropString:(NSString *)string
284 int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
286 NSMutableData *data = [NSMutableData data];
288 [data appendBytes:&len length:sizeof(int)];
289 [data appendBytes:[string UTF8String] length:len];
291 [self sendMessage:DropStringMsgID data:data];
295 - (void)passArguments:(NSDictionary *)args
299 [self sendMessage:OpenWithArgumentsMsgID data:[args dictionaryAsData]];
301 // HACK! Fool findUnusedEditor into thinking that this controller is not
302 // unused anymore, in case it is called before the arguments have reached
303 // the Vim process. This should be a "safe" hack since the next time the
304 // Vim process flushes its output queue the state will be updated again (at
305 // which time the "unusedEditor" state will have been properly set).
306 NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:
308 [dict setObject:[NSNumber numberWithBool:NO] forKey:@"unusedEditor"];
310 vimState = [dict copy];
313 - (void)sendMessage:(int)msgid data:(NSData *)data
315 //NSLog(@"sendMessage:%s (isInitialized=%d inProcessCommandQueue=%d)",
316 // MessageStrings[msgid], isInitialized, inProcessCommandQueue);
318 if (!isInitialized) return;
320 if (inProcessCommandQueue) {
321 //NSLog(@"In process command queue; delaying message send.");
322 [sendQueue addObject:[NSNumber numberWithInt:msgid]];
324 [sendQueue addObject:data];
326 [sendQueue addObject:[NSNull null]];
331 [backendProxy processInput:msgid data:data];
333 @catch (NSException *e) {
334 //NSLog(@"%@ %s Exception caught during DO call: %@",
335 // [self className], _cmd, e);
339 - (BOOL)sendMessageNow:(int)msgid data:(NSData *)data
340 timeout:(NSTimeInterval)timeout
342 // Send a message with a timeout. USE WITH EXTREME CAUTION! Sending
343 // messages in rapid succession with a timeout may cause MacVim to beach
344 // ball forever. In almost all circumstances sendMessage:data: should be
347 if (!isInitialized || inProcessCommandQueue)
350 if (timeout < 0) timeout = 0;
353 NSConnection *conn = [backendProxy connectionForProxy];
354 NSTimeInterval oldTimeout = [conn requestTimeout];
356 [conn setRequestTimeout:timeout];
359 [backendProxy processInput:msgid data:data];
361 @catch (NSException *e) {
365 [conn setRequestTimeout:oldTimeout];
371 - (void)addVimInput:(NSString *)string
373 // This is a very general method of adding input to the Vim process. It is
374 // basically the same as calling remote_send() on the process (see
375 // ':h remote_send').
377 NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
378 [self sendMessage:AddInputMsgID data:data];
382 - (NSString *)evaluateVimExpression:(NSString *)expr
384 NSString *eval = nil;
387 eval = [backendProxy evaluateExpression:expr];
389 @catch (NSException *ex) { /* do nothing */ }
394 - (id)evaluateVimExpressionCocoa:(NSString *)expr errorString:(NSString **)errstr
399 eval = [backendProxy evaluateExpressionCocoa:expr
401 } @catch (NSException *ex) {
402 *errstr = [ex reason];
415 if (!isInitialized) return;
418 [toolbar setDelegate:nil];
419 [[NSNotificationCenter defaultCenter] removeObserver:self];
420 //[[backendProxy connectionForProxy] invalidate];
421 //[windowController close];
422 [windowController cleanup];
425 - (oneway void)showSavePanelWithAttributes:(in bycopy NSDictionary *)attr
427 if (!isInitialized) return;
429 BOOL inDefaultMode = [[[NSRunLoop currentRunLoop] currentMode]
430 isEqual:NSDefaultRunLoopMode];
431 if (!inDefaultMode) {
432 // Delay call until run loop is in default mode.
433 [self performSelectorOnMainThread:
434 @selector(showSavePanelWithAttributes:)
437 modes:[NSArray arrayWithObject:
438 NSDefaultRunLoopMode]];
442 NSString *dir = [attr objectForKey:@"dir"];
443 BOOL saving = [[attr objectForKey:@"saving"] boolValue];
446 // 'dir == nil' means: set dir to the pwd of the Vim process, or let
447 // open dialog decide (depending on the below user default).
448 BOOL trackPwd = [[NSUserDefaults standardUserDefaults]
449 boolForKey:MMDialogsTrackPwdKey];
451 dir = [vimState objectForKey:@"pwd"];
455 [[NSSavePanel savePanel] beginSheetForDirectory:dir file:nil
456 modalForWindow:[windowController window]
458 didEndSelector:@selector(savePanelDidEnd:code:context:)
461 NSOpenPanel *panel = [NSOpenPanel openPanel];
462 [panel setAllowsMultipleSelection:NO];
463 [panel setAccessoryView:openPanelAccessoryView()];
465 [panel beginSheetForDirectory:dir file:nil types:nil
466 modalForWindow:[windowController window]
468 didEndSelector:@selector(savePanelDidEnd:code:context:)
473 - (oneway void)presentDialogWithAttributes:(in bycopy NSDictionary *)attr
475 if (!isInitialized) return;
477 BOOL inDefaultMode = [[[NSRunLoop currentRunLoop] currentMode]
478 isEqual:NSDefaultRunLoopMode];
479 if (!inDefaultMode) {
480 // Delay call until run loop is in default mode.
481 [self performSelectorOnMainThread:
482 @selector(presentDialogWithAttributes:)
485 modes:[NSArray arrayWithObject:
486 NSDefaultRunLoopMode]];
490 NSArray *buttonTitles = [attr objectForKey:@"buttonTitles"];
491 if (!(buttonTitles && [buttonTitles count])) return;
493 int style = [[attr objectForKey:@"alertStyle"] intValue];
494 NSString *message = [attr objectForKey:@"messageText"];
495 NSString *text = [attr objectForKey:@"informativeText"];
496 NSString *textFieldString = [attr objectForKey:@"textFieldString"];
497 MMAlert *alert = [[MMAlert alloc] init];
499 // NOTE! This has to be done before setting the informative text.
501 [alert setTextFieldString:textFieldString];
503 [alert setAlertStyle:style];
506 [alert setMessageText:message];
508 // If no message text is specified 'Alert' is used, which we don't
509 // want, so set an empty string as message text.
510 [alert setMessageText:@""];
514 [alert setInformativeText:text];
515 } else if (textFieldString) {
516 // Make sure there is always room for the input text field.
517 [alert setInformativeText:@""];
520 unsigned i, count = [buttonTitles count];
521 for (i = 0; i < count; ++i) {
522 NSString *title = [buttonTitles objectAtIndex:i];
523 // NOTE: The title of the button may contain the character '&' to
524 // indicate that the following letter should be the key equivalent
525 // associated with the button. Extract this letter and lowercase it.
526 NSString *keyEquivalent = nil;
527 NSRange hotkeyRange = [title rangeOfString:@"&"];
528 if (NSNotFound != hotkeyRange.location) {
529 if ([title length] > NSMaxRange(hotkeyRange)) {
530 NSRange keyEquivRange = NSMakeRange(hotkeyRange.location+1, 1);
531 keyEquivalent = [[title substringWithRange:keyEquivRange]
535 NSMutableString *string = [NSMutableString stringWithString:title];
536 [string deleteCharactersInRange:hotkeyRange];
540 [alert addButtonWithTitle:title];
542 // Set key equivalent for the button, but only if NSAlert hasn't
543 // already done so. (Check the documentation for
544 // - [NSAlert addButtonWithTitle:] to see what key equivalents are
545 // automatically assigned.)
546 NSButton *btn = [[alert buttons] lastObject];
547 if ([[btn keyEquivalent] length] == 0 && keyEquivalent) {
548 [btn setKeyEquivalent:keyEquivalent];
552 [alert beginSheetModalForWindow:[windowController window]
554 didEndSelector:@selector(alertDidEnd:code:context:)
560 - (oneway void)processCommandQueue:(in bycopy NSArray *)queue
562 if (!isInitialized) return;
564 if (inProcessCommandQueue) {
565 // NOTE! If a synchronous DO call is made during
566 // doProcessCommandQueue: below it may happen that this method is
567 // called a second time while the synchronous message is waiting for a
568 // reply (could also happen if doProcessCommandQueue: enters a modal
569 // loop, see comment below). Since this method cannot be considered
570 // reentrant, we queue the input and return immediately.
572 // If doProcessCommandQueue: enters a modal loop (happens e.g. on
573 // ShowPopupMenuMsgID) then the receiveQueue could grow to become
574 // arbitrarily large because DO calls still get processed. To avoid
575 // this we set a cap on the size of the queue and simply clear it if it
576 // becomes too large. (That is messages will be dropped and hence Vim
577 // and MacVim will at least temporarily be out of sync.)
578 if ([receiveQueue count] >= MMReceiveQueueCap)
579 [receiveQueue removeAllObjects];
581 [receiveQueue addObject:queue];
585 inProcessCommandQueue = YES;
586 [self doProcessCommandQueue:queue];
589 for (i = 0; i < [receiveQueue count]; ++i) {
590 // Note that doProcessCommandQueue: may cause the receiveQueue to grow
591 // or get cleared (due to cap being hit). Make sure to retain the item
592 // to process or it may get released from under us.
593 NSArray *q = [[receiveQueue objectAtIndex:i] retain];
594 [self doProcessCommandQueue:q];
598 // We assume that the remaining calls make no synchronous DO calls. If
599 // that did happen anyway, the command queue could get processed out of
602 // See comment below why this is called here and not later.
603 [windowController processCommandQueueDidFinish];
605 // NOTE: Ensure that no calls are made after this "if" clause that may call
606 // sendMessage::. If this happens anyway, such messages will be put on the
607 // send queue and then the queue will not be flushed until the next time
608 // this method is called.
609 if ([sendQueue count] > 0) {
611 [backendProxy processInputAndData:sendQueue];
613 @catch (NSException *e) {
614 // Connection timed out, just ignore this.
615 //NSLog(@"WARNING! Connection timed out in %s", _cmd);
618 [sendQueue removeAllObjects];
621 [receiveQueue removeAllObjects];
622 inProcessCommandQueue = NO;
625 - (NSToolbarItem *)toolbar:(NSToolbar *)theToolbar
626 itemForItemIdentifier:(NSString *)itemId
627 willBeInsertedIntoToolbar:(BOOL)flag
629 NSToolbarItem *item = [toolbarItemDict objectForKey:itemId];
631 NSLog(@"WARNING: No toolbar item with id '%@'", itemId);
637 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)theToolbar
642 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)theToolbar
647 @end // MMVimController
651 @implementation MMVimController (Private)
653 - (void)doProcessCommandQueue:(NSArray *)queue
655 NSMutableArray *delayQueue = nil;
658 unsigned i, count = [queue count];
660 NSLog(@"WARNING: Uneven number of components (%d) in command "
661 "queue. Skipping...", count);
665 //NSLog(@"======== %s BEGIN ========", _cmd);
666 for (i = 0; i < count; i += 2) {
667 NSData *value = [queue objectAtIndex:i];
668 NSData *data = [queue objectAtIndex:i+1];
670 int msgid = *((int*)[value bytes]);
671 //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
673 BOOL inDefaultMode = [[[NSRunLoop currentRunLoop] currentMode]
674 isEqual:NSDefaultRunLoopMode];
675 if (!inDefaultMode && isUnsafeMessage(msgid)) {
676 // NOTE: Because we may be listening to DO messages in "event
677 // tracking mode" we have to take extra care when doing things
678 // like releasing view items (and other Cocoa objects).
679 // Messages that may be potentially "unsafe" are delayed until
680 // the run loop is back to default mode at which time they are
681 // safe to call again.
682 // A problem with this approach is that it is hard to
683 // classify which messages are unsafe. As a rule of thumb, if
684 // a message may release an object used by the Cocoa framework
685 // (e.g. views) then the message should be considered unsafe.
686 // Delaying messages may have undesired side-effects since it
687 // means that messages may not be processed in the order Vim
688 // sent them, so beware.
690 delayQueue = [NSMutableArray array];
692 //NSLog(@"Adding unsafe message '%s' to delay queue (mode=%@)",
693 // MessageStrings[msgid],
694 // [[NSRunLoop currentRunLoop] currentMode]);
695 [delayQueue addObject:value];
696 [delayQueue addObject:data];
698 [self handleMessage:msgid data:data];
701 //NSLog(@"======== %s END ========", _cmd);
703 @catch (NSException *e) {
704 NSLog(@"Exception caught whilst processing command queue: %@", e);
708 //NSLog(@" Flushing delay queue (%d items)", [delayQueue count]/2);
709 [self performSelectorOnMainThread:@selector(processCommandQueue:)
710 withObject:delayQueue
712 modes:[NSArray arrayWithObject:
713 NSDefaultRunLoopMode]];
717 - (void)handleMessage:(int)msgid data:(NSData *)data
719 //if (msgid != AddMenuMsgID && msgid != AddMenuItemMsgID &&
720 // msgid != EnableMenuItemMsgID)
721 // NSLog(@"%@ %s%s", [self className], _cmd, MessageStrings[msgid]);
723 if (OpenWindowMsgID == msgid) {
724 [windowController openWindow];
726 // If the vim controller is preloading then the window will be
727 // displayed when it is taken off the preload cache.
729 [windowController showWindow];
730 } else if (BatchDrawMsgID == msgid) {
731 [[[windowController vimView] textView] performBatchDrawWithData:data];
732 } else if (SelectTabMsgID == msgid) {
733 #if 0 // NOTE: Tab selection is done inside updateTabsWithData:.
734 const void *bytes = [data bytes];
735 int idx = *((int*)bytes);
736 //NSLog(@"Selecting tab with index %d", idx);
737 [windowController selectTabWithIndex:idx];
739 } else if (UpdateTabBarMsgID == msgid) {
740 [windowController updateTabsWithData:data];
741 } else if (ShowTabBarMsgID == msgid) {
742 [windowController showTabBar:YES];
743 } else if (HideTabBarMsgID == msgid) {
744 [windowController showTabBar:NO];
745 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid ||
746 SetTextDimensionsReplyMsgID == msgid) {
747 const void *bytes = [data bytes];
748 int rows = *((int*)bytes); bytes += sizeof(int);
749 int cols = *((int*)bytes); bytes += sizeof(int);
751 [windowController setTextDimensionsWithRows:rows
753 isLive:(LiveResizeMsgID==msgid)
754 isReply:(SetTextDimensionsReplyMsgID==msgid)];
755 } else if (SetWindowTitleMsgID == msgid) {
756 const void *bytes = [data bytes];
757 int len = *((int*)bytes); bytes += sizeof(int);
759 NSString *string = [[NSString alloc] initWithBytes:(void*)bytes
760 length:len encoding:NSUTF8StringEncoding];
762 // While in live resize the window title displays the dimensions of the
763 // window so don't clobber this with a spurious "set title" message
765 if (![[windowController vimView] inLiveResize])
766 [windowController setTitle:string];
769 } else if (SetDocumentFilenameMsgID == msgid) {
770 const void *bytes = [data bytes];
771 int len = *((int*)bytes); bytes += sizeof(int);
774 NSString *filename = [[NSString alloc] initWithBytes:(void*)bytes
775 length:len encoding:NSUTF8StringEncoding];
777 [windowController setDocumentFilename:filename];
781 [windowController setDocumentFilename:@""];
783 } else if (AddMenuMsgID == msgid) {
784 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
785 [self addMenuWithDescriptor:[attrs objectForKey:@"descriptor"]
786 atIndex:[[attrs objectForKey:@"index"] intValue]];
787 } else if (AddMenuItemMsgID == msgid) {
788 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
789 [self addMenuItemWithDescriptor:[attrs objectForKey:@"descriptor"]
790 atIndex:[[attrs objectForKey:@"index"] intValue]
791 tip:[attrs objectForKey:@"tip"]
792 icon:[attrs objectForKey:@"icon"]
793 keyEquivalent:[attrs objectForKey:@"keyEquivalent"]
794 modifierMask:[[attrs objectForKey:@"modifierMask"] intValue]
795 action:[attrs objectForKey:@"action"]
796 isAlternate:[[attrs objectForKey:@"isAlternate"] boolValue]];
797 } else if (RemoveMenuItemMsgID == msgid) {
798 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
799 [self removeMenuItemWithDescriptor:[attrs objectForKey:@"descriptor"]];
800 } else if (EnableMenuItemMsgID == msgid) {
801 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
802 [self enableMenuItemWithDescriptor:[attrs objectForKey:@"descriptor"]
803 state:[[attrs objectForKey:@"enable"] boolValue]];
804 } else if (ShowToolbarMsgID == msgid) {
805 const void *bytes = [data bytes];
806 int enable = *((int*)bytes); bytes += sizeof(int);
807 int flags = *((int*)bytes); bytes += sizeof(int);
809 int mode = NSToolbarDisplayModeDefault;
810 if (flags & ToolbarLabelFlag) {
811 mode = flags & ToolbarIconFlag ? NSToolbarDisplayModeIconAndLabel
812 : NSToolbarDisplayModeLabelOnly;
813 } else if (flags & ToolbarIconFlag) {
814 mode = NSToolbarDisplayModeIconOnly;
817 int size = flags & ToolbarSizeRegularFlag ? NSToolbarSizeModeRegular
818 : NSToolbarSizeModeSmall;
820 [windowController showToolbar:enable size:size mode:mode];
821 } else if (CreateScrollbarMsgID == msgid) {
822 const void *bytes = [data bytes];
823 long ident = *((long*)bytes); bytes += sizeof(long);
824 int type = *((int*)bytes); bytes += sizeof(int);
826 [windowController createScrollbarWithIdentifier:ident type:type];
827 } else if (DestroyScrollbarMsgID == msgid) {
828 const void *bytes = [data bytes];
829 long ident = *((long*)bytes); bytes += sizeof(long);
831 [windowController destroyScrollbarWithIdentifier:ident];
832 } else if (ShowScrollbarMsgID == msgid) {
833 const void *bytes = [data bytes];
834 long ident = *((long*)bytes); bytes += sizeof(long);
835 int visible = *((int*)bytes); bytes += sizeof(int);
837 [windowController showScrollbarWithIdentifier:ident state:visible];
838 } else if (SetScrollbarPositionMsgID == msgid) {
839 const void *bytes = [data bytes];
840 long ident = *((long*)bytes); bytes += sizeof(long);
841 int pos = *((int*)bytes); bytes += sizeof(int);
842 int len = *((int*)bytes); bytes += sizeof(int);
844 [windowController setScrollbarPosition:pos length:len
846 } else if (SetScrollbarThumbMsgID == msgid) {
847 const void *bytes = [data bytes];
848 long ident = *((long*)bytes); bytes += sizeof(long);
849 float val = *((float*)bytes); bytes += sizeof(float);
850 float prop = *((float*)bytes); bytes += sizeof(float);
852 [windowController setScrollbarThumbValue:val proportion:prop
854 } else if (SetFontMsgID == msgid) {
855 const void *bytes = [data bytes];
856 float size = *((float*)bytes); bytes += sizeof(float);
857 int len = *((int*)bytes); bytes += sizeof(int);
858 NSString *name = [[NSString alloc]
859 initWithBytes:(void*)bytes length:len
860 encoding:NSUTF8StringEncoding];
861 NSFont *font = [NSFont fontWithName:name size:size];
863 // This should never happen, but if e.g. the default font was
864 // missing from the app bundle we might end up here.
865 NSLog(@"WARNING: Can't set font %@, using Cocoa default", name);
866 font = [NSFont userFixedPitchFontOfSize:0];
869 [windowController setFont:font];
871 } else if (SetWideFontMsgID == msgid) {
872 const void *bytes = [data bytes];
873 float size = *((float*)bytes); bytes += sizeof(float);
874 int len = *((int*)bytes); bytes += sizeof(int);
876 NSString *name = [[NSString alloc]
877 initWithBytes:(void*)bytes length:len
878 encoding:NSUTF8StringEncoding];
879 NSFont *font = [NSFont fontWithName:name size:size];
880 [windowController setWideFont:font];
884 [windowController setWideFont:nil];
886 } else if (SetDefaultColorsMsgID == msgid) {
887 const void *bytes = [data bytes];
888 unsigned bg = *((unsigned*)bytes); bytes += sizeof(unsigned);
889 unsigned fg = *((unsigned*)bytes); bytes += sizeof(unsigned);
890 NSColor *back = [NSColor colorWithArgbInt:bg];
891 NSColor *fore = [NSColor colorWithRgbInt:fg];
893 [windowController setDefaultColorsBackground:back foreground:fore];
894 } else if (ExecuteActionMsgID == msgid) {
895 const void *bytes = [data bytes];
896 int len = *((int*)bytes); bytes += sizeof(int);
897 NSString *actionName = [[NSString alloc]
898 initWithBytes:(void*)bytes length:len
899 encoding:NSUTF8StringEncoding];
901 SEL sel = NSSelectorFromString(actionName);
902 [NSApp sendAction:sel to:nil from:self];
904 [actionName release];
905 } else if (ShowPopupMenuMsgID == msgid) {
906 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
908 // The popup menu enters a modal loop so delay this call so that we
909 // don't block inside processCommandQueue:.
910 [self performSelectorOnMainThread:@selector(popupMenuWithAttributes:)
913 modes:[NSArray arrayWithObject:
914 NSDefaultRunLoopMode]];
915 } else if (SetMouseShapeMsgID == msgid) {
916 const void *bytes = [data bytes];
917 int shape = *((int*)bytes); bytes += sizeof(int);
919 [windowController setMouseShape:shape];
920 } else if (AdjustLinespaceMsgID == msgid) {
921 const void *bytes = [data bytes];
922 int linespace = *((int*)bytes); bytes += sizeof(int);
924 [windowController adjustLinespace:linespace];
925 } else if (ActivateMsgID == msgid) {
926 //NSLog(@"ActivateMsgID");
927 [NSApp activateIgnoringOtherApps:YES];
928 [[windowController window] makeKeyAndOrderFront:self];
929 } else if (SetServerNameMsgID == msgid) {
930 NSString *name = [[NSString alloc] initWithData:data
931 encoding:NSUTF8StringEncoding];
932 [self setServerName:name];
934 } else if (EnterFullscreenMsgID == msgid) {
935 const void *bytes = [data bytes];
936 int fuoptions = *((int*)bytes); bytes += sizeof(int);
937 int bg = *((int*)bytes);
938 NSColor *back = [NSColor colorWithArgbInt:bg];
940 [windowController enterFullscreen:fuoptions backgroundColor:back];
941 } else if (LeaveFullscreenMsgID == msgid) {
942 [windowController leaveFullscreen];
943 } else if (BuffersNotModifiedMsgID == msgid) {
944 [windowController setBuffersModified:NO];
945 } else if (BuffersModifiedMsgID == msgid) {
946 [windowController setBuffersModified:YES];
947 } else if (SetPreEditPositionMsgID == msgid) {
948 const int *dim = (const int*)[data bytes];
949 [[[windowController vimView] textView] setPreEditRow:dim[0]
951 } else if (EnableAntialiasMsgID == msgid) {
952 [[[windowController vimView] textView] setAntialias:YES];
953 } else if (DisableAntialiasMsgID == msgid) {
954 [[[windowController vimView] textView] setAntialias:NO];
955 } else if (SetVimStateMsgID == msgid) {
956 NSDictionary *dict = [NSDictionary dictionaryWithData:data];
959 vimState = [dict retain];
961 } else if (CloseWindowMsgID == msgid) {
962 [self scheduleClose];
963 } else if (SetFullscreenColorMsgID == msgid) {
964 const int *bg = (const int*)[data bytes];
965 NSColor *color = [NSColor colorWithRgbInt:*bg];
967 [windowController setFullscreenBackgroundColor:color];
968 } else if (ShowFindReplaceDialogMsgID == msgid) {
969 NSDictionary *dict = [NSDictionary dictionaryWithData:data];
971 [[MMFindReplaceController sharedInstance]
972 showWithText:[dict objectForKey:@"text"]
973 flags:[[dict objectForKey:@"flags"] intValue]];
975 // IMPORTANT: When adding a new message, make sure to update
976 // isUnsafeMessage() if necessary!
978 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
982 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
983 context:(void *)context
985 NSString *path = (code == NSOKButton) ? [panel filename] : nil;
987 // NOTE! setDialogReturn: is a synchronous call so set a proper timeout to
988 // avoid waiting forever for it to finish. We make this a synchronous call
989 // so that we can be fairly certain that Vim doesn't think the dialog box
990 // is still showing when MacVim has in fact already dismissed it.
991 NSConnection *conn = [backendProxy connectionForProxy];
992 NSTimeInterval oldTimeout = [conn requestTimeout];
993 [conn setRequestTimeout:MMSetDialogReturnTimeout];
996 [backendProxy setDialogReturn:path];
998 // Add file to the "Recent Files" menu (this ensures that files that
999 // are opened/saved from a :browse command are added to this menu).
1001 [[NSDocumentController sharedDocumentController]
1002 noteNewRecentFilePath:path];
1004 @catch (NSException *e) {
1005 NSLog(@"Exception caught in %s %@", _cmd, e);
1008 [conn setRequestTimeout:oldTimeout];
1012 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context
1016 code = code - NSAlertFirstButtonReturn + 1;
1018 if ([alert isKindOfClass:[MMAlert class]] && [alert textField]) {
1019 ret = [NSArray arrayWithObjects:[NSNumber numberWithInt:code],
1020 [[alert textField] stringValue], nil];
1022 ret = [NSArray arrayWithObject:[NSNumber numberWithInt:code]];
1026 [backendProxy setDialogReturn:ret];
1028 @catch (NSException *e) {
1029 NSLog(@"Exception caught in %s %@", _cmd, e);
1033 - (NSMenuItem *)menuItemForDescriptor:(NSArray *)desc
1035 if (!(desc && [desc count] > 0)) return nil;
1037 NSString *rootName = [desc objectAtIndex:0];
1038 NSArray *rootItems = [rootName hasPrefix:@"PopUp"] ? popupMenuItems
1039 : [mainMenu itemArray];
1041 NSMenuItem *item = nil;
1042 int i, count = [rootItems count];
1043 for (i = 0; i < count; ++i) {
1044 item = [rootItems objectAtIndex:i];
1045 if ([[item title] isEqual:rootName])
1049 if (i == count) return nil;
1051 count = [desc count];
1052 for (i = 1; i < count; ++i) {
1053 item = [[item submenu] itemWithTitle:[desc objectAtIndex:i]];
1054 if (!item) return nil;
1060 - (NSMenu *)parentMenuForDescriptor:(NSArray *)desc
1062 if (!(desc && [desc count] > 0)) return nil;
1064 NSString *rootName = [desc objectAtIndex:0];
1065 NSArray *rootItems = [rootName hasPrefix:@"PopUp"] ? popupMenuItems
1066 : [mainMenu itemArray];
1069 int i, count = [rootItems count];
1070 for (i = 0; i < count; ++i) {
1071 NSMenuItem *item = [rootItems objectAtIndex:i];
1072 if ([[item title] isEqual:rootName]) {
1073 menu = [item submenu];
1078 if (!menu) return nil;
1080 count = [desc count] - 1;
1081 for (i = 1; i < count; ++i) {
1082 NSMenuItem *item = [menu itemWithTitle:[desc objectAtIndex:i]];
1083 menu = [item submenu];
1084 if (!menu) return nil;
1090 - (NSMenu *)topLevelMenuForTitle:(NSString *)title
1092 // Search only the top-level menus.
1094 unsigned i, count = [popupMenuItems count];
1095 for (i = 0; i < count; ++i) {
1096 NSMenuItem *item = [popupMenuItems objectAtIndex:i];
1097 if ([title isEqual:[item title]])
1098 return [item submenu];
1101 count = [mainMenu numberOfItems];
1102 for (i = 0; i < count; ++i) {
1103 NSMenuItem *item = [mainMenu itemAtIndex:i];
1104 if ([title isEqual:[item title]])
1105 return [item submenu];
1111 - (void)addMenuWithDescriptor:(NSArray *)desc atIndex:(int)idx
1113 if (!(desc && [desc count] > 0 && idx >= 0)) return;
1115 NSString *rootName = [desc objectAtIndex:0];
1116 if ([rootName isEqual:@"ToolBar"]) {
1117 // The toolbar only has one menu, we take this as a hint to create a
1118 // toolbar, then we return.
1120 // NOTE! Each toolbar must have a unique identifier, else each
1121 // window will have the same toolbar.
1122 NSString *ident = [NSString stringWithFormat:@"%d", (int)self];
1123 toolbar = [[NSToolbar alloc] initWithIdentifier:ident];
1125 [toolbar setShowsBaselineSeparator:NO];
1126 [toolbar setDelegate:self];
1127 [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
1128 [toolbar setSizeMode:NSToolbarSizeModeSmall];
1130 [windowController setToolbar:toolbar];
1136 // This is either a main menu item or a popup menu item.
1137 NSString *title = [desc lastObject];
1138 NSMenuItem *item = [[NSMenuItem alloc] init];
1139 NSMenu *menu = [[NSMenu alloc] initWithTitle:title];
1141 [item setTitle:title];
1142 [item setSubmenu:menu];
1144 NSMenu *parent = [self parentMenuForDescriptor:desc];
1145 if (!parent && [rootName hasPrefix:@"PopUp"]) {
1146 if ([popupMenuItems count] <= idx) {
1147 [popupMenuItems addObject:item];
1149 [popupMenuItems insertObject:item atIndex:idx];
1152 // If descriptor has no parent and its not a popup (or toolbar) menu,
1153 // then it must belong to main menu.
1154 if (!parent) parent = mainMenu;
1156 if ([parent numberOfItems] <= idx) {
1157 [parent addItem:item];
1159 [parent insertItem:item atIndex:idx];
1167 - (void)addMenuItemWithDescriptor:(NSArray *)desc
1170 icon:(NSString *)icon
1171 keyEquivalent:(NSString *)keyEquivalent
1172 modifierMask:(int)modifierMask
1173 action:(NSString *)action
1174 isAlternate:(BOOL)isAlternate
1176 if (!(desc && [desc count] > 1 && idx >= 0)) return;
1178 NSString *title = [desc lastObject];
1179 NSString *rootName = [desc objectAtIndex:0];
1181 if ([rootName isEqual:@"ToolBar"]) {
1182 if (toolbar && [desc count] == 2)
1183 [self addToolbarItemWithLabel:title tip:tip icon:icon atIndex:idx];
1187 NSMenu *parent = [self parentMenuForDescriptor:desc];
1189 NSLog(@"WARNING: Menu item '%@' has no parent",
1190 [desc componentsJoinedByString:@"->"]);
1194 NSMenuItem *item = nil;
1195 if (0 == [title length]
1196 || ([title hasPrefix:@"-"] && [title hasSuffix:@"-"])) {
1197 item = [NSMenuItem separatorItem];
1198 [item setTitle:title];
1200 item = [[[NSMenuItem alloc] init] autorelease];
1201 [item setTitle:title];
1203 // Note: It is possible to set the action to a message that "doesn't
1204 // exist" without problems. We take advantage of this when adding
1205 // "dummy items" e.g. when dealing with the "Recent Files" menu (in
1206 // which case a recentFilesDummy: action is set, although it is never
1208 if ([action length] > 0)
1209 [item setAction:NSSelectorFromString(action)];
1211 [item setAction:@selector(vimMenuItemAction:)];
1212 if ([tip length] > 0) [item setToolTip:tip];
1213 if ([keyEquivalent length] > 0) {
1214 [item setKeyEquivalent:keyEquivalent];
1215 [item setKeyEquivalentModifierMask:modifierMask];
1217 [item setAlternate:isAlternate];
1219 // The tag is used to indicate whether Vim thinks a menu item should be
1220 // enabled or disabled. By default Vim thinks menu items are enabled.
1224 if ([parent numberOfItems] <= idx) {
1225 [parent addItem:item];
1227 [parent insertItem:item atIndex:idx];
1231 - (void)removeMenuItemWithDescriptor:(NSArray *)desc
1233 if (!(desc && [desc count] > 0)) return;
1235 NSString *title = [desc lastObject];
1236 NSString *rootName = [desc objectAtIndex:0];
1237 if ([rootName isEqual:@"ToolBar"]) {
1239 // Only remove toolbar items, never actually remove the toolbar
1240 // itself or strange things may happen.
1241 if ([desc count] == 2) {
1242 int idx = [toolbar indexOfItemWithItemIdentifier:title];
1243 if (idx != NSNotFound)
1244 [toolbar removeItemAtIndex:idx];
1250 NSMenuItem *item = [self menuItemForDescriptor:desc];
1252 NSLog(@"Failed to remove menu item, descriptor not found: %@",
1253 [desc componentsJoinedByString:@"->"]);
1259 if ([item menu] == [NSApp mainMenu] || ![item menu]) {
1260 // NOTE: To be on the safe side we try to remove the item from
1261 // both arrays (it is ok to call removeObject: even if an array
1262 // does not contain the object to remove).
1263 [popupMenuItems removeObject:item];
1267 [[item menu] removeItem:item];
1272 - (void)enableMenuItemWithDescriptor:(NSArray *)desc state:(BOOL)on
1274 if (!(desc && [desc count] > 0)) return;
1276 /*NSLog(@"%sable item %@", on ? "En" : "Dis",
1277 [desc componentsJoinedByString:@"->"]);*/
1279 NSString *rootName = [desc objectAtIndex:0];
1280 if ([rootName isEqual:@"ToolBar"]) {
1281 if (toolbar && [desc count] == 2) {
1282 NSString *title = [desc lastObject];
1283 [[toolbar itemWithItemIdentifier:title] setEnabled:on];
1286 // Use tag to set whether item is enabled or disabled instead of
1287 // calling setEnabled:. This way the menus can autoenable themselves
1288 // but at the same time Vim can set if a menu is enabled whenever it
1290 [[self menuItemForDescriptor:desc] setTag:on];
1294 - (void)addToolbarItemToDictionaryWithLabel:(NSString *)title
1295 toolTip:(NSString *)tip
1296 icon:(NSString *)icon
1298 // If the item corresponds to a separator then do nothing, since it is
1299 // already defined by Cocoa.
1300 if (!title || [title isEqual:NSToolbarSeparatorItemIdentifier]
1301 || [title isEqual:NSToolbarSpaceItemIdentifier]
1302 || [title isEqual:NSToolbarFlexibleSpaceItemIdentifier])
1305 NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:title];
1306 [item setLabel:title];
1307 [item setToolTip:tip];
1308 [item setAction:@selector(vimToolbarItemAction:)];
1309 [item setAutovalidates:NO];
1311 NSImage *img = [NSImage imageNamed:icon];
1313 img = [[[NSImage alloc] initByReferencingFile:icon] autorelease];
1314 if (!(img && [img isValid]))
1318 NSLog(@"WARNING: Could not find image with name '%@' to use as toolbar"
1319 " image for identifier '%@';"
1320 " using default toolbar icon '%@' instead.",
1321 icon, title, MMDefaultToolbarImageName);
1323 img = [NSImage imageNamed:MMDefaultToolbarImageName];
1326 [item setImage:img];
1328 [toolbarItemDict setObject:item forKey:title];
1333 - (void)addToolbarItemWithLabel:(NSString *)label
1335 icon:(NSString *)icon
1338 if (!toolbar) return;
1340 // Check for separator items.
1342 label = NSToolbarSeparatorItemIdentifier;
1343 } else if ([label length] >= 2 && [label hasPrefix:@"-"]
1344 && [label hasSuffix:@"-"]) {
1345 // The label begins and ends with '-'; decided which kind of separator
1346 // item it is by looking at the prefix.
1347 if ([label hasPrefix:@"-space"]) {
1348 label = NSToolbarSpaceItemIdentifier;
1349 } else if ([label hasPrefix:@"-flexspace"]) {
1350 label = NSToolbarFlexibleSpaceItemIdentifier;
1352 label = NSToolbarSeparatorItemIdentifier;
1356 [self addToolbarItemToDictionaryWithLabel:label toolTip:tip icon:icon];
1358 int maxIdx = [[toolbar items] count];
1359 if (maxIdx < idx) idx = maxIdx;
1361 [toolbar insertItemWithItemIdentifier:label atIndex:idx];
1364 - (void)popupMenuWithDescriptor:(NSArray *)desc
1365 atRow:(NSNumber *)row
1366 column:(NSNumber *)col
1368 NSMenu *menu = [[self menuItemForDescriptor:desc] submenu];
1371 id textView = [[windowController vimView] textView];
1374 // TODO: Let textView convert (row,col) to NSPoint.
1375 int r = [row intValue];
1376 int c = [col intValue];
1377 NSSize cellSize = [textView cellSize];
1378 pt = NSMakePoint((c+1)*cellSize.width, (r+1)*cellSize.height);
1379 pt = [textView convertPoint:pt toView:nil];
1381 pt = [[windowController window] mouseLocationOutsideOfEventStream];
1384 NSEvent *event = [NSEvent mouseEventWithType:NSRightMouseDown
1388 windowNumber:[[windowController window] windowNumber]
1394 [NSMenu popUpContextMenu:menu withEvent:event forView:textView];
1397 - (void)popupMenuWithAttributes:(NSDictionary *)attrs
1401 [self popupMenuWithDescriptor:[attrs objectForKey:@"descriptor"]
1402 atRow:[attrs objectForKey:@"row"]
1403 column:[attrs objectForKey:@"column"]];
1406 - (void)connectionDidDie:(NSNotification *)notification
1408 //NSLog(@"%@ %s%@", [self className], _cmd, notification);
1409 [self scheduleClose];
1412 - (void)scheduleClose
1414 // NOTE! This message can arrive at pretty much anytime, e.g. while
1415 // the run loop is the 'event tracking' mode. This means that Cocoa may
1416 // well be in the middle of processing some message while this message is
1417 // received. If we were to remove the vim controller straight away we may
1418 // free objects that Cocoa is currently using (e.g. view objects). The
1419 // following call ensures that the vim controller is not released until the
1420 // run loop is back in the 'default' mode.
1421 [[MMAppController sharedInstance]
1422 performSelectorOnMainThread:@selector(removeVimController:)
1425 modes:[NSArray arrayWithObject:
1426 NSDefaultRunLoopMode]];
1429 @end // MMVimController (Private)
1434 @implementation MMAlert
1437 [textField release]; textField = nil;
1441 - (void)setTextFieldString:(NSString *)textFieldString
1443 [textField release];
1444 textField = [[NSTextField alloc] init];
1445 [textField setStringValue:textFieldString];
1448 - (NSTextField *)textField
1453 - (void)setInformativeText:(NSString *)text
1456 // HACK! Add some space for the text field.
1457 [super setInformativeText:[text stringByAppendingString:@"\n\n\n"]];
1459 [super setInformativeText:text];
1463 - (void)beginSheetModalForWindow:(NSWindow *)window
1464 modalDelegate:(id)delegate
1465 didEndSelector:(SEL)didEndSelector
1466 contextInfo:(void *)contextInfo
1468 [super beginSheetModalForWindow:window
1469 modalDelegate:delegate
1470 didEndSelector:didEndSelector
1471 contextInfo:contextInfo];
1473 // HACK! Place the input text field at the bottom of the informative text
1474 // (which has been made a bit larger by adding newline characters).
1475 NSView *contentView = [[self window] contentView];
1476 NSRect rect = [contentView frame];
1477 rect.origin.y = rect.size.height;
1479 NSArray *subviews = [contentView subviews];
1480 unsigned i, count = [subviews count];
1481 for (i = 0; i < count; ++i) {
1482 NSView *view = [subviews objectAtIndex:i];
1483 if ([view isKindOfClass:[NSTextField class]]
1484 && [view frame].origin.y < rect.origin.y) {
1485 // NOTE: The informative text field is the lowest NSTextField in
1486 // the alert dialog.
1487 rect = [view frame];
1491 rect.size.height = MMAlertTextFieldHeight;
1492 [textField setFrame:rect];
1493 [contentView addSubview:textField];
1494 [textField becomeFirstResponder];
1503 isUnsafeMessage(int msgid)
1505 // Messages that may release Cocoa objects must be added to this list. For
1506 // example, UpdateTabBarMsgID may delete NSTabViewItem objects so it goes
1508 static int unsafeMessages[] = { // REASON MESSAGE IS ON THIS LIST:
1509 //OpenWindowMsgID, // Changes lots of state
1510 UpdateTabBarMsgID, // May delete NSTabViewItem
1511 RemoveMenuItemMsgID, // Deletes NSMenuItem
1512 DestroyScrollbarMsgID, // Deletes NSScroller
1513 ExecuteActionMsgID, // Impossible to predict
1514 ShowPopupMenuMsgID, // Enters modal loop
1516 EnterFullscreenMsgID, // Modifies delegate of window controller
1517 LeaveFullscreenMsgID, // Modifies delegate of window controller
1518 CloseWindowMsgID, // See note below
1521 // NOTE about CloseWindowMsgID: If this arrives at the same time as say
1522 // ExecuteActionMsgID, then the "execute" message will be lost due to it
1523 // being queued and handled after the "close" message has caused the
1524 // controller to cleanup...UNLESS we add CloseWindowMsgID to the list of
1525 // unsafe messages. This is the _only_ reason it is on this list (since
1526 // all that happens in response to it is that we schedule another message
1527 // for later handling).
1529 int i, count = sizeof(unsafeMessages)/sizeof(unsafeMessages[0]);
1530 for (i = 0; i < count; ++i)
1531 if (msgid == unsafeMessages[i])