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 "MMFindReplaceController.h"
31 #import "MMTextView.h"
32 #import "MMVimController.h"
34 #import "MMWindowController.h"
35 #import "Miscellaneous.h"
37 #ifdef MM_ENABLE_PLUGINS
38 #import "MMPlugInManager.h"
41 static NSString *MMDefaultToolbarImageName = @"Attention";
42 static int MMAlertTextFieldHeight = 22;
44 // NOTE: By default a message sent to the backend will be dropped if it cannot
45 // be delivered instantly; otherwise there is a possibility that MacVim will
46 // 'beachball' while waiting to deliver DO messages to an unresponsive Vim
47 // process. This means that you cannot rely on any message sent with
48 // sendMessage: to actually reach Vim.
49 static NSTimeInterval MMBackendProxyRequestTimeout = 0;
51 // Timeout used for setDialogReturn:.
52 static NSTimeInterval MMSetDialogReturnTimeout = 1.0;
54 static unsigned identifierCounter = 1;
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)doProcessInputQueue:(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;
98 - (void)handleBrowseForFile:(NSDictionary *)attr;
99 - (void)handleShowDialog:(NSDictionary *)attr;
105 @implementation MMVimController
107 - (id)initWithBackend:(id)backend pid:(int)processIdentifier
109 if (!(self = [super init]))
112 // TODO: Come up with a better way of creating an identifier.
113 identifier = identifierCounter++;
116 [[MMWindowController alloc] initWithVimController:self];
117 backendProxy = [backend retain];
118 popupMenuItems = [[NSMutableArray alloc] init];
119 toolbarItemDict = [[NSMutableDictionary alloc] init];
120 pid = processIdentifier;
121 creationDate = [[NSDate alloc] init];
123 NSConnection *connection = [backendProxy connectionForProxy];
125 // TODO: Check that this will not set the timeout for the root proxy
126 // (in MMAppController).
127 [connection setRequestTimeout:MMBackendProxyRequestTimeout];
129 [[NSNotificationCenter defaultCenter] addObserver:self
130 selector:@selector(connectionDidDie:)
131 name:NSConnectionDidDieNotification object:connection];
133 // Set up a main menu with only a "MacVim" menu (copied from a template
134 // which itself is set up in MainMenu.nib). The main menu is populated
136 mainMenu = [[NSMenu alloc] initWithTitle:@"MainMenu"];
137 NSMenuItem *appMenuItem = [[MMAppController sharedInstance]
138 appMenuItemTemplate];
139 appMenuItem = [[appMenuItem copy] autorelease];
141 // Note: If the title of the application menu is anything but what
142 // CFBundleName says then the application menu will not be typeset in
143 // boldface for some reason. (It should already be set when we copy
144 // from the default main menu, but this is not the case for some
146 NSString *appName = [[NSBundle mainBundle]
147 objectForInfoDictionaryKey:@"CFBundleName"];
148 [appMenuItem setTitle:appName];
150 [mainMenu addItem:appMenuItem];
152 #ifdef MM_ENABLE_PLUGINS
153 instanceMediator = [[MMPlugInInstanceMediator alloc]
154 initWithVimController:self];
168 #ifdef MM_ENABLE_PLUGINS
169 [instanceMediator release]; instanceMediator = nil;
172 [serverName release]; serverName = nil;
173 [backendProxy release]; backendProxy = nil;
175 [toolbarItemDict release]; toolbarItemDict = nil;
176 [toolbar release]; toolbar = nil;
177 [popupMenuItems release]; popupMenuItems = nil;
178 [windowController release]; windowController = nil;
180 [vimState release]; vimState = nil;
181 [mainMenu release]; mainMenu = nil;
182 [creationDate release]; creationDate = nil;
187 - (unsigned)vimControllerId
192 - (MMWindowController *)windowController
194 return windowController;
197 #ifdef MM_ENABLE_PLUGINS
198 - (MMPlugInInstanceMediator *)instanceMediator
200 return instanceMediator;
204 - (NSDictionary *)vimState
209 - (id)objectForVimStateKey:(NSString *)key
211 return [vimState objectForKey:key];
224 - (void)setIsPreloading:(BOOL)yn
229 - (NSDate *)creationDate
234 - (void)setServerName:(NSString *)name
236 if (name != serverName) {
237 [serverName release];
238 serverName = [name copy];
242 - (NSString *)serverName
252 - (void)dropFiles:(NSArray *)filenames forceOpen:(BOOL)force
254 filenames = normalizeFilenames(filenames);
255 ASLogInfo(@"filenames=%@ force=%d", filenames, force);
257 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
259 // Default to opening in tabs if layout is invalid or set to "windows".
260 int layout = [ud integerForKey:MMOpenLayoutKey];
261 if (layout < 0 || layout > MMLayoutTabs)
262 layout = MMLayoutTabs;
264 BOOL splitVert = [ud boolForKey:MMVerticalSplitKey];
265 if (splitVert && MMLayoutHorizontalSplit == layout)
266 layout = MMLayoutVerticalSplit;
268 NSDictionary *args = [NSDictionary dictionaryWithObjectsAndKeys:
269 [NSNumber numberWithInt:layout], @"layout",
270 filenames, @"filenames",
271 [NSNumber numberWithBool:force], @"forceOpen",
274 [self sendMessage:DropFilesMsgID data:[args dictionaryAsData]];
277 - (void)file:(NSString *)filename draggedToTabAtIndex:(NSUInteger)tabIndex
279 filename = normalizeFilename(filename);
280 ASLogInfo(@"filename=%@ index=%d", filename, tabIndex);
282 NSString *fnEsc = [filename stringByEscapingSpecialFilenameCharacters];
283 NSString *input = [NSString stringWithFormat:@"<C-\\><C-N>:silent "
285 "edit! %@<CR>", tabIndex + 1, fnEsc];
286 [self addVimInput:input];
289 - (void)filesDraggedToTabBar:(NSArray *)filenames
291 filenames = normalizeFilenames(filenames);
292 ASLogInfo(@"%@", filenames);
294 NSUInteger i, count = [filenames count];
295 NSMutableString *input = [NSMutableString stringWithString:@"<C-\\><C-N>"
296 ":silent! tabnext 9999"];
297 for (i = 0; i < count; i++) {
298 NSString *fn = [filenames objectAtIndex:i];
299 NSString *fnEsc = [fn stringByEscapingSpecialFilenameCharacters];
300 [input appendFormat:@"|tabedit %@", fnEsc];
302 [input appendString:@"<CR>"];
303 [self addVimInput:input];
306 - (void)dropString:(NSString *)string
308 ASLogInfo(@"%@", string);
309 int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
311 NSMutableData *data = [NSMutableData data];
313 [data appendBytes:&len length:sizeof(int)];
314 [data appendBytes:[string UTF8String] length:len];
316 [self sendMessage:DropStringMsgID data:data];
320 - (void)passArguments:(NSDictionary *)args
324 ASLogDebug(@"args=%@", args);
326 [self sendMessage:OpenWithArgumentsMsgID data:[args dictionaryAsData]];
328 // HACK! Fool findUnusedEditor into thinking that this controller is not
329 // unused anymore, in case it is called before the arguments have reached
330 // the Vim process. This should be a "safe" hack since the next time the
331 // Vim process flushes its output queue the state will be updated again (at
332 // which time the "unusedEditor" state will have been properly set).
333 NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:
335 [dict setObject:[NSNumber numberWithBool:NO] forKey:@"unusedEditor"];
337 vimState = [dict copy];
340 - (void)sendMessage:(int)msgid data:(NSData *)data
342 ASLogDebug(@"msg=%s (isInitialized=%d)",
343 MessageStrings[msgid], isInitialized);
345 if (!isInitialized) return;
348 [backendProxy processInput:msgid data:data];
350 @catch (NSException *ex) {
351 ASLogDebug(@"processInput:data: failed: pid=%d id=%d msg=%s reason=%@",
352 pid, identifier, MessageStrings[msgid], ex);
356 - (BOOL)sendMessageNow:(int)msgid data:(NSData *)data
357 timeout:(NSTimeInterval)timeout
359 // Send a message with a timeout. USE WITH EXTREME CAUTION! Sending
360 // messages in rapid succession with a timeout may cause MacVim to beach
361 // ball forever. In almost all circumstances sendMessage:data: should be
364 ASLogDebug(@"msg=%s (isInitialized=%d)",
365 MessageStrings[msgid], isInitialized);
370 if (timeout < 0) timeout = 0;
373 NSConnection *conn = [backendProxy connectionForProxy];
374 NSTimeInterval oldTimeout = [conn requestTimeout];
376 [conn setRequestTimeout:timeout];
379 [backendProxy processInput:msgid data:data];
381 @catch (NSException *ex) {
383 ASLogDebug(@"processInput:data: failed: pid=%d id=%d msg=%s reason=%@",
384 pid, identifier, MessageStrings[msgid], ex);
387 [conn setRequestTimeout:oldTimeout];
393 - (void)addVimInput:(NSString *)string
395 ASLogDebug(@"%@", string);
397 // This is a very general method of adding input to the Vim process. It is
398 // basically the same as calling remote_send() on the process (see
399 // ':h remote_send').
401 NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
402 [self sendMessage:AddInputMsgID data:data];
406 - (NSString *)evaluateVimExpression:(NSString *)expr
408 NSString *eval = nil;
411 eval = [backendProxy evaluateExpression:expr];
412 ASLogDebug(@"eval(%@)=%@", expr, eval);
414 @catch (NSException *ex) {
415 ASLogDebug(@"evaluateExpression: failed: pid=%d id=%d reason=%@",
416 pid, identifier, ex);
422 - (id)evaluateVimExpressionCocoa:(NSString *)expr
423 errorString:(NSString **)errstr
428 eval = [backendProxy evaluateExpressionCocoa:expr
430 ASLogDebug(@"eval(%@)=%@", expr, eval);
431 } @catch (NSException *ex) {
432 ASLogDebug(@"evaluateExpressionCocoa: failed: pid=%d id=%d reason=%@",
433 pid, identifier, ex);
434 *errstr = [ex reason];
447 if (!isInitialized) return;
449 // Remove any delayed calls made on this object.
450 [NSObject cancelPreviousPerformRequestsWithTarget:self];
453 [toolbar setDelegate:nil];
454 [[NSNotificationCenter defaultCenter] removeObserver:self];
455 //[[backendProxy connectionForProxy] invalidate];
456 //[windowController close];
457 [windowController cleanup];
460 - (void)processInputQueue:(NSArray *)queue
462 if (!isInitialized) return;
464 // NOTE: This method must not raise any exceptions (see comment in the
467 [self doProcessInputQueue:queue];
468 [windowController processInputQueueDidFinish];
470 @catch (NSException *ex) {
471 ASLogDebug(@"Exception: pid=%d id=%d reason=%@", pid, identifier, ex);
475 - (NSToolbarItem *)toolbar:(NSToolbar *)theToolbar
476 itemForItemIdentifier:(NSString *)itemId
477 willBeInsertedIntoToolbar:(BOOL)flag
479 NSToolbarItem *item = [toolbarItemDict objectForKey:itemId];
481 ASLogWarn(@"No toolbar item with id '%@'", itemId);
487 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)theToolbar
492 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)theToolbar
497 @end // MMVimController
501 @implementation MMVimController (Private)
503 - (void)doProcessInputQueue:(NSArray *)queue
505 NSMutableArray *delayQueue = nil;
507 unsigned i, count = [queue count];
509 ASLogWarn(@"Uneven number of components (%d) in command queue. "
510 "Skipping...", count);
514 for (i = 0; i < count; i += 2) {
515 NSData *value = [queue objectAtIndex:i];
516 NSData *data = [queue objectAtIndex:i+1];
518 int msgid = *((int*)[value bytes]);
520 BOOL inDefaultMode = [[[NSRunLoop currentRunLoop] currentMode]
521 isEqual:NSDefaultRunLoopMode];
522 if (!inDefaultMode && isUnsafeMessage(msgid)) {
523 // NOTE: Because we may be listening to DO messages in "event
524 // tracking mode" we have to take extra care when doing things
525 // like releasing view items (and other Cocoa objects).
526 // Messages that may be potentially "unsafe" are delayed until
527 // the run loop is back to default mode at which time they are
528 // safe to call again.
529 // A problem with this approach is that it is hard to
530 // classify which messages are unsafe. As a rule of thumb, if
531 // a message may release an object used by the Cocoa framework
532 // (e.g. views) then the message should be considered unsafe.
533 // Delaying messages may have undesired side-effects since it
534 // means that messages may not be processed in the order Vim
535 // sent them, so beware.
537 delayQueue = [NSMutableArray array];
539 ASLogDebug(@"Adding unsafe message '%s' to delay queue (mode=%@)",
540 MessageStrings[msgid],
541 [[NSRunLoop currentRunLoop] currentMode]);
542 [delayQueue addObject:value];
543 [delayQueue addObject:data];
545 [self handleMessage:msgid data:data];
550 ASLogDebug(@" Flushing delay queue (%d items)",
551 [delayQueue count]/2);
552 [self performSelector:@selector(processInputQueue:)
553 withObject:delayQueue
558 - (void)handleMessage:(int)msgid data:(NSData *)data
560 if (OpenWindowMsgID == msgid) {
561 [windowController openWindow];
563 // If the vim controller is preloading then the window will be
564 // displayed when it is taken off the preload cache.
566 [windowController showWindow];
567 } else if (BatchDrawMsgID == msgid) {
568 [[[windowController vimView] textView] performBatchDrawWithData:data];
569 } else if (SelectTabMsgID == msgid) {
570 #if 0 // NOTE: Tab selection is done inside updateTabsWithData:.
571 const void *bytes = [data bytes];
572 int idx = *((int*)bytes);
573 [windowController selectTabWithIndex:idx];
575 } else if (UpdateTabBarMsgID == msgid) {
576 [windowController updateTabsWithData:data];
577 } else if (ShowTabBarMsgID == msgid) {
578 [windowController showTabBar:YES];
579 } else if (HideTabBarMsgID == msgid) {
580 [windowController showTabBar:NO];
581 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid ||
582 SetTextDimensionsReplyMsgID == msgid) {
583 const void *bytes = [data bytes];
584 int rows = *((int*)bytes); bytes += sizeof(int);
585 int cols = *((int*)bytes); bytes += sizeof(int);
587 // NOTE: When a resize message originated in the frontend, Vim
588 // acknowledges it with a reply message. When this happens the window
589 // should not move (the frontend would already have moved the window).
590 BOOL onScreen = SetTextDimensionsReplyMsgID!=msgid;
592 [windowController setTextDimensionsWithRows:rows
594 isLive:(LiveResizeMsgID==msgid)
595 keepOnScreen:onScreen];
596 } else if (SetWindowTitleMsgID == msgid) {
597 const void *bytes = [data bytes];
598 int len = *((int*)bytes); bytes += sizeof(int);
600 NSString *string = [[NSString alloc] initWithBytes:(void*)bytes
601 length:len encoding:NSUTF8StringEncoding];
603 // While in live resize the window title displays the dimensions of the
604 // window so don't clobber this with a spurious "set title" message
606 if (![[windowController vimView] inLiveResize])
607 [windowController setTitle:string];
610 } else if (SetDocumentFilenameMsgID == msgid) {
611 const void *bytes = [data bytes];
612 int len = *((int*)bytes); bytes += sizeof(int);
615 NSString *filename = [[NSString alloc] initWithBytes:(void*)bytes
616 length:len encoding:NSUTF8StringEncoding];
618 [windowController setDocumentFilename:filename];
622 [windowController setDocumentFilename:@""];
624 } else if (AddMenuMsgID == msgid) {
625 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
626 [self addMenuWithDescriptor:[attrs objectForKey:@"descriptor"]
627 atIndex:[[attrs objectForKey:@"index"] intValue]];
628 } else if (AddMenuItemMsgID == msgid) {
629 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
630 [self addMenuItemWithDescriptor:[attrs objectForKey:@"descriptor"]
631 atIndex:[[attrs objectForKey:@"index"] intValue]
632 tip:[attrs objectForKey:@"tip"]
633 icon:[attrs objectForKey:@"icon"]
634 keyEquivalent:[attrs objectForKey:@"keyEquivalent"]
635 modifierMask:[[attrs objectForKey:@"modifierMask"] intValue]
636 action:[attrs objectForKey:@"action"]
637 isAlternate:[[attrs objectForKey:@"isAlternate"] boolValue]];
638 } else if (RemoveMenuItemMsgID == msgid) {
639 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
640 [self removeMenuItemWithDescriptor:[attrs objectForKey:@"descriptor"]];
641 } else if (EnableMenuItemMsgID == msgid) {
642 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
643 [self enableMenuItemWithDescriptor:[attrs objectForKey:@"descriptor"]
644 state:[[attrs objectForKey:@"enable"] boolValue]];
645 } else if (ShowToolbarMsgID == msgid) {
646 const void *bytes = [data bytes];
647 int enable = *((int*)bytes); bytes += sizeof(int);
648 int flags = *((int*)bytes); bytes += sizeof(int);
650 int mode = NSToolbarDisplayModeDefault;
651 if (flags & ToolbarLabelFlag) {
652 mode = flags & ToolbarIconFlag ? NSToolbarDisplayModeIconAndLabel
653 : NSToolbarDisplayModeLabelOnly;
654 } else if (flags & ToolbarIconFlag) {
655 mode = NSToolbarDisplayModeIconOnly;
658 int size = flags & ToolbarSizeRegularFlag ? NSToolbarSizeModeRegular
659 : NSToolbarSizeModeSmall;
661 [windowController showToolbar:enable size:size mode:mode];
662 } else if (CreateScrollbarMsgID == msgid) {
663 const void *bytes = [data bytes];
664 int32_t ident = *((int32_t*)bytes); bytes += sizeof(int32_t);
665 int type = *((int*)bytes); bytes += sizeof(int);
667 [windowController createScrollbarWithIdentifier:ident type:type];
668 } else if (DestroyScrollbarMsgID == msgid) {
669 const void *bytes = [data bytes];
670 int32_t ident = *((int32_t*)bytes); bytes += sizeof(int32_t);
672 [windowController destroyScrollbarWithIdentifier:ident];
673 } else if (ShowScrollbarMsgID == msgid) {
674 const void *bytes = [data bytes];
675 int32_t ident = *((int32_t*)bytes); bytes += sizeof(int32_t);
676 int visible = *((int*)bytes); bytes += sizeof(int);
678 [windowController showScrollbarWithIdentifier:ident state:visible];
679 } else if (SetScrollbarPositionMsgID == msgid) {
680 const void *bytes = [data bytes];
681 int32_t ident = *((int32_t*)bytes); bytes += sizeof(int32_t);
682 int pos = *((int*)bytes); bytes += sizeof(int);
683 int len = *((int*)bytes); bytes += sizeof(int);
685 [windowController setScrollbarPosition:pos length:len
687 } else if (SetScrollbarThumbMsgID == msgid) {
688 const void *bytes = [data bytes];
689 int32_t ident = *((int32_t*)bytes); bytes += sizeof(int32_t);
690 float val = *((float*)bytes); bytes += sizeof(float);
691 float prop = *((float*)bytes); bytes += sizeof(float);
693 [windowController setScrollbarThumbValue:val proportion:prop
695 } else if (SetFontMsgID == msgid) {
696 const void *bytes = [data bytes];
697 float size = *((float*)bytes); bytes += sizeof(float);
698 int len = *((int*)bytes); bytes += sizeof(int);
699 NSString *name = [[NSString alloc]
700 initWithBytes:(void*)bytes length:len
701 encoding:NSUTF8StringEncoding];
702 NSFont *font = [NSFont fontWithName:name size:size];
704 // This should only happen if the default font was not loaded in
705 // which case we fall back on using the Cocoa default fixed width
707 font = [NSFont userFixedPitchFontOfSize:size];
710 [windowController setFont:font];
712 } else if (SetWideFontMsgID == msgid) {
713 const void *bytes = [data bytes];
714 float size = *((float*)bytes); bytes += sizeof(float);
715 int len = *((int*)bytes); bytes += sizeof(int);
717 NSString *name = [[NSString alloc]
718 initWithBytes:(void*)bytes length:len
719 encoding:NSUTF8StringEncoding];
720 NSFont *font = [NSFont fontWithName:name size:size];
721 [windowController setWideFont:font];
725 [windowController setWideFont:nil];
727 } else if (SetDefaultColorsMsgID == msgid) {
728 const void *bytes = [data bytes];
729 unsigned bg = *((unsigned*)bytes); bytes += sizeof(unsigned);
730 unsigned fg = *((unsigned*)bytes); bytes += sizeof(unsigned);
731 NSColor *back = [NSColor colorWithArgbInt:bg];
732 NSColor *fore = [NSColor colorWithRgbInt:fg];
734 [windowController setDefaultColorsBackground:back foreground:fore];
735 } else if (ExecuteActionMsgID == msgid) {
736 const void *bytes = [data bytes];
737 int len = *((int*)bytes); bytes += sizeof(int);
738 NSString *actionName = [[NSString alloc]
739 initWithBytes:(void*)bytes length:len
740 encoding:NSUTF8StringEncoding];
742 SEL sel = NSSelectorFromString(actionName);
743 [NSApp sendAction:sel to:nil from:self];
745 [actionName release];
746 } else if (ShowPopupMenuMsgID == msgid) {
747 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
749 // The popup menu enters a modal loop so delay this call so that we
750 // don't block inside processInputQueue:.
751 [self performSelector:@selector(popupMenuWithAttributes:)
754 } else if (SetMouseShapeMsgID == msgid) {
755 const void *bytes = [data bytes];
756 int shape = *((int*)bytes); bytes += sizeof(int);
758 [windowController setMouseShape:shape];
759 } else if (AdjustLinespaceMsgID == msgid) {
760 const void *bytes = [data bytes];
761 int linespace = *((int*)bytes); bytes += sizeof(int);
763 [windowController adjustLinespace:linespace];
764 } else if (ActivateMsgID == msgid) {
765 [NSApp activateIgnoringOtherApps:YES];
766 [[windowController window] makeKeyAndOrderFront:self];
767 } else if (SetServerNameMsgID == msgid) {
768 NSString *name = [[NSString alloc] initWithData:data
769 encoding:NSUTF8StringEncoding];
770 [self setServerName:name];
772 } else if (EnterFullscreenMsgID == msgid) {
773 const void *bytes = [data bytes];
774 int fuoptions = *((int*)bytes); bytes += sizeof(int);
775 int bg = *((int*)bytes);
776 NSColor *back = [NSColor colorWithArgbInt:bg];
778 [windowController enterFullscreen:fuoptions backgroundColor:back];
779 } else if (LeaveFullscreenMsgID == msgid) {
780 [windowController leaveFullscreen];
781 } else if (BuffersNotModifiedMsgID == msgid) {
782 [windowController setBuffersModified:NO];
783 } else if (BuffersModifiedMsgID == msgid) {
784 [windowController setBuffersModified:YES];
785 } else if (SetPreEditPositionMsgID == msgid) {
786 const int *dim = (const int*)[data bytes];
787 [[[windowController vimView] textView] setPreEditRow:dim[0]
789 } else if (EnableAntialiasMsgID == msgid) {
790 [[[windowController vimView] textView] setAntialias:YES];
791 } else if (DisableAntialiasMsgID == msgid) {
792 [[[windowController vimView] textView] setAntialias:NO];
793 } else if (SetVimStateMsgID == msgid) {
794 NSDictionary *dict = [NSDictionary dictionaryWithData:data];
797 vimState = [dict retain];
799 } else if (CloseWindowMsgID == msgid) {
800 [self scheduleClose];
801 } else if (SetFullscreenColorMsgID == msgid) {
802 const int *bg = (const int*)[data bytes];
803 NSColor *color = [NSColor colorWithRgbInt:*bg];
805 [windowController setFullscreenBackgroundColor:color];
806 } else if (ShowFindReplaceDialogMsgID == msgid) {
807 NSDictionary *dict = [NSDictionary dictionaryWithData:data];
809 [[MMFindReplaceController sharedInstance]
810 showWithText:[dict objectForKey:@"text"]
811 flags:[[dict objectForKey:@"flags"] intValue]];
813 } else if (ActivateKeyScriptMsgID == msgid) {
814 [[[windowController vimView] textView] activateIm:YES];
815 } else if (DeactivateKeyScriptMsgID == msgid) {
816 [[[windowController vimView] textView] activateIm:NO];
817 } else if (EnableImControlMsgID == msgid) {
818 [[[windowController vimView] textView] setImControl:YES];
819 } else if (DisableImControlMsgID == msgid) {
820 [[[windowController vimView] textView] setImControl:NO];
821 } else if (BrowseForFileMsgID == msgid) {
822 NSDictionary *dict = [NSDictionary dictionaryWithData:data];
824 [self handleBrowseForFile:dict];
825 } else if (ShowDialogMsgID == msgid) {
826 NSDictionary *dict = [NSDictionary dictionaryWithData:data];
828 [self handleShowDialog:dict];
829 } else if (ZoomMsgID == msgid) {
830 const void *bytes = [data bytes];
831 int rows = *((int*)bytes); bytes += sizeof(int);
832 int cols = *((int*)bytes); bytes += sizeof(int);
833 int state = *((int*)bytes); bytes += sizeof(int);
835 [windowController zoomWithRows:rows
838 // IMPORTANT: When adding a new message, make sure to update
839 // isUnsafeMessage() if necessary!
841 ASLogWarn(@"Unknown message received (msgid=%d)", msgid);
845 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
846 context:(void *)context
848 NSString *path = (code == NSOKButton) ? [panel filename] : nil;
849 ASLogDebug(@"Open/save panel path=%@", path);
851 // NOTE! This causes the sheet animation to run its course BEFORE the rest
852 // of this function is executed. If we do not wait for the sheet to
853 // disappear before continuing it can happen that the controller is
854 // released from under us (i.e. we'll crash and burn) because this
855 // animation is otherwise performed in the default run loop mode!
856 [panel orderOut:self];
858 // NOTE! setDialogReturn: is a synchronous call so set a proper timeout to
859 // avoid waiting forever for it to finish. We make this a synchronous call
860 // so that we can be fairly certain that Vim doesn't think the dialog box
861 // is still showing when MacVim has in fact already dismissed it.
862 NSConnection *conn = [backendProxy connectionForProxy];
863 NSTimeInterval oldTimeout = [conn requestTimeout];
864 [conn setRequestTimeout:MMSetDialogReturnTimeout];
867 [backendProxy setDialogReturn:path];
869 // Add file to the "Recent Files" menu (this ensures that files that
870 // are opened/saved from a :browse command are added to this menu).
872 [[NSDocumentController sharedDocumentController]
873 noteNewRecentFilePath:path];
875 @catch (NSException *ex) {
876 ASLogDebug(@"Exception: pid=%d id=%d reason=%@", pid, identifier, ex);
879 [conn setRequestTimeout:oldTimeout];
883 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context
887 code = code - NSAlertFirstButtonReturn + 1;
889 if ([alert isKindOfClass:[MMAlert class]] && [alert textField]) {
890 ret = [NSArray arrayWithObjects:[NSNumber numberWithInt:code],
891 [[alert textField] stringValue], nil];
893 ret = [NSArray arrayWithObject:[NSNumber numberWithInt:code]];
896 ASLogDebug(@"Alert return=%@", ret);
898 // NOTE! This causes the sheet animation to run its course BEFORE the rest
899 // of this function is executed. If we do not wait for the sheet to
900 // disappear before continuing it can happen that the controller is
901 // released from under us (i.e. we'll crash and burn) because this
902 // animation is otherwise performed in the default run loop mode!
903 [[alert window] orderOut:self];
906 [backendProxy setDialogReturn:ret];
908 @catch (NSException *ex) {
909 ASLogDebug(@"setDialogReturn: failed: pid=%d id=%d reason=%@",
910 pid, identifier, ex);
914 - (NSMenuItem *)menuItemForDescriptor:(NSArray *)desc
916 if (!(desc && [desc count] > 0)) return nil;
918 NSString *rootName = [desc objectAtIndex:0];
919 NSArray *rootItems = [rootName hasPrefix:@"PopUp"] ? popupMenuItems
920 : [mainMenu itemArray];
922 NSMenuItem *item = nil;
923 int i, count = [rootItems count];
924 for (i = 0; i < count; ++i) {
925 item = [rootItems objectAtIndex:i];
926 if ([[item title] isEqual:rootName])
930 if (i == count) return nil;
932 count = [desc count];
933 for (i = 1; i < count; ++i) {
934 item = [[item submenu] itemWithTitle:[desc objectAtIndex:i]];
935 if (!item) return nil;
941 - (NSMenu *)parentMenuForDescriptor:(NSArray *)desc
943 if (!(desc && [desc count] > 0)) return nil;
945 NSString *rootName = [desc objectAtIndex:0];
946 NSArray *rootItems = [rootName hasPrefix:@"PopUp"] ? popupMenuItems
947 : [mainMenu itemArray];
950 int i, count = [rootItems count];
951 for (i = 0; i < count; ++i) {
952 NSMenuItem *item = [rootItems objectAtIndex:i];
953 if ([[item title] isEqual:rootName]) {
954 menu = [item submenu];
959 if (!menu) return nil;
961 count = [desc count] - 1;
962 for (i = 1; i < count; ++i) {
963 NSMenuItem *item = [menu itemWithTitle:[desc objectAtIndex:i]];
964 menu = [item submenu];
965 if (!menu) return nil;
971 - (NSMenu *)topLevelMenuForTitle:(NSString *)title
973 // Search only the top-level menus.
975 unsigned i, count = [popupMenuItems count];
976 for (i = 0; i < count; ++i) {
977 NSMenuItem *item = [popupMenuItems objectAtIndex:i];
978 if ([title isEqual:[item title]])
979 return [item submenu];
982 count = [mainMenu numberOfItems];
983 for (i = 0; i < count; ++i) {
984 NSMenuItem *item = [mainMenu itemAtIndex:i];
985 if ([title isEqual:[item title]])
986 return [item submenu];
992 - (void)addMenuWithDescriptor:(NSArray *)desc atIndex:(int)idx
994 if (!(desc && [desc count] > 0 && idx >= 0)) return;
996 NSString *rootName = [desc objectAtIndex:0];
997 if ([rootName isEqual:@"ToolBar"]) {
998 // The toolbar only has one menu, we take this as a hint to create a
999 // toolbar, then we return.
1001 // NOTE! Each toolbar must have a unique identifier, else each
1002 // window will have the same toolbar.
1003 NSString *ident = [NSString stringWithFormat:@"%d", identifier];
1004 toolbar = [[NSToolbar alloc] initWithIdentifier:ident];
1006 [toolbar setShowsBaselineSeparator:NO];
1007 [toolbar setDelegate:self];
1008 [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
1009 [toolbar setSizeMode:NSToolbarSizeModeSmall];
1011 [windowController setToolbar:toolbar];
1017 // This is either a main menu item or a popup menu item.
1018 NSString *title = [desc lastObject];
1019 NSMenuItem *item = [[NSMenuItem alloc] init];
1020 NSMenu *menu = [[NSMenu alloc] initWithTitle:title];
1022 [item setTitle:title];
1023 [item setSubmenu:menu];
1025 NSMenu *parent = [self parentMenuForDescriptor:desc];
1026 if (!parent && [rootName hasPrefix:@"PopUp"]) {
1027 if ([popupMenuItems count] <= idx) {
1028 [popupMenuItems addObject:item];
1030 [popupMenuItems insertObject:item atIndex:idx];
1033 // If descriptor has no parent and its not a popup (or toolbar) menu,
1034 // then it must belong to main menu.
1035 if (!parent) parent = mainMenu;
1037 if ([parent numberOfItems] <= idx) {
1038 [parent addItem:item];
1040 [parent insertItem:item atIndex:idx];
1048 - (void)addMenuItemWithDescriptor:(NSArray *)desc
1051 icon:(NSString *)icon
1052 keyEquivalent:(NSString *)keyEquivalent
1053 modifierMask:(int)modifierMask
1054 action:(NSString *)action
1055 isAlternate:(BOOL)isAlternate
1057 if (!(desc && [desc count] > 1 && idx >= 0)) return;
1059 NSString *title = [desc lastObject];
1060 NSString *rootName = [desc objectAtIndex:0];
1062 if ([rootName isEqual:@"ToolBar"]) {
1063 if (toolbar && [desc count] == 2)
1064 [self addToolbarItemWithLabel:title tip:tip icon:icon atIndex:idx];
1068 NSMenu *parent = [self parentMenuForDescriptor:desc];
1070 ASLogWarn(@"Menu item '%@' has no parent",
1071 [desc componentsJoinedByString:@"->"]);
1075 NSMenuItem *item = nil;
1076 if (0 == [title length]
1077 || ([title hasPrefix:@"-"] && [title hasSuffix:@"-"])) {
1078 item = [NSMenuItem separatorItem];
1079 [item setTitle:title];
1081 item = [[[NSMenuItem alloc] init] autorelease];
1082 [item setTitle:title];
1084 // Note: It is possible to set the action to a message that "doesn't
1085 // exist" without problems. We take advantage of this when adding
1086 // "dummy items" e.g. when dealing with the "Recent Files" menu (in
1087 // which case a recentFilesDummy: action is set, although it is never
1089 if ([action length] > 0)
1090 [item setAction:NSSelectorFromString(action)];
1092 [item setAction:@selector(vimMenuItemAction:)];
1093 if ([tip length] > 0) [item setToolTip:tip];
1094 if ([keyEquivalent length] > 0) {
1095 [item setKeyEquivalent:keyEquivalent];
1096 [item setKeyEquivalentModifierMask:modifierMask];
1098 [item setAlternate:isAlternate];
1100 // The tag is used to indicate whether Vim thinks a menu item should be
1101 // enabled or disabled. By default Vim thinks menu items are enabled.
1105 if ([parent numberOfItems] <= idx) {
1106 [parent addItem:item];
1108 [parent insertItem:item atIndex:idx];
1112 - (void)removeMenuItemWithDescriptor:(NSArray *)desc
1114 if (!(desc && [desc count] > 0)) return;
1116 NSString *title = [desc lastObject];
1117 NSString *rootName = [desc objectAtIndex:0];
1118 if ([rootName isEqual:@"ToolBar"]) {
1120 // Only remove toolbar items, never actually remove the toolbar
1121 // itself or strange things may happen.
1122 if ([desc count] == 2) {
1123 NSUInteger idx = [toolbar indexOfItemWithItemIdentifier:title];
1124 if (idx != NSNotFound)
1125 [toolbar removeItemAtIndex:idx];
1131 NSMenuItem *item = [self menuItemForDescriptor:desc];
1133 ASLogWarn(@"Failed to remove menu item, descriptor not found: %@",
1134 [desc componentsJoinedByString:@"->"]);
1140 if ([item menu] == [NSApp mainMenu] || ![item menu]) {
1141 // NOTE: To be on the safe side we try to remove the item from
1142 // both arrays (it is ok to call removeObject: even if an array
1143 // does not contain the object to remove).
1144 [popupMenuItems removeObject:item];
1148 [[item menu] removeItem:item];
1153 - (void)enableMenuItemWithDescriptor:(NSArray *)desc state:(BOOL)on
1155 if (!(desc && [desc count] > 0)) return;
1157 NSString *rootName = [desc objectAtIndex:0];
1158 if ([rootName isEqual:@"ToolBar"]) {
1159 if (toolbar && [desc count] == 2) {
1160 NSString *title = [desc lastObject];
1161 [[toolbar itemWithItemIdentifier:title] setEnabled:on];
1164 // Use tag to set whether item is enabled or disabled instead of
1165 // calling setEnabled:. This way the menus can autoenable themselves
1166 // but at the same time Vim can set if a menu is enabled whenever it
1168 [[self menuItemForDescriptor:desc] setTag:on];
1172 - (void)addToolbarItemToDictionaryWithLabel:(NSString *)title
1173 toolTip:(NSString *)tip
1174 icon:(NSString *)icon
1176 // If the item corresponds to a separator then do nothing, since it is
1177 // already defined by Cocoa.
1178 if (!title || [title isEqual:NSToolbarSeparatorItemIdentifier]
1179 || [title isEqual:NSToolbarSpaceItemIdentifier]
1180 || [title isEqual:NSToolbarFlexibleSpaceItemIdentifier])
1183 NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:title];
1184 [item setLabel:title];
1185 [item setToolTip:tip];
1186 [item setAction:@selector(vimToolbarItemAction:)];
1187 [item setAutovalidates:NO];
1189 NSImage *img = [NSImage imageNamed:icon];
1191 img = [[[NSImage alloc] initByReferencingFile:icon] autorelease];
1192 if (!(img && [img isValid]))
1196 ASLogNotice(@"Could not find image with name '%@' to use as toolbar"
1197 " image for identifier '%@';"
1198 " using default toolbar icon '%@' instead.",
1199 icon, title, MMDefaultToolbarImageName);
1201 img = [NSImage imageNamed:MMDefaultToolbarImageName];
1204 [item setImage:img];
1206 [toolbarItemDict setObject:item forKey:title];
1211 - (void)addToolbarItemWithLabel:(NSString *)label
1213 icon:(NSString *)icon
1216 if (!toolbar) return;
1218 // Check for separator items.
1220 label = NSToolbarSeparatorItemIdentifier;
1221 } else if ([label length] >= 2 && [label hasPrefix:@"-"]
1222 && [label hasSuffix:@"-"]) {
1223 // The label begins and ends with '-'; decided which kind of separator
1224 // item it is by looking at the prefix.
1225 if ([label hasPrefix:@"-space"]) {
1226 label = NSToolbarSpaceItemIdentifier;
1227 } else if ([label hasPrefix:@"-flexspace"]) {
1228 label = NSToolbarFlexibleSpaceItemIdentifier;
1230 label = NSToolbarSeparatorItemIdentifier;
1234 [self addToolbarItemToDictionaryWithLabel:label toolTip:tip icon:icon];
1236 int maxIdx = [[toolbar items] count];
1237 if (maxIdx < idx) idx = maxIdx;
1239 [toolbar insertItemWithItemIdentifier:label atIndex:idx];
1242 - (void)popupMenuWithDescriptor:(NSArray *)desc
1243 atRow:(NSNumber *)row
1244 column:(NSNumber *)col
1246 NSMenu *menu = [[self menuItemForDescriptor:desc] submenu];
1249 id textView = [[windowController vimView] textView];
1252 // TODO: Let textView convert (row,col) to NSPoint.
1253 int r = [row intValue];
1254 int c = [col intValue];
1255 NSSize cellSize = [textView cellSize];
1256 pt = NSMakePoint((c+1)*cellSize.width, (r+1)*cellSize.height);
1257 pt = [textView convertPoint:pt toView:nil];
1259 pt = [[windowController window] mouseLocationOutsideOfEventStream];
1262 NSEvent *event = [NSEvent mouseEventWithType:NSRightMouseDown
1266 windowNumber:[[windowController window] windowNumber]
1272 [NSMenu popUpContextMenu:menu withEvent:event forView:textView];
1275 - (void)popupMenuWithAttributes:(NSDictionary *)attrs
1279 [self popupMenuWithDescriptor:[attrs objectForKey:@"descriptor"]
1280 atRow:[attrs objectForKey:@"row"]
1281 column:[attrs objectForKey:@"column"]];
1284 - (void)connectionDidDie:(NSNotification *)notification
1286 ASLogDebug(@"%@", notification);
1287 [self scheduleClose];
1290 - (void)scheduleClose
1292 ASLogDebug(@"pid=%d id=%d", pid, identifier);
1294 // NOTE! This message can arrive at pretty much anytime, e.g. while
1295 // the run loop is the 'event tracking' mode. This means that Cocoa may
1296 // well be in the middle of processing some message while this message is
1297 // received. If we were to remove the vim controller straight away we may
1298 // free objects that Cocoa is currently using (e.g. view objects). The
1299 // following call ensures that the vim controller is not released until the
1300 // run loop is back in the 'default' mode.
1301 // Also, since the app may be multithreaded (e.g. as a result of showing
1302 // the open panel) we have to ensure this call happens on the main thread,
1303 // else there is a race condition that may lead to a crash.
1304 [[MMAppController sharedInstance]
1305 performSelectorOnMainThread:@selector(removeVimController:)
1308 modes:[NSArray arrayWithObject:
1309 NSDefaultRunLoopMode]];
1312 // NSSavePanel delegate
1313 - (void)panel:(id)sender willExpand:(BOOL)expanding
1315 // Show or hide the "show hidden files" button
1317 [sender setAccessoryView:showHiddenFilesView()];
1319 [sender setShowsHiddenFiles:NO];
1320 [sender setAccessoryView:nil];
1324 - (void)handleBrowseForFile:(NSDictionary *)attr
1326 if (!isInitialized) return;
1328 NSString *dir = [attr objectForKey:@"dir"];
1329 BOOL saving = [[attr objectForKey:@"saving"] boolValue];
1332 // 'dir == nil' means: set dir to the pwd of the Vim process, or let
1333 // open dialog decide (depending on the below user default).
1334 BOOL trackPwd = [[NSUserDefaults standardUserDefaults]
1335 boolForKey:MMDialogsTrackPwdKey];
1337 dir = [vimState objectForKey:@"pwd"];
1341 NSSavePanel *panel = [NSSavePanel savePanel];
1343 // The delegate will be notified when the panel is expanded at which
1344 // time we may hide/show the "show hidden files" button (this button is
1345 // always visible for the open panel since it is always expanded).
1346 [panel setDelegate:self];
1347 if ([panel isExpanded])
1348 [panel setAccessoryView:showHiddenFilesView()];
1350 [panel beginSheetForDirectory:dir file:nil
1351 modalForWindow:[windowController window]
1353 didEndSelector:@selector(savePanelDidEnd:code:context:)
1356 NSOpenPanel *panel = [NSOpenPanel openPanel];
1357 [panel setAllowsMultipleSelection:NO];
1358 [panel setAccessoryView:showHiddenFilesView()];
1360 [panel beginSheetForDirectory:dir file:nil types:nil
1361 modalForWindow:[windowController window]
1363 didEndSelector:@selector(savePanelDidEnd:code:context:)
1368 - (void)handleShowDialog:(NSDictionary *)attr
1370 if (!isInitialized) return;
1372 NSArray *buttonTitles = [attr objectForKey:@"buttonTitles"];
1373 if (!(buttonTitles && [buttonTitles count])) return;
1375 int style = [[attr objectForKey:@"alertStyle"] intValue];
1376 NSString *message = [attr objectForKey:@"messageText"];
1377 NSString *text = [attr objectForKey:@"informativeText"];
1378 NSString *textFieldString = [attr objectForKey:@"textFieldString"];
1379 MMAlert *alert = [[MMAlert alloc] init];
1381 // NOTE! This has to be done before setting the informative text.
1382 if (textFieldString)
1383 [alert setTextFieldString:textFieldString];
1385 [alert setAlertStyle:style];
1388 [alert setMessageText:message];
1390 // If no message text is specified 'Alert' is used, which we don't
1391 // want, so set an empty string as message text.
1392 [alert setMessageText:@""];
1396 [alert setInformativeText:text];
1397 } else if (textFieldString) {
1398 // Make sure there is always room for the input text field.
1399 [alert setInformativeText:@""];
1402 unsigned i, count = [buttonTitles count];
1403 for (i = 0; i < count; ++i) {
1404 NSString *title = [buttonTitles objectAtIndex:i];
1405 // NOTE: The title of the button may contain the character '&' to
1406 // indicate that the following letter should be the key equivalent
1407 // associated with the button. Extract this letter and lowercase it.
1408 NSString *keyEquivalent = nil;
1409 NSRange hotkeyRange = [title rangeOfString:@"&"];
1410 if (NSNotFound != hotkeyRange.location) {
1411 if ([title length] > NSMaxRange(hotkeyRange)) {
1412 NSRange keyEquivRange = NSMakeRange(hotkeyRange.location+1, 1);
1413 keyEquivalent = [[title substringWithRange:keyEquivRange]
1417 NSMutableString *string = [NSMutableString stringWithString:title];
1418 [string deleteCharactersInRange:hotkeyRange];
1422 [alert addButtonWithTitle:title];
1424 // Set key equivalent for the button, but only if NSAlert hasn't
1425 // already done so. (Check the documentation for
1426 // - [NSAlert addButtonWithTitle:] to see what key equivalents are
1427 // automatically assigned.)
1428 NSButton *btn = [[alert buttons] lastObject];
1429 if ([[btn keyEquivalent] length] == 0 && keyEquivalent) {
1430 [btn setKeyEquivalent:keyEquivalent];
1434 [alert beginSheetModalForWindow:[windowController window]
1436 didEndSelector:@selector(alertDidEnd:code:context:)
1443 @end // MMVimController (Private)
1448 @implementation MMAlert
1454 [textField release]; textField = nil;
1458 - (void)setTextFieldString:(NSString *)textFieldString
1460 [textField release];
1461 textField = [[NSTextField alloc] init];
1462 [textField setStringValue:textFieldString];
1465 - (NSTextField *)textField
1470 - (void)setInformativeText:(NSString *)text
1473 // HACK! Add some space for the text field.
1474 [super setInformativeText:[text stringByAppendingString:@"\n\n\n"]];
1476 [super setInformativeText:text];
1480 - (void)beginSheetModalForWindow:(NSWindow *)window
1481 modalDelegate:(id)delegate
1482 didEndSelector:(SEL)didEndSelector
1483 contextInfo:(void *)contextInfo
1485 [super beginSheetModalForWindow:window
1486 modalDelegate:delegate
1487 didEndSelector:didEndSelector
1488 contextInfo:contextInfo];
1490 // HACK! Place the input text field at the bottom of the informative text
1491 // (which has been made a bit larger by adding newline characters).
1492 NSView *contentView = [[self window] contentView];
1493 NSRect rect = [contentView frame];
1494 rect.origin.y = rect.size.height;
1496 NSArray *subviews = [contentView subviews];
1497 unsigned i, count = [subviews count];
1498 for (i = 0; i < count; ++i) {
1499 NSView *view = [subviews objectAtIndex:i];
1500 if ([view isKindOfClass:[NSTextField class]]
1501 && [view frame].origin.y < rect.origin.y) {
1502 // NOTE: The informative text field is the lowest NSTextField in
1503 // the alert dialog.
1504 rect = [view frame];
1508 rect.size.height = MMAlertTextFieldHeight;
1509 [textField setFrame:rect];
1510 [contentView addSubview:textField];
1511 [textField becomeFirstResponder];
1520 isUnsafeMessage(int msgid)
1522 // Messages that may release Cocoa objects must be added to this list. For
1523 // example, UpdateTabBarMsgID may delete NSTabViewItem objects so it goes
1525 static int unsafeMessages[] = { // REASON MESSAGE IS ON THIS LIST:
1526 //OpenWindowMsgID, // Changes lots of state
1527 UpdateTabBarMsgID, // May delete NSTabViewItem
1528 RemoveMenuItemMsgID, // Deletes NSMenuItem
1529 DestroyScrollbarMsgID, // Deletes NSScroller
1530 ExecuteActionMsgID, // Impossible to predict
1531 ShowPopupMenuMsgID, // Enters modal loop
1533 EnterFullscreenMsgID, // Modifies delegate of window controller
1534 LeaveFullscreenMsgID, // Modifies delegate of window controller
1535 CloseWindowMsgID, // See note below
1536 BrowseForFileMsgID, // Enters modal loop
1537 ShowDialogMsgID, // Enters modal loop
1540 // NOTE about CloseWindowMsgID: If this arrives at the same time as say
1541 // ExecuteActionMsgID, then the "execute" message will be lost due to it
1542 // being queued and handled after the "close" message has caused the
1543 // controller to cleanup...UNLESS we add CloseWindowMsgID to the list of
1544 // unsafe messages. This is the _only_ reason it is on this list (since
1545 // all that happens in response to it is that we schedule another message
1546 // for later handling).
1548 int i, count = sizeof(unsafeMessages)/sizeof(unsafeMessages[0]);
1549 for (i = 0; i < count; ++i)
1550 if (msgid == unsafeMessages[i])