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. A MMVimController sends input
14 * directly to a MMBackend, but communication from MMBackend to MMVimController
15 * goes via MMAppController so that it can coordinate all incoming distributed
18 * MMVimController does not deal with visual presentation. Essentially it
19 * should be able to run with no window present.
21 * Output from the backend is received in processInputQueue: (this message is
22 * called from MMAppController so it is not a DO call). Input is sent to the
23 * backend via sendMessage:data: or addVimInput:. The latter allows execution
24 * of arbitrary strings in the Vim process, much like the Vim script function
25 * remote_send() does. The messages that may be passed between frontend and
26 * backend are defined in an enum in MacVim.h.
29 #import "MMAppController.h"
30 #import "MMAtsuiTextView.h"
31 #import "MMFindReplaceController.h"
32 #import "MMTextView.h"
33 #import "MMVimController.h"
35 #import "MMWindowController.h"
36 #import "Miscellaneous.h"
38 #ifdef MM_ENABLE_PLUGINS
39 #import "MMPlugInManager.h"
42 static NSString *MMDefaultToolbarImageName = @"Attention";
43 static int MMAlertTextFieldHeight = 22;
45 // NOTE: By default a message sent to the backend will be dropped if it cannot
46 // be delivered instantly; otherwise there is a possibility that MacVim will
47 // 'beachball' while waiting to deliver DO messages to an unresponsive Vim
48 // process. This means that you cannot rely on any message sent with
49 // sendMessage: to actually reach Vim.
50 static NSTimeInterval MMBackendProxyRequestTimeout = 0;
52 // Timeout used for setDialogReturn:.
53 static NSTimeInterval MMSetDialogReturnTimeout = 1.0;
55 static unsigned identifierCounter = 1;
57 static BOOL isUnsafeMessage(int msgid);
60 @interface MMAlert : NSAlert {
61 NSTextField *textField;
63 - (void)setTextFieldString:(NSString *)textFieldString;
64 - (NSTextField *)textField;
68 @interface MMVimController (Private)
69 - (void)doProcessInputQueue:(NSArray *)queue;
70 - (void)handleMessage:(int)msgid data:(NSData *)data;
71 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
72 context:(void *)context;
73 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context;
74 - (NSMenuItem *)menuItemForDescriptor:(NSArray *)desc;
75 - (NSMenu *)parentMenuForDescriptor:(NSArray *)desc;
76 - (NSMenu *)topLevelMenuForTitle:(NSString *)title;
77 - (void)addMenuWithDescriptor:(NSArray *)desc atIndex:(int)index;
78 - (void)addMenuItemWithDescriptor:(NSArray *)desc
82 keyEquivalent:(NSString *)keyEquivalent
83 modifierMask:(int)modifierMask
84 action:(NSString *)action
85 isAlternate:(BOOL)isAlternate;
86 - (void)removeMenuItemWithDescriptor:(NSArray *)desc;
87 - (void)enableMenuItemWithDescriptor:(NSArray *)desc state:(BOOL)on;
88 - (void)addToolbarItemToDictionaryWithLabel:(NSString *)title
89 toolTip:(NSString *)tip icon:(NSString *)icon;
90 - (void)addToolbarItemWithLabel:(NSString *)label
91 tip:(NSString *)tip icon:(NSString *)icon
93 - (void)popupMenuWithDescriptor:(NSArray *)desc
95 column:(NSNumber *)col;
96 - (void)popupMenuWithAttributes:(NSDictionary *)attrs;
97 - (void)connectionDidDie:(NSNotification *)notification;
98 - (void)scheduleClose;
99 - (void)handleBrowseForFile:(NSDictionary *)attr;
100 - (void)handleShowDialog:(NSDictionary *)attr;
106 @implementation MMVimController
108 - (id)initWithBackend:(id)backend pid:(int)processIdentifier
110 if (!(self = [super init]))
113 // TODO: Come up with a better way of creating an identifier.
114 identifier = identifierCounter++;
117 [[MMWindowController alloc] initWithVimController:self];
118 backendProxy = [backend retain];
119 popupMenuItems = [[NSMutableArray alloc] init];
120 toolbarItemDict = [[NSMutableDictionary alloc] init];
121 pid = processIdentifier;
122 creationDate = [[NSDate alloc] init];
124 NSConnection *connection = [backendProxy connectionForProxy];
126 // TODO: Check that this will not set the timeout for the root proxy
127 // (in MMAppController).
128 [connection setRequestTimeout:MMBackendProxyRequestTimeout];
130 [[NSNotificationCenter defaultCenter] addObserver:self
131 selector:@selector(connectionDidDie:)
132 name:NSConnectionDidDieNotification object:connection];
134 // Set up a main menu with only a "MacVim" menu (copied from a template
135 // which itself is set up in MainMenu.nib). The main menu is populated
137 mainMenu = [[NSMenu alloc] initWithTitle:@"MainMenu"];
138 NSMenuItem *appMenuItem = [[MMAppController sharedInstance]
139 appMenuItemTemplate];
140 appMenuItem = [[appMenuItem copy] autorelease];
142 // Note: If the title of the application menu is anything but what
143 // CFBundleName says then the application menu will not be typeset in
144 // boldface for some reason. (It should already be set when we copy
145 // from the default main menu, but this is not the case for some
147 NSString *appName = [[NSBundle mainBundle]
148 objectForInfoDictionaryKey:@"CFBundleName"];
149 [appMenuItem setTitle:appName];
151 [mainMenu addItem:appMenuItem];
153 #ifdef MM_ENABLE_PLUGINS
154 instanceMediator = [[MMPlugInInstanceMediator alloc]
155 initWithVimController:self];
169 #ifdef MM_ENABLE_PLUGINS
170 [instanceMediator release]; instanceMediator = nil;
173 [serverName release]; serverName = nil;
174 [backendProxy release]; backendProxy = nil;
176 [toolbarItemDict release]; toolbarItemDict = nil;
177 [toolbar release]; toolbar = nil;
178 [popupMenuItems release]; popupMenuItems = nil;
179 [windowController release]; windowController = nil;
181 [vimState release]; vimState = nil;
182 [mainMenu release]; mainMenu = nil;
183 [creationDate release]; creationDate = nil;
188 - (unsigned)identifier
193 - (MMWindowController *)windowController
195 return windowController;
198 #ifdef MM_ENABLE_PLUGINS
199 - (MMPlugInInstanceMediator *)instanceMediator
201 return instanceMediator;
205 - (NSDictionary *)vimState
210 - (id)objectForVimStateKey:(NSString *)key
212 return [vimState objectForKey:key];
225 - (void)setIsPreloading:(BOOL)yn
230 - (NSDate *)creationDate
235 - (void)setServerName:(NSString *)name
237 if (name != serverName) {
238 [serverName release];
239 serverName = [name copy];
243 - (NSString *)serverName
253 - (void)dropFiles:(NSArray *)filenames forceOpen:(BOOL)force
255 filenames = normalizeFilenames(filenames);
256 ASLogInfo(@"filenames=%@ force=%d", filenames, force);
258 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
260 // Default to opening in tabs if layout is invalid or set to "windows".
261 int layout = [ud integerForKey:MMOpenLayoutKey];
262 if (layout < 0 || layout > MMLayoutTabs)
263 layout = MMLayoutTabs;
265 BOOL splitVert = [ud boolForKey:MMVerticalSplitKey];
266 if (splitVert && MMLayoutHorizontalSplit == layout)
267 layout = MMLayoutVerticalSplit;
269 NSDictionary *args = [NSDictionary dictionaryWithObjectsAndKeys:
270 [NSNumber numberWithInt:layout], @"layout",
271 filenames, @"filenames",
272 [NSNumber numberWithBool:force], @"forceOpen",
275 [self sendMessage:DropFilesMsgID data:[args dictionaryAsData]];
278 - (void)file:(NSString *)filename draggedToTabAtIndex:(NSUInteger)tabIndex
280 filename = normalizeFilename(filename);
281 ASLogInfo(@"filename=%@ index=%d", filename, tabIndex);
283 NSString *fnEsc = [filename stringByEscapingSpecialFilenameCharacters];
284 NSString *input = [NSString stringWithFormat:@"<C-\\><C-N>:silent "
286 "edit! %@<CR>", tabIndex + 1, fnEsc];
287 [self addVimInput:input];
290 - (void)filesDraggedToTabBar:(NSArray *)filenames
292 filenames = normalizeFilenames(filenames);
293 ASLogInfo(@"%@", filenames);
295 NSUInteger i, count = [filenames count];
296 NSMutableString *input = [NSMutableString stringWithString:@"<C-\\><C-N>"
297 ":silent! tabnext 9999"];
298 for (i = 0; i < count; i++) {
299 NSString *fn = [filenames objectAtIndex:i];
300 NSString *fnEsc = [fn stringByEscapingSpecialFilenameCharacters];
301 [input appendFormat:@"|tabedit %@", fnEsc];
303 [input appendString:@"<CR>"];
304 [self addVimInput:input];
307 - (void)dropString:(NSString *)string
309 ASLogInfo(@"%@", string);
310 int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
312 NSMutableData *data = [NSMutableData data];
314 [data appendBytes:&len length:sizeof(int)];
315 [data appendBytes:[string UTF8String] length:len];
317 [self sendMessage:DropStringMsgID data:data];
321 - (void)passArguments:(NSDictionary *)args
325 ASLogDebug(@"args=%@", args);
327 [self sendMessage:OpenWithArgumentsMsgID data:[args dictionaryAsData]];
329 // HACK! Fool findUnusedEditor into thinking that this controller is not
330 // unused anymore, in case it is called before the arguments have reached
331 // the Vim process. This should be a "safe" hack since the next time the
332 // Vim process flushes its output queue the state will be updated again (at
333 // which time the "unusedEditor" state will have been properly set).
334 NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:
336 [dict setObject:[NSNumber numberWithBool:NO] forKey:@"unusedEditor"];
338 vimState = [dict copy];
341 - (void)sendMessage:(int)msgid data:(NSData *)data
343 ASLogDebug(@"msg=%s (isInitialized=%d)",
344 MessageStrings[msgid], isInitialized);
346 if (!isInitialized) return;
349 [backendProxy processInput:msgid data:data];
351 @catch (NSException *ex) {
352 ASLogNotice(@"processInput:data: failed: pid=%d id=%d msg=%s reason=%@",
353 pid, identifier, MessageStrings[msgid], ex);
357 - (BOOL)sendMessageNow:(int)msgid data:(NSData *)data
358 timeout:(NSTimeInterval)timeout
360 // Send a message with a timeout. USE WITH EXTREME CAUTION! Sending
361 // messages in rapid succession with a timeout may cause MacVim to beach
362 // ball forever. In almost all circumstances sendMessage:data: should be
365 ASLogDebug(@"msg=%s (isInitialized=%d)",
366 MessageStrings[msgid], isInitialized);
371 if (timeout < 0) timeout = 0;
374 NSConnection *conn = [backendProxy connectionForProxy];
375 NSTimeInterval oldTimeout = [conn requestTimeout];
377 [conn setRequestTimeout:timeout];
380 [backendProxy processInput:msgid data:data];
382 @catch (NSException *ex) {
384 ASLogNotice(@"processInput:data: failed: pid=%d id=%d msg=%s reason=%@",
385 pid, identifier, MessageStrings[msgid], ex);
388 [conn setRequestTimeout:oldTimeout];
394 - (void)addVimInput:(NSString *)string
396 ASLogDebug(@"%@", string);
398 // This is a very general method of adding input to the Vim process. It is
399 // basically the same as calling remote_send() on the process (see
400 // ':h remote_send').
402 NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
403 [self sendMessage:AddInputMsgID data:data];
407 - (NSString *)evaluateVimExpression:(NSString *)expr
409 NSString *eval = nil;
412 eval = [backendProxy evaluateExpression:expr];
413 ASLogDebug(@"eval(%@)=%@", expr, eval);
415 @catch (NSException *ex) {
416 ASLogNotice(@"evaluateExpression: failed: pid=%d id=%d reason=%@",
417 pid, identifier, ex);
423 - (id)evaluateVimExpressionCocoa:(NSString *)expr
424 errorString:(NSString **)errstr
429 eval = [backendProxy evaluateExpressionCocoa:expr
431 ASLogDebug(@"eval(%@)=%@", expr, eval);
432 } @catch (NSException *ex) {
433 ASLogNotice(@"evaluateExpressionCocoa: failed: pid=%d id=%d reason=%@",
434 pid, identifier, ex);
435 *errstr = [ex reason];
448 if (!isInitialized) return;
450 // Remove any delayed calls made on this object.
451 [NSObject cancelPreviousPerformRequestsWithTarget:self];
454 [toolbar setDelegate:nil];
455 [[NSNotificationCenter defaultCenter] removeObserver:self];
456 //[[backendProxy connectionForProxy] invalidate];
457 //[windowController close];
458 [windowController cleanup];
461 - (void)processInputQueue:(NSArray *)queue
463 if (!isInitialized) return;
465 // NOTE: This method must not raise any exceptions (see comment in the
468 [self doProcessInputQueue:queue];
469 [windowController processInputQueueDidFinish];
471 @catch (NSException *ex) {
472 ASLogNotice(@"Exception: pid=%d id=%d reason=%@", pid, identifier, ex);
476 - (NSToolbarItem *)toolbar:(NSToolbar *)theToolbar
477 itemForItemIdentifier:(NSString *)itemId
478 willBeInsertedIntoToolbar:(BOOL)flag
480 NSToolbarItem *item = [toolbarItemDict objectForKey:itemId];
482 ASLogWarn(@"No toolbar item with id '%@'", itemId);
488 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)theToolbar
493 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)theToolbar
498 @end // MMVimController
502 @implementation MMVimController (Private)
504 - (void)doProcessInputQueue:(NSArray *)queue
506 NSMutableArray *delayQueue = nil;
508 unsigned i, count = [queue count];
510 ASLogWarn(@"Uneven number of components (%d) in command queue. "
511 "Skipping...", count);
515 for (i = 0; i < count; i += 2) {
516 NSData *value = [queue objectAtIndex:i];
517 NSData *data = [queue objectAtIndex:i+1];
519 int msgid = *((int*)[value bytes]);
521 BOOL inDefaultMode = [[[NSRunLoop currentRunLoop] currentMode]
522 isEqual:NSDefaultRunLoopMode];
523 if (!inDefaultMode && isUnsafeMessage(msgid)) {
524 // NOTE: Because we may be listening to DO messages in "event
525 // tracking mode" we have to take extra care when doing things
526 // like releasing view items (and other Cocoa objects).
527 // Messages that may be potentially "unsafe" are delayed until
528 // the run loop is back to default mode at which time they are
529 // safe to call again.
530 // A problem with this approach is that it is hard to
531 // classify which messages are unsafe. As a rule of thumb, if
532 // a message may release an object used by the Cocoa framework
533 // (e.g. views) then the message should be considered unsafe.
534 // Delaying messages may have undesired side-effects since it
535 // means that messages may not be processed in the order Vim
536 // sent them, so beware.
538 delayQueue = [NSMutableArray array];
540 ASLogDebug(@"Adding unsafe message '%s' to delay queue (mode=%@)",
541 MessageStrings[msgid],
542 [[NSRunLoop currentRunLoop] currentMode]);
543 [delayQueue addObject:value];
544 [delayQueue addObject:data];
546 [self handleMessage:msgid data:data];
551 ASLogDebug(@" Flushing delay queue (%d items)",
552 [delayQueue count]/2);
553 [self performSelector:@selector(processInputQueue:)
554 withObject:delayQueue
559 - (void)handleMessage:(int)msgid data:(NSData *)data
561 if (OpenWindowMsgID == msgid) {
562 [windowController openWindow];
564 // If the vim controller is preloading then the window will be
565 // displayed when it is taken off the preload cache.
567 [windowController showWindow];
568 } else if (BatchDrawMsgID == msgid) {
569 [[[windowController vimView] textView] performBatchDrawWithData:data];
570 } else if (SelectTabMsgID == msgid) {
571 #if 0 // NOTE: Tab selection is done inside updateTabsWithData:.
572 const void *bytes = [data bytes];
573 int idx = *((int*)bytes);
574 [windowController selectTabWithIndex:idx];
576 } else if (UpdateTabBarMsgID == msgid) {
577 [windowController updateTabsWithData:data];
578 } else if (ShowTabBarMsgID == msgid) {
579 [windowController showTabBar:YES];
580 } else if (HideTabBarMsgID == msgid) {
581 [windowController showTabBar:NO];
582 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid ||
583 SetTextDimensionsReplyMsgID == msgid) {
584 const void *bytes = [data bytes];
585 int rows = *((int*)bytes); bytes += sizeof(int);
586 int cols = *((int*)bytes); bytes += sizeof(int);
588 [windowController setTextDimensionsWithRows:rows
590 isLive:(LiveResizeMsgID==msgid)
591 isReply:(SetTextDimensionsReplyMsgID==msgid)];
592 } else if (SetWindowTitleMsgID == msgid) {
593 const void *bytes = [data bytes];
594 int len = *((int*)bytes); bytes += sizeof(int);
596 NSString *string = [[NSString alloc] initWithBytes:(void*)bytes
597 length:len encoding:NSUTF8StringEncoding];
599 // While in live resize the window title displays the dimensions of the
600 // window so don't clobber this with a spurious "set title" message
602 if (![[windowController vimView] inLiveResize])
603 [windowController setTitle:string];
606 } else if (SetDocumentFilenameMsgID == msgid) {
607 const void *bytes = [data bytes];
608 int len = *((int*)bytes); bytes += sizeof(int);
611 NSString *filename = [[NSString alloc] initWithBytes:(void*)bytes
612 length:len encoding:NSUTF8StringEncoding];
614 [windowController setDocumentFilename:filename];
618 [windowController setDocumentFilename:@""];
620 } else if (AddMenuMsgID == msgid) {
621 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
622 [self addMenuWithDescriptor:[attrs objectForKey:@"descriptor"]
623 atIndex:[[attrs objectForKey:@"index"] intValue]];
624 } else if (AddMenuItemMsgID == msgid) {
625 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
626 [self addMenuItemWithDescriptor:[attrs objectForKey:@"descriptor"]
627 atIndex:[[attrs objectForKey:@"index"] intValue]
628 tip:[attrs objectForKey:@"tip"]
629 icon:[attrs objectForKey:@"icon"]
630 keyEquivalent:[attrs objectForKey:@"keyEquivalent"]
631 modifierMask:[[attrs objectForKey:@"modifierMask"] intValue]
632 action:[attrs objectForKey:@"action"]
633 isAlternate:[[attrs objectForKey:@"isAlternate"] boolValue]];
634 } else if (RemoveMenuItemMsgID == msgid) {
635 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
636 [self removeMenuItemWithDescriptor:[attrs objectForKey:@"descriptor"]];
637 } else if (EnableMenuItemMsgID == msgid) {
638 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
639 [self enableMenuItemWithDescriptor:[attrs objectForKey:@"descriptor"]
640 state:[[attrs objectForKey:@"enable"] boolValue]];
641 } else if (ShowToolbarMsgID == msgid) {
642 const void *bytes = [data bytes];
643 int enable = *((int*)bytes); bytes += sizeof(int);
644 int flags = *((int*)bytes); bytes += sizeof(int);
646 int mode = NSToolbarDisplayModeDefault;
647 if (flags & ToolbarLabelFlag) {
648 mode = flags & ToolbarIconFlag ? NSToolbarDisplayModeIconAndLabel
649 : NSToolbarDisplayModeLabelOnly;
650 } else if (flags & ToolbarIconFlag) {
651 mode = NSToolbarDisplayModeIconOnly;
654 int size = flags & ToolbarSizeRegularFlag ? NSToolbarSizeModeRegular
655 : NSToolbarSizeModeSmall;
657 [windowController showToolbar:enable size:size mode:mode];
658 } else if (CreateScrollbarMsgID == msgid) {
659 const void *bytes = [data bytes];
660 long ident = *((long*)bytes); bytes += sizeof(long);
661 int type = *((int*)bytes); bytes += sizeof(int);
663 [windowController createScrollbarWithIdentifier:ident type:type];
664 } else if (DestroyScrollbarMsgID == msgid) {
665 const void *bytes = [data bytes];
666 long ident = *((long*)bytes); bytes += sizeof(long);
668 [windowController destroyScrollbarWithIdentifier:ident];
669 } else if (ShowScrollbarMsgID == msgid) {
670 const void *bytes = [data bytes];
671 long ident = *((long*)bytes); bytes += sizeof(long);
672 int visible = *((int*)bytes); bytes += sizeof(int);
674 [windowController showScrollbarWithIdentifier:ident state:visible];
675 } else if (SetScrollbarPositionMsgID == msgid) {
676 const void *bytes = [data bytes];
677 long ident = *((long*)bytes); bytes += sizeof(long);
678 int pos = *((int*)bytes); bytes += sizeof(int);
679 int len = *((int*)bytes); bytes += sizeof(int);
681 [windowController setScrollbarPosition:pos length:len
683 } else if (SetScrollbarThumbMsgID == msgid) {
684 const void *bytes = [data bytes];
685 long ident = *((long*)bytes); bytes += sizeof(long);
686 float val = *((float*)bytes); bytes += sizeof(float);
687 float prop = *((float*)bytes); bytes += sizeof(float);
689 [windowController setScrollbarThumbValue:val proportion:prop
691 } else if (SetFontMsgID == msgid) {
692 const void *bytes = [data bytes];
693 float size = *((float*)bytes); bytes += sizeof(float);
694 int len = *((int*)bytes); bytes += sizeof(int);
695 NSString *name = [[NSString alloc]
696 initWithBytes:(void*)bytes length:len
697 encoding:NSUTF8StringEncoding];
698 NSFont *font = [NSFont fontWithName:name size:size];
700 // This should only happen if the default font was not loaded in
701 // which case we fall back on using the Cocoa default fixed width
703 font = [NSFont userFixedPitchFontOfSize:size];
706 [windowController setFont:font];
708 } else if (SetWideFontMsgID == msgid) {
709 const void *bytes = [data bytes];
710 float size = *((float*)bytes); bytes += sizeof(float);
711 int len = *((int*)bytes); bytes += sizeof(int);
713 NSString *name = [[NSString alloc]
714 initWithBytes:(void*)bytes length:len
715 encoding:NSUTF8StringEncoding];
716 NSFont *font = [NSFont fontWithName:name size:size];
717 [windowController setWideFont:font];
721 [windowController setWideFont:nil];
723 } else if (SetDefaultColorsMsgID == msgid) {
724 const void *bytes = [data bytes];
725 unsigned bg = *((unsigned*)bytes); bytes += sizeof(unsigned);
726 unsigned fg = *((unsigned*)bytes); bytes += sizeof(unsigned);
727 NSColor *back = [NSColor colorWithArgbInt:bg];
728 NSColor *fore = [NSColor colorWithRgbInt:fg];
730 [windowController setDefaultColorsBackground:back foreground:fore];
731 } else if (ExecuteActionMsgID == msgid) {
732 const void *bytes = [data bytes];
733 int len = *((int*)bytes); bytes += sizeof(int);
734 NSString *actionName = [[NSString alloc]
735 initWithBytes:(void*)bytes length:len
736 encoding:NSUTF8StringEncoding];
738 SEL sel = NSSelectorFromString(actionName);
739 [NSApp sendAction:sel to:nil from:self];
741 [actionName release];
742 } else if (ShowPopupMenuMsgID == msgid) {
743 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
745 // The popup menu enters a modal loop so delay this call so that we
746 // don't block inside processInputQueue:.
747 [self performSelector:@selector(popupMenuWithAttributes:)
750 } else if (SetMouseShapeMsgID == msgid) {
751 const void *bytes = [data bytes];
752 int shape = *((int*)bytes); bytes += sizeof(int);
754 [windowController setMouseShape:shape];
755 } else if (AdjustLinespaceMsgID == msgid) {
756 const void *bytes = [data bytes];
757 int linespace = *((int*)bytes); bytes += sizeof(int);
759 [windowController adjustLinespace:linespace];
760 } else if (ActivateMsgID == msgid) {
761 [NSApp activateIgnoringOtherApps:YES];
762 [[windowController window] makeKeyAndOrderFront:self];
763 } else if (SetServerNameMsgID == msgid) {
764 NSString *name = [[NSString alloc] initWithData:data
765 encoding:NSUTF8StringEncoding];
766 [self setServerName:name];
768 } else if (EnterFullscreenMsgID == msgid) {
769 const void *bytes = [data bytes];
770 int fuoptions = *((int*)bytes); bytes += sizeof(int);
771 int bg = *((int*)bytes);
772 NSColor *back = [NSColor colorWithArgbInt:bg];
774 [windowController enterFullscreen:fuoptions backgroundColor:back];
775 } else if (LeaveFullscreenMsgID == msgid) {
776 [windowController leaveFullscreen];
777 } else if (BuffersNotModifiedMsgID == msgid) {
778 [windowController setBuffersModified:NO];
779 } else if (BuffersModifiedMsgID == msgid) {
780 [windowController setBuffersModified:YES];
781 } else if (SetPreEditPositionMsgID == msgid) {
782 const int *dim = (const int*)[data bytes];
783 [[[windowController vimView] textView] setPreEditRow:dim[0]
785 } else if (EnableAntialiasMsgID == msgid) {
786 [[[windowController vimView] textView] setAntialias:YES];
787 } else if (DisableAntialiasMsgID == msgid) {
788 [[[windowController vimView] textView] setAntialias:NO];
789 } else if (SetVimStateMsgID == msgid) {
790 NSDictionary *dict = [NSDictionary dictionaryWithData:data];
793 vimState = [dict retain];
795 } else if (CloseWindowMsgID == msgid) {
796 [self scheduleClose];
797 } else if (SetFullscreenColorMsgID == msgid) {
798 const int *bg = (const int*)[data bytes];
799 NSColor *color = [NSColor colorWithRgbInt:*bg];
801 [windowController setFullscreenBackgroundColor:color];
802 } else if (ShowFindReplaceDialogMsgID == msgid) {
803 NSDictionary *dict = [NSDictionary dictionaryWithData:data];
805 [[MMFindReplaceController sharedInstance]
806 showWithText:[dict objectForKey:@"text"]
807 flags:[[dict objectForKey:@"flags"] intValue]];
809 } else if (ActivateKeyScriptMsgID == msgid) {
810 [[[windowController vimView] textView] activateIm:YES];
811 } else if (DeactivateKeyScriptMsgID == msgid) {
812 [[[windowController vimView] textView] activateIm:NO];
813 } else if (EnableImControlMsgID == msgid) {
814 [[[windowController vimView] textView] setImControl:YES];
815 } else if (DisableImControlMsgID == msgid) {
816 [[[windowController vimView] textView] setImControl:NO];
817 } else if (BrowseForFileMsgID == msgid) {
818 NSDictionary *dict = [NSDictionary dictionaryWithData:data];
820 [self handleBrowseForFile:dict];
821 } else if (ShowDialogMsgID == msgid) {
822 NSDictionary *dict = [NSDictionary dictionaryWithData:data];
824 [self handleShowDialog:dict];
825 // IMPORTANT: When adding a new message, make sure to update
826 // isUnsafeMessage() if necessary!
828 ASLogWarn(@"Unknown message received (msgid=%d)", msgid);
832 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
833 context:(void *)context
835 NSString *path = (code == NSOKButton) ? [panel filename] : nil;
836 ASLogDebug(@"Open/save panel path=%@", path);
838 // NOTE! This causes the sheet animation to run its course BEFORE the rest
839 // of this function is executed. If we do not wait for the sheet to
840 // disappear before continuing it can happen that the controller is
841 // released from under us (i.e. we'll crash and burn) because this
842 // animation is otherwise performed in the default run loop mode!
843 [panel orderOut:self];
845 // NOTE! setDialogReturn: is a synchronous call so set a proper timeout to
846 // avoid waiting forever for it to finish. We make this a synchronous call
847 // so that we can be fairly certain that Vim doesn't think the dialog box
848 // is still showing when MacVim has in fact already dismissed it.
849 NSConnection *conn = [backendProxy connectionForProxy];
850 NSTimeInterval oldTimeout = [conn requestTimeout];
851 [conn setRequestTimeout:MMSetDialogReturnTimeout];
854 [backendProxy setDialogReturn:path];
856 // Add file to the "Recent Files" menu (this ensures that files that
857 // are opened/saved from a :browse command are added to this menu).
859 [[NSDocumentController sharedDocumentController]
860 noteNewRecentFilePath:path];
862 @catch (NSException *ex) {
863 ASLogNotice(@"Exception: pid=%d id=%d reason=%@", pid, identifier, ex);
866 [conn setRequestTimeout:oldTimeout];
870 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context
874 code = code - NSAlertFirstButtonReturn + 1;
876 if ([alert isKindOfClass:[MMAlert class]] && [alert textField]) {
877 ret = [NSArray arrayWithObjects:[NSNumber numberWithInt:code],
878 [[alert textField] stringValue], nil];
880 ret = [NSArray arrayWithObject:[NSNumber numberWithInt:code]];
883 ASLogDebug(@"Alert return=%@", ret);
885 // NOTE! This causes the sheet animation to run its course BEFORE the rest
886 // of this function is executed. If we do not wait for the sheet to
887 // disappear before continuing it can happen that the controller is
888 // released from under us (i.e. we'll crash and burn) because this
889 // animation is otherwise performed in the default run loop mode!
890 [[alert window] orderOut:self];
893 [backendProxy setDialogReturn:ret];
895 @catch (NSException *ex) {
896 ASLogNotice(@"setDialogReturn: failed: pid=%d id=%d reason=%@",
897 pid, identifier, ex);
901 - (NSMenuItem *)menuItemForDescriptor:(NSArray *)desc
903 if (!(desc && [desc count] > 0)) return nil;
905 NSString *rootName = [desc objectAtIndex:0];
906 NSArray *rootItems = [rootName hasPrefix:@"PopUp"] ? popupMenuItems
907 : [mainMenu itemArray];
909 NSMenuItem *item = nil;
910 int i, count = [rootItems count];
911 for (i = 0; i < count; ++i) {
912 item = [rootItems objectAtIndex:i];
913 if ([[item title] isEqual:rootName])
917 if (i == count) return nil;
919 count = [desc count];
920 for (i = 1; i < count; ++i) {
921 item = [[item submenu] itemWithTitle:[desc objectAtIndex:i]];
922 if (!item) return nil;
928 - (NSMenu *)parentMenuForDescriptor:(NSArray *)desc
930 if (!(desc && [desc count] > 0)) return nil;
932 NSString *rootName = [desc objectAtIndex:0];
933 NSArray *rootItems = [rootName hasPrefix:@"PopUp"] ? popupMenuItems
934 : [mainMenu itemArray];
937 int i, count = [rootItems count];
938 for (i = 0; i < count; ++i) {
939 NSMenuItem *item = [rootItems objectAtIndex:i];
940 if ([[item title] isEqual:rootName]) {
941 menu = [item submenu];
946 if (!menu) return nil;
948 count = [desc count] - 1;
949 for (i = 1; i < count; ++i) {
950 NSMenuItem *item = [menu itemWithTitle:[desc objectAtIndex:i]];
951 menu = [item submenu];
952 if (!menu) return nil;
958 - (NSMenu *)topLevelMenuForTitle:(NSString *)title
960 // Search only the top-level menus.
962 unsigned i, count = [popupMenuItems count];
963 for (i = 0; i < count; ++i) {
964 NSMenuItem *item = [popupMenuItems objectAtIndex:i];
965 if ([title isEqual:[item title]])
966 return [item submenu];
969 count = [mainMenu numberOfItems];
970 for (i = 0; i < count; ++i) {
971 NSMenuItem *item = [mainMenu itemAtIndex:i];
972 if ([title isEqual:[item title]])
973 return [item submenu];
979 - (void)addMenuWithDescriptor:(NSArray *)desc atIndex:(int)idx
981 if (!(desc && [desc count] > 0 && idx >= 0)) return;
983 NSString *rootName = [desc objectAtIndex:0];
984 if ([rootName isEqual:@"ToolBar"]) {
985 // The toolbar only has one menu, we take this as a hint to create a
986 // toolbar, then we return.
988 // NOTE! Each toolbar must have a unique identifier, else each
989 // window will have the same toolbar.
990 NSString *ident = [NSString stringWithFormat:@"%d", (int)self];
991 toolbar = [[NSToolbar alloc] initWithIdentifier:ident];
993 [toolbar setShowsBaselineSeparator:NO];
994 [toolbar setDelegate:self];
995 [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
996 [toolbar setSizeMode:NSToolbarSizeModeSmall];
998 [windowController setToolbar:toolbar];
1004 // This is either a main menu item or a popup menu item.
1005 NSString *title = [desc lastObject];
1006 NSMenuItem *item = [[NSMenuItem alloc] init];
1007 NSMenu *menu = [[NSMenu alloc] initWithTitle:title];
1009 [item setTitle:title];
1010 [item setSubmenu:menu];
1012 NSMenu *parent = [self parentMenuForDescriptor:desc];
1013 if (!parent && [rootName hasPrefix:@"PopUp"]) {
1014 if ([popupMenuItems count] <= idx) {
1015 [popupMenuItems addObject:item];
1017 [popupMenuItems insertObject:item atIndex:idx];
1020 // If descriptor has no parent and its not a popup (or toolbar) menu,
1021 // then it must belong to main menu.
1022 if (!parent) parent = mainMenu;
1024 if ([parent numberOfItems] <= idx) {
1025 [parent addItem:item];
1027 [parent insertItem:item atIndex:idx];
1035 - (void)addMenuItemWithDescriptor:(NSArray *)desc
1038 icon:(NSString *)icon
1039 keyEquivalent:(NSString *)keyEquivalent
1040 modifierMask:(int)modifierMask
1041 action:(NSString *)action
1042 isAlternate:(BOOL)isAlternate
1044 if (!(desc && [desc count] > 1 && idx >= 0)) return;
1046 NSString *title = [desc lastObject];
1047 NSString *rootName = [desc objectAtIndex:0];
1049 if ([rootName isEqual:@"ToolBar"]) {
1050 if (toolbar && [desc count] == 2)
1051 [self addToolbarItemWithLabel:title tip:tip icon:icon atIndex:idx];
1055 NSMenu *parent = [self parentMenuForDescriptor:desc];
1057 ASLogWarn(@"Menu item '%@' has no parent",
1058 [desc componentsJoinedByString:@"->"]);
1062 NSMenuItem *item = nil;
1063 if (0 == [title length]
1064 || ([title hasPrefix:@"-"] && [title hasSuffix:@"-"])) {
1065 item = [NSMenuItem separatorItem];
1066 [item setTitle:title];
1068 item = [[[NSMenuItem alloc] init] autorelease];
1069 [item setTitle:title];
1071 // Note: It is possible to set the action to a message that "doesn't
1072 // exist" without problems. We take advantage of this when adding
1073 // "dummy items" e.g. when dealing with the "Recent Files" menu (in
1074 // which case a recentFilesDummy: action is set, although it is never
1076 if ([action length] > 0)
1077 [item setAction:NSSelectorFromString(action)];
1079 [item setAction:@selector(vimMenuItemAction:)];
1080 if ([tip length] > 0) [item setToolTip:tip];
1081 if ([keyEquivalent length] > 0) {
1082 [item setKeyEquivalent:keyEquivalent];
1083 [item setKeyEquivalentModifierMask:modifierMask];
1085 [item setAlternate:isAlternate];
1087 // The tag is used to indicate whether Vim thinks a menu item should be
1088 // enabled or disabled. By default Vim thinks menu items are enabled.
1092 if ([parent numberOfItems] <= idx) {
1093 [parent addItem:item];
1095 [parent insertItem:item atIndex:idx];
1099 - (void)removeMenuItemWithDescriptor:(NSArray *)desc
1101 if (!(desc && [desc count] > 0)) return;
1103 NSString *title = [desc lastObject];
1104 NSString *rootName = [desc objectAtIndex:0];
1105 if ([rootName isEqual:@"ToolBar"]) {
1107 // Only remove toolbar items, never actually remove the toolbar
1108 // itself or strange things may happen.
1109 if ([desc count] == 2) {
1110 int idx = [toolbar indexOfItemWithItemIdentifier:title];
1111 if (idx != NSNotFound)
1112 [toolbar removeItemAtIndex:idx];
1118 NSMenuItem *item = [self menuItemForDescriptor:desc];
1120 ASLogWarn(@"Failed to remove menu item, descriptor not found: %@",
1121 [desc componentsJoinedByString:@"->"]);
1127 if ([item menu] == [NSApp mainMenu] || ![item menu]) {
1128 // NOTE: To be on the safe side we try to remove the item from
1129 // both arrays (it is ok to call removeObject: even if an array
1130 // does not contain the object to remove).
1131 [popupMenuItems removeObject:item];
1135 [[item menu] removeItem:item];
1140 - (void)enableMenuItemWithDescriptor:(NSArray *)desc state:(BOOL)on
1142 if (!(desc && [desc count] > 0)) return;
1144 NSString *rootName = [desc objectAtIndex:0];
1145 if ([rootName isEqual:@"ToolBar"]) {
1146 if (toolbar && [desc count] == 2) {
1147 NSString *title = [desc lastObject];
1148 [[toolbar itemWithItemIdentifier:title] setEnabled:on];
1151 // Use tag to set whether item is enabled or disabled instead of
1152 // calling setEnabled:. This way the menus can autoenable themselves
1153 // but at the same time Vim can set if a menu is enabled whenever it
1155 [[self menuItemForDescriptor:desc] setTag:on];
1159 - (void)addToolbarItemToDictionaryWithLabel:(NSString *)title
1160 toolTip:(NSString *)tip
1161 icon:(NSString *)icon
1163 // If the item corresponds to a separator then do nothing, since it is
1164 // already defined by Cocoa.
1165 if (!title || [title isEqual:NSToolbarSeparatorItemIdentifier]
1166 || [title isEqual:NSToolbarSpaceItemIdentifier]
1167 || [title isEqual:NSToolbarFlexibleSpaceItemIdentifier])
1170 NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:title];
1171 [item setLabel:title];
1172 [item setToolTip:tip];
1173 [item setAction:@selector(vimToolbarItemAction:)];
1174 [item setAutovalidates:NO];
1176 NSImage *img = [NSImage imageNamed:icon];
1178 img = [[[NSImage alloc] initByReferencingFile:icon] autorelease];
1179 if (!(img && [img isValid]))
1183 ASLogNotice(@"Could not find image with name '%@' to use as toolbar"
1184 " image for identifier '%@';"
1185 " using default toolbar icon '%@' instead.",
1186 icon, title, MMDefaultToolbarImageName);
1188 img = [NSImage imageNamed:MMDefaultToolbarImageName];
1191 [item setImage:img];
1193 [toolbarItemDict setObject:item forKey:title];
1198 - (void)addToolbarItemWithLabel:(NSString *)label
1200 icon:(NSString *)icon
1203 if (!toolbar) return;
1205 // Check for separator items.
1207 label = NSToolbarSeparatorItemIdentifier;
1208 } else if ([label length] >= 2 && [label hasPrefix:@"-"]
1209 && [label hasSuffix:@"-"]) {
1210 // The label begins and ends with '-'; decided which kind of separator
1211 // item it is by looking at the prefix.
1212 if ([label hasPrefix:@"-space"]) {
1213 label = NSToolbarSpaceItemIdentifier;
1214 } else if ([label hasPrefix:@"-flexspace"]) {
1215 label = NSToolbarFlexibleSpaceItemIdentifier;
1217 label = NSToolbarSeparatorItemIdentifier;
1221 [self addToolbarItemToDictionaryWithLabel:label toolTip:tip icon:icon];
1223 int maxIdx = [[toolbar items] count];
1224 if (maxIdx < idx) idx = maxIdx;
1226 [toolbar insertItemWithItemIdentifier:label atIndex:idx];
1229 - (void)popupMenuWithDescriptor:(NSArray *)desc
1230 atRow:(NSNumber *)row
1231 column:(NSNumber *)col
1233 NSMenu *menu = [[self menuItemForDescriptor:desc] submenu];
1236 id textView = [[windowController vimView] textView];
1239 // TODO: Let textView convert (row,col) to NSPoint.
1240 int r = [row intValue];
1241 int c = [col intValue];
1242 NSSize cellSize = [textView cellSize];
1243 pt = NSMakePoint((c+1)*cellSize.width, (r+1)*cellSize.height);
1244 pt = [textView convertPoint:pt toView:nil];
1246 pt = [[windowController window] mouseLocationOutsideOfEventStream];
1249 NSEvent *event = [NSEvent mouseEventWithType:NSRightMouseDown
1253 windowNumber:[[windowController window] windowNumber]
1259 [NSMenu popUpContextMenu:menu withEvent:event forView:textView];
1262 - (void)popupMenuWithAttributes:(NSDictionary *)attrs
1266 [self popupMenuWithDescriptor:[attrs objectForKey:@"descriptor"]
1267 atRow:[attrs objectForKey:@"row"]
1268 column:[attrs objectForKey:@"column"]];
1271 - (void)connectionDidDie:(NSNotification *)notification
1273 ASLogDebug(@"%@", notification);
1274 [self scheduleClose];
1277 - (void)scheduleClose
1279 ASLogDebug(@"pid=%d id=%d", pid, identifier);
1281 // NOTE! This message can arrive at pretty much anytime, e.g. while
1282 // the run loop is the 'event tracking' mode. This means that Cocoa may
1283 // well be in the middle of processing some message while this message is
1284 // received. If we were to remove the vim controller straight away we may
1285 // free objects that Cocoa is currently using (e.g. view objects). The
1286 // following call ensures that the vim controller is not released until the
1287 // run loop is back in the 'default' mode.
1288 // Also, since the app may be multithreaded (e.g. as a result of showing
1289 // the open panel) we have to ensure this call happens on the main thread,
1290 // else there is a race condition that may lead to a crash.
1291 [[MMAppController sharedInstance]
1292 performSelectorOnMainThread:@selector(removeVimController:)
1295 modes:[NSArray arrayWithObject:
1296 NSDefaultRunLoopMode]];
1299 // NSSavePanel delegate
1300 - (void)panel:(id)sender willExpand:(BOOL)expanding
1302 // Show or hide the "show hidden files" button
1304 [sender setAccessoryView:showHiddenFilesView()];
1306 [sender setShowsHiddenFiles:NO];
1307 [sender setAccessoryView:nil];
1311 - (void)handleBrowseForFile:(NSDictionary *)attr
1313 if (!isInitialized) return;
1315 NSString *dir = [attr objectForKey:@"dir"];
1316 BOOL saving = [[attr objectForKey:@"saving"] boolValue];
1319 // 'dir == nil' means: set dir to the pwd of the Vim process, or let
1320 // open dialog decide (depending on the below user default).
1321 BOOL trackPwd = [[NSUserDefaults standardUserDefaults]
1322 boolForKey:MMDialogsTrackPwdKey];
1324 dir = [vimState objectForKey:@"pwd"];
1328 NSSavePanel *panel = [NSSavePanel savePanel];
1330 // The delegate will be notified when the panel is expanded at which
1331 // time we may hide/show the "show hidden files" button (this button is
1332 // always visible for the open panel since it is always expanded).
1333 [panel setDelegate:self];
1334 if ([panel isExpanded])
1335 [panel setAccessoryView:showHiddenFilesView()];
1337 [panel beginSheetForDirectory:dir file:nil
1338 modalForWindow:[windowController window]
1340 didEndSelector:@selector(savePanelDidEnd:code:context:)
1343 NSOpenPanel *panel = [NSOpenPanel openPanel];
1344 [panel setAllowsMultipleSelection:NO];
1345 [panel setAccessoryView:showHiddenFilesView()];
1347 [panel beginSheetForDirectory:dir file:nil types:nil
1348 modalForWindow:[windowController window]
1350 didEndSelector:@selector(savePanelDidEnd:code:context:)
1355 - (void)handleShowDialog:(NSDictionary *)attr
1357 if (!isInitialized) return;
1359 NSArray *buttonTitles = [attr objectForKey:@"buttonTitles"];
1360 if (!(buttonTitles && [buttonTitles count])) return;
1362 int style = [[attr objectForKey:@"alertStyle"] intValue];
1363 NSString *message = [attr objectForKey:@"messageText"];
1364 NSString *text = [attr objectForKey:@"informativeText"];
1365 NSString *textFieldString = [attr objectForKey:@"textFieldString"];
1366 MMAlert *alert = [[MMAlert alloc] init];
1368 // NOTE! This has to be done before setting the informative text.
1369 if (textFieldString)
1370 [alert setTextFieldString:textFieldString];
1372 [alert setAlertStyle:style];
1375 [alert setMessageText:message];
1377 // If no message text is specified 'Alert' is used, which we don't
1378 // want, so set an empty string as message text.
1379 [alert setMessageText:@""];
1383 [alert setInformativeText:text];
1384 } else if (textFieldString) {
1385 // Make sure there is always room for the input text field.
1386 [alert setInformativeText:@""];
1389 unsigned i, count = [buttonTitles count];
1390 for (i = 0; i < count; ++i) {
1391 NSString *title = [buttonTitles objectAtIndex:i];
1392 // NOTE: The title of the button may contain the character '&' to
1393 // indicate that the following letter should be the key equivalent
1394 // associated with the button. Extract this letter and lowercase it.
1395 NSString *keyEquivalent = nil;
1396 NSRange hotkeyRange = [title rangeOfString:@"&"];
1397 if (NSNotFound != hotkeyRange.location) {
1398 if ([title length] > NSMaxRange(hotkeyRange)) {
1399 NSRange keyEquivRange = NSMakeRange(hotkeyRange.location+1, 1);
1400 keyEquivalent = [[title substringWithRange:keyEquivRange]
1404 NSMutableString *string = [NSMutableString stringWithString:title];
1405 [string deleteCharactersInRange:hotkeyRange];
1409 [alert addButtonWithTitle:title];
1411 // Set key equivalent for the button, but only if NSAlert hasn't
1412 // already done so. (Check the documentation for
1413 // - [NSAlert addButtonWithTitle:] to see what key equivalents are
1414 // automatically assigned.)
1415 NSButton *btn = [[alert buttons] lastObject];
1416 if ([[btn keyEquivalent] length] == 0 && keyEquivalent) {
1417 [btn setKeyEquivalent:keyEquivalent];
1421 [alert beginSheetModalForWindow:[windowController window]
1423 didEndSelector:@selector(alertDidEnd:code:context:)
1430 @end // MMVimController (Private)
1435 @implementation MMAlert
1441 [textField release]; textField = nil;
1445 - (void)setTextFieldString:(NSString *)textFieldString
1447 [textField release];
1448 textField = [[NSTextField alloc] init];
1449 [textField setStringValue:textFieldString];
1452 - (NSTextField *)textField
1457 - (void)setInformativeText:(NSString *)text
1460 // HACK! Add some space for the text field.
1461 [super setInformativeText:[text stringByAppendingString:@"\n\n\n"]];
1463 [super setInformativeText:text];
1467 - (void)beginSheetModalForWindow:(NSWindow *)window
1468 modalDelegate:(id)delegate
1469 didEndSelector:(SEL)didEndSelector
1470 contextInfo:(void *)contextInfo
1472 [super beginSheetModalForWindow:window
1473 modalDelegate:delegate
1474 didEndSelector:didEndSelector
1475 contextInfo:contextInfo];
1477 // HACK! Place the input text field at the bottom of the informative text
1478 // (which has been made a bit larger by adding newline characters).
1479 NSView *contentView = [[self window] contentView];
1480 NSRect rect = [contentView frame];
1481 rect.origin.y = rect.size.height;
1483 NSArray *subviews = [contentView subviews];
1484 unsigned i, count = [subviews count];
1485 for (i = 0; i < count; ++i) {
1486 NSView *view = [subviews objectAtIndex:i];
1487 if ([view isKindOfClass:[NSTextField class]]
1488 && [view frame].origin.y < rect.origin.y) {
1489 // NOTE: The informative text field is the lowest NSTextField in
1490 // the alert dialog.
1491 rect = [view frame];
1495 rect.size.height = MMAlertTextFieldHeight;
1496 [textField setFrame:rect];
1497 [contentView addSubview:textField];
1498 [textField becomeFirstResponder];
1507 isUnsafeMessage(int msgid)
1509 // Messages that may release Cocoa objects must be added to this list. For
1510 // example, UpdateTabBarMsgID may delete NSTabViewItem objects so it goes
1512 static int unsafeMessages[] = { // REASON MESSAGE IS ON THIS LIST:
1513 //OpenWindowMsgID, // Changes lots of state
1514 UpdateTabBarMsgID, // May delete NSTabViewItem
1515 RemoveMenuItemMsgID, // Deletes NSMenuItem
1516 DestroyScrollbarMsgID, // Deletes NSScroller
1517 ExecuteActionMsgID, // Impossible to predict
1518 ShowPopupMenuMsgID, // Enters modal loop
1520 EnterFullscreenMsgID, // Modifies delegate of window controller
1521 LeaveFullscreenMsgID, // Modifies delegate of window controller
1522 CloseWindowMsgID, // See note below
1523 BrowseForFileMsgID, // Enters modal loop
1524 ShowDialogMsgID, // Enters modal loop
1527 // NOTE about CloseWindowMsgID: If this arrives at the same time as say
1528 // ExecuteActionMsgID, then the "execute" message will be lost due to it
1529 // being queued and handled after the "close" message has caused the
1530 // controller to cleanup...UNLESS we add CloseWindowMsgID to the list of
1531 // unsafe messages. This is the _only_ reason it is on this list (since
1532 // all that happens in response to it is that we schedule another message
1533 // for later handling).
1535 int i, count = sizeof(unsafeMessages)/sizeof(unsafeMessages[0]);
1536 for (i = 0; i < count; ++i)
1537 if (msgid == unsafeMessages[i])