1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
3 * VIM - Vi IMproved by Bram Moolenaar
4 * MacVim GUI port by Bjorn Winckler
6 * Do ":help uganda" in Vim to read copying and usage conditions.
7 * Do ":help credits" in Vim to see a list of people who contributed.
8 * See README.txt for an overview of the Vim source code.
13 * Coordinates input/output to/from backend. Each MMBackend communicates
14 * directly with a MMVimController.
16 * MMVimController does not deal with visual presentation. Essentially it
17 * should be able to run with no window present.
19 * Output from the backend is received in processCommandQueue:. Input is sent
20 * to the backend via sendMessage:data: or addVimInput:. The latter allows
21 * execution of arbitrary strings in the Vim process, much like the Vim script
22 * function remote_send() does. The messages that may be passed between
23 * frontend and backend are defined in an enum in MacVim.h.
26 #import "MMAppController.h"
27 #import "MMAtsuiTextView.h"
28 #import "MMFindReplaceController.h"
29 #import "MMTextView.h"
30 #import "MMVimController.h"
32 #import "MMWindowController.h"
33 #import "Miscellaneous.h"
35 #ifdef MM_ENABLE_PLUGINS
36 #import "MMPlugInManager.h"
39 static NSString *MMDefaultToolbarImageName = @"Attention";
40 static int MMAlertTextFieldHeight = 22;
42 // NOTE: By default a message sent to the backend will be dropped if it cannot
43 // be delivered instantly; otherwise there is a possibility that MacVim will
44 // 'beachball' while waiting to deliver DO messages to an unresponsive Vim
45 // process. This means that you cannot rely on any message sent with
46 // sendMessage: to actually reach Vim.
47 static NSTimeInterval MMBackendProxyRequestTimeout = 0;
49 // Timeout used for setDialogReturn:.
50 static NSTimeInterval MMSetDialogReturnTimeout = 1.0;
52 // Maximum number of items in the receiveQueue. (It is hard to predict what
53 // consequences changing this number will have.)
54 static int MMReceiveQueueCap = 100;
56 static BOOL isUnsafeMessage(int msgid);
58 static unsigned identifierCounter = 1;
61 @interface MMAlert : NSAlert {
62 NSTextField *textField;
64 - (void)setTextFieldString:(NSString *)textFieldString;
65 - (NSTextField *)textField;
69 @interface MMVimController (Private)
70 - (void)doProcessCommandQueue:(NSArray *)queue;
71 - (void)handleMessage:(int)msgid data:(NSData *)data;
72 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
73 context:(void *)context;
74 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context;
75 - (NSMenuItem *)menuItemForDescriptor:(NSArray *)desc;
76 - (NSMenu *)parentMenuForDescriptor:(NSArray *)desc;
77 - (NSMenu *)topLevelMenuForTitle:(NSString *)title;
78 - (void)addMenuWithDescriptor:(NSArray *)desc atIndex:(int)index;
79 - (void)addMenuItemWithDescriptor:(NSArray *)desc
83 keyEquivalent:(NSString *)keyEquivalent
84 modifierMask:(int)modifierMask
85 action:(NSString *)action
86 isAlternate:(BOOL)isAlternate;
87 - (void)removeMenuItemWithDescriptor:(NSArray *)desc;
88 - (void)enableMenuItemWithDescriptor:(NSArray *)desc state:(BOOL)on;
89 - (void)addToolbarItemToDictionaryWithLabel:(NSString *)title
90 toolTip:(NSString *)tip icon:(NSString *)icon;
91 - (void)addToolbarItemWithLabel:(NSString *)label
92 tip:(NSString *)tip icon:(NSString *)icon
94 - (void)popupMenuWithDescriptor:(NSArray *)desc
96 column:(NSNumber *)col;
97 - (void)popupMenuWithAttributes:(NSDictionary *)attrs;
98 - (void)connectionDidDie:(NSNotification *)notification;
99 - (void)scheduleClose;
100 - (void)handleBrowseForFile:(NSDictionary *)attr;
101 - (void)handleShowDialog:(NSDictionary *)attr;
107 @implementation MMVimController
109 - (id)initWithBackend:(id)backend pid:(int)processIdentifier
111 if (!(self = [super init]))
114 identifier = identifierCounter++;
116 [[MMWindowController alloc] initWithVimController:self];
117 backendProxy = [backend retain];
118 sendQueue = [NSMutableArray new];
119 receiveQueue = [NSMutableArray new];
120 popupMenuItems = [[NSMutableArray alloc] init];
121 toolbarItemDict = [[NSMutableDictionary alloc] init];
122 pid = processIdentifier;
123 creationDate = [[NSDate alloc] init];
125 NSConnection *connection = [backendProxy connectionForProxy];
127 // TODO: Check that this will not set the timeout for the root proxy
128 // (in MMAppController).
129 [connection setRequestTimeout:MMBackendProxyRequestTimeout];
131 [[NSNotificationCenter defaultCenter] addObserver:self
132 selector:@selector(connectionDidDie:)
133 name:NSConnectionDidDieNotification object:connection];
135 // Set up a main menu with only a "MacVim" menu (copied from a template
136 // which itself is set up in MainMenu.nib). The main menu is populated
138 mainMenu = [[NSMenu alloc] initWithTitle:@"MainMenu"];
139 NSMenuItem *appMenuItem = [[MMAppController sharedInstance]
140 appMenuItemTemplate];
141 appMenuItem = [[appMenuItem copy] autorelease];
143 // Note: If the title of the application menu is anything but what
144 // CFBundleName says then the application menu will not be typeset in
145 // boldface for some reason. (It should already be set when we copy
146 // from the default main menu, but this is not the case for some
148 NSString *appName = [[NSBundle mainBundle]
149 objectForInfoDictionaryKey:@"CFBundleName"];
150 [appMenuItem setTitle:appName];
152 [mainMenu addItem:appMenuItem];
154 #ifdef MM_ENABLE_PLUGINS
155 instanceMediator = [[MMPlugInInstanceMediator alloc]
156 initWithVimController:self];
170 #ifdef MM_ENABLE_PLUGINS
171 [instanceMediator release]; instanceMediator = nil;
174 [serverName release]; serverName = nil;
175 [backendProxy release]; backendProxy = nil;
176 [sendQueue release]; sendQueue = nil;
177 [receiveQueue release]; receiveQueue = nil;
179 [toolbarItemDict release]; toolbarItemDict = nil;
180 [toolbar release]; toolbar = nil;
181 [popupMenuItems release]; popupMenuItems = nil;
182 [windowController release]; windowController = nil;
184 [vimState release]; vimState = nil;
185 [mainMenu release]; mainMenu = nil;
186 [creationDate release]; creationDate = nil;
191 - (unsigned)identifier
196 - (MMWindowController *)windowController
198 return windowController;
201 #ifdef MM_ENABLE_PLUGINS
202 - (MMPlugInInstanceMediator *)instanceMediator
204 return instanceMediator;
208 - (NSDictionary *)vimState
213 - (id)objectForVimStateKey:(NSString *)key
215 return [vimState objectForKey:key];
228 - (void)setIsPreloading:(BOOL)yn
233 - (NSDate *)creationDate
238 - (void)setServerName:(NSString *)name
240 if (name != serverName) {
241 [serverName release];
242 serverName = [name copy];
246 - (NSString *)serverName
256 - (void)dropFiles:(NSArray *)filenames forceOpen:(BOOL)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 NSString *fnEsc = [filename stringByEscapingSpecialFilenameCharacters];
281 NSString *input = [NSString stringWithFormat:@"<C-\\><C-N>:silent "
283 "edit! %@<CR>", tabIndex + 1, fnEsc];
284 [self addVimInput:input];
287 - (void)filesDraggedToTabBar:(NSArray *)filenames
289 NSUInteger i, count = [filenames count];
290 NSMutableString *input = [NSMutableString stringWithString:@"<C-\\><C-N>"
291 ":silent! tabnext 9999"];
292 for (i = 0; i < count; i++) {
293 NSString *fn = [filenames objectAtIndex:i];
294 NSString *fnEsc = [fn stringByEscapingSpecialFilenameCharacters];
295 [input appendFormat:@"|tabedit %@", fnEsc];
297 [input appendString:@"<CR>"];
298 [self addVimInput:input];
301 - (void)dropString:(NSString *)string
303 int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
305 NSMutableData *data = [NSMutableData data];
307 [data appendBytes:&len length:sizeof(int)];
308 [data appendBytes:[string UTF8String] length:len];
310 [self sendMessage:DropStringMsgID data:data];
314 - (void)passArguments:(NSDictionary *)args
318 [self sendMessage:OpenWithArgumentsMsgID data:[args dictionaryAsData]];
320 // HACK! Fool findUnusedEditor into thinking that this controller is not
321 // unused anymore, in case it is called before the arguments have reached
322 // the Vim process. This should be a "safe" hack since the next time the
323 // Vim process flushes its output queue the state will be updated again (at
324 // which time the "unusedEditor" state will have been properly set).
325 NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:
327 [dict setObject:[NSNumber numberWithBool:NO] forKey:@"unusedEditor"];
329 vimState = [dict copy];
332 - (void)sendMessage:(int)msgid data:(NSData *)data
334 //NSLog(@"sendMessage:%s (isInitialized=%d inProcessCommandQueue=%d)",
335 // MessageStrings[msgid], isInitialized, inProcessCommandQueue);
337 if (!isInitialized) return;
339 if (inProcessCommandQueue) {
340 //NSLog(@"In process command queue; delaying message send.");
341 [sendQueue addObject:[NSNumber numberWithInt:msgid]];
343 [sendQueue addObject:data];
345 [sendQueue addObject:[NSNull null]];
350 [backendProxy processInput:msgid data:data];
352 @catch (NSException *e) {
353 //NSLog(@"%@ %s Exception caught during DO call: %@",
354 // [self className], _cmd, e);
358 - (BOOL)sendMessageNow:(int)msgid data:(NSData *)data
359 timeout:(NSTimeInterval)timeout
361 // Send a message with a timeout. USE WITH EXTREME CAUTION! Sending
362 // messages in rapid succession with a timeout may cause MacVim to beach
363 // ball forever. In almost all circumstances sendMessage:data: should be
366 if (!isInitialized || inProcessCommandQueue)
369 if (timeout < 0) timeout = 0;
372 NSConnection *conn = [backendProxy connectionForProxy];
373 NSTimeInterval oldTimeout = [conn requestTimeout];
375 [conn setRequestTimeout:timeout];
378 [backendProxy processInput:msgid data:data];
380 @catch (NSException *e) {
384 [conn setRequestTimeout:oldTimeout];
390 - (void)addVimInput:(NSString *)string
392 // This is a very general method of adding input to the Vim process. It is
393 // basically the same as calling remote_send() on the process (see
394 // ':h remote_send').
396 NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
397 [self sendMessage:AddInputMsgID data:data];
401 - (NSString *)evaluateVimExpression:(NSString *)expr
403 NSString *eval = nil;
406 eval = [backendProxy evaluateExpression:expr];
408 @catch (NSException *ex) { /* do nothing */ }
413 - (id)evaluateVimExpressionCocoa:(NSString *)expr errorString:(NSString **)errstr
418 eval = [backendProxy evaluateExpressionCocoa:expr
420 } @catch (NSException *ex) {
421 *errstr = [ex reason];
434 if (!isInitialized) return;
437 [toolbar setDelegate:nil];
438 [[NSNotificationCenter defaultCenter] removeObserver:self];
439 //[[backendProxy connectionForProxy] invalidate];
440 //[windowController close];
441 [windowController cleanup];
444 - (void)processInputQueue:(NSArray *)queue
446 if (!isInitialized) return;
448 if (inProcessCommandQueue) {
449 // NOTE! If a synchronous DO call is made during
450 // doProcessCommandQueue: below it may happen that this method is
451 // called a second time while the synchronous message is waiting for a
452 // reply (could also happen if doProcessCommandQueue: enters a modal
453 // loop, see comment below). Since this method cannot be considered
454 // reentrant, we queue the input and return immediately.
456 // If doProcessCommandQueue: enters a modal loop (happens e.g. on
457 // ShowPopupMenuMsgID) then the receiveQueue could grow to become
458 // arbitrarily large because DO calls still get processed. To avoid
459 // this we set a cap on the size of the queue and simply clear it if it
460 // becomes too large. (That is messages will be dropped and hence Vim
461 // and MacVim will at least temporarily be out of sync.)
462 if ([receiveQueue count] >= MMReceiveQueueCap)
463 [receiveQueue removeAllObjects];
465 [receiveQueue addObject:queue];
469 inProcessCommandQueue = YES;
470 [self doProcessCommandQueue:queue];
473 for (i = 0; i < [receiveQueue count]; ++i) {
474 // Note that doProcessCommandQueue: may cause the receiveQueue to grow
475 // or get cleared (due to cap being hit). Make sure to retain the item
476 // to process or it may get released from under us.
477 NSArray *q = [[receiveQueue objectAtIndex:i] retain];
478 [self doProcessCommandQueue:q];
482 // We assume that the remaining calls make no synchronous DO calls. If
483 // that did happen anyway, the command queue could get processed out of
486 // See comment below why this is called here and not later.
487 [windowController processCommandQueueDidFinish];
489 // NOTE: Ensure that no calls are made after this "if" clause that may call
490 // sendMessage::. If this happens anyway, such messages will be put on the
491 // send queue and then the queue will not be flushed until the next time
492 // this method is called.
493 if ([sendQueue count] > 0) {
495 [backendProxy processInputAndData:sendQueue];
497 @catch (NSException *e) {
498 // Connection timed out, just ignore this.
499 //NSLog(@"WARNING! Connection timed out in %s", _cmd);
502 [sendQueue removeAllObjects];
505 [receiveQueue removeAllObjects];
506 inProcessCommandQueue = NO;
509 - (NSToolbarItem *)toolbar:(NSToolbar *)theToolbar
510 itemForItemIdentifier:(NSString *)itemId
511 willBeInsertedIntoToolbar:(BOOL)flag
513 NSToolbarItem *item = [toolbarItemDict objectForKey:itemId];
515 NSLog(@"WARNING: No toolbar item with id '%@'", itemId);
521 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)theToolbar
526 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)theToolbar
531 @end // MMVimController
535 @implementation MMVimController (Private)
537 - (void)doProcessCommandQueue:(NSArray *)queue
539 NSMutableArray *delayQueue = nil;
542 unsigned i, count = [queue count];
544 NSLog(@"WARNING: Uneven number of components (%d) in command "
545 "queue. Skipping...", count);
549 //NSLog(@"======== %s BEGIN ========", _cmd);
550 for (i = 0; i < count; i += 2) {
551 NSData *value = [queue objectAtIndex:i];
552 NSData *data = [queue objectAtIndex:i+1];
554 int msgid = *((int*)[value bytes]);
555 //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
557 BOOL inDefaultMode = [[[NSRunLoop currentRunLoop] currentMode]
558 isEqual:NSDefaultRunLoopMode];
559 if (!inDefaultMode && isUnsafeMessage(msgid)) {
560 // NOTE: Because we may be listening to DO messages in "event
561 // tracking mode" we have to take extra care when doing things
562 // like releasing view items (and other Cocoa objects).
563 // Messages that may be potentially "unsafe" are delayed until
564 // the run loop is back to default mode at which time they are
565 // safe to call again.
566 // A problem with this approach is that it is hard to
567 // classify which messages are unsafe. As a rule of thumb, if
568 // a message may release an object used by the Cocoa framework
569 // (e.g. views) then the message should be considered unsafe.
570 // Delaying messages may have undesired side-effects since it
571 // means that messages may not be processed in the order Vim
572 // sent them, so beware.
574 delayQueue = [NSMutableArray array];
576 //NSLog(@"Adding unsafe message '%s' to delay queue (mode=%@)",
577 // MessageStrings[msgid],
578 // [[NSRunLoop currentRunLoop] currentMode]);
579 [delayQueue addObject:value];
580 [delayQueue addObject:data];
582 [self handleMessage:msgid data:data];
585 //NSLog(@"======== %s END ========", _cmd);
587 @catch (NSException *e) {
588 NSLog(@"Exception caught whilst processing command queue: %@", e);
592 //NSLog(@" Flushing delay queue (%d items)", [delayQueue count]/2);
593 [self performSelectorOnMainThread:@selector(processCommandQueue:)
594 withObject:delayQueue
596 modes:[NSArray arrayWithObject:
597 NSDefaultRunLoopMode]];
601 - (void)handleMessage:(int)msgid data:(NSData *)data
603 //if (msgid != AddMenuMsgID && msgid != AddMenuItemMsgID &&
604 // msgid != EnableMenuItemMsgID)
605 // NSLog(@"%@ %s%s", [self className], _cmd, MessageStrings[msgid]);
607 if (OpenWindowMsgID == msgid) {
608 [windowController openWindow];
610 // If the vim controller is preloading then the window will be
611 // displayed when it is taken off the preload cache.
613 [windowController showWindow];
614 } else if (BatchDrawMsgID == msgid) {
615 [[[windowController vimView] textView] performBatchDrawWithData:data];
616 } else if (SelectTabMsgID == msgid) {
617 #if 0 // NOTE: Tab selection is done inside updateTabsWithData:.
618 const void *bytes = [data bytes];
619 int idx = *((int*)bytes);
620 //NSLog(@"Selecting tab with index %d", idx);
621 [windowController selectTabWithIndex:idx];
623 } else if (UpdateTabBarMsgID == msgid) {
624 [windowController updateTabsWithData:data];
625 } else if (ShowTabBarMsgID == msgid) {
626 [windowController showTabBar:YES];
627 } else if (HideTabBarMsgID == msgid) {
628 [windowController showTabBar:NO];
629 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid ||
630 SetTextDimensionsReplyMsgID == msgid) {
631 const void *bytes = [data bytes];
632 int rows = *((int*)bytes); bytes += sizeof(int);
633 int cols = *((int*)bytes); bytes += sizeof(int);
635 [windowController setTextDimensionsWithRows:rows
637 isLive:(LiveResizeMsgID==msgid)
638 isReply:(SetTextDimensionsReplyMsgID==msgid)];
639 } else if (SetWindowTitleMsgID == msgid) {
640 const void *bytes = [data bytes];
641 int len = *((int*)bytes); bytes += sizeof(int);
643 NSString *string = [[NSString alloc] initWithBytes:(void*)bytes
644 length:len encoding:NSUTF8StringEncoding];
646 // While in live resize the window title displays the dimensions of the
647 // window so don't clobber this with a spurious "set title" message
649 if (![[windowController vimView] inLiveResize])
650 [windowController setTitle:string];
653 } else if (SetDocumentFilenameMsgID == msgid) {
654 const void *bytes = [data bytes];
655 int len = *((int*)bytes); bytes += sizeof(int);
658 NSString *filename = [[NSString alloc] initWithBytes:(void*)bytes
659 length:len encoding:NSUTF8StringEncoding];
661 [windowController setDocumentFilename:filename];
665 [windowController setDocumentFilename:@""];
667 } else if (AddMenuMsgID == msgid) {
668 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
669 [self addMenuWithDescriptor:[attrs objectForKey:@"descriptor"]
670 atIndex:[[attrs objectForKey:@"index"] intValue]];
671 } else if (AddMenuItemMsgID == msgid) {
672 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
673 [self addMenuItemWithDescriptor:[attrs objectForKey:@"descriptor"]
674 atIndex:[[attrs objectForKey:@"index"] intValue]
675 tip:[attrs objectForKey:@"tip"]
676 icon:[attrs objectForKey:@"icon"]
677 keyEquivalent:[attrs objectForKey:@"keyEquivalent"]
678 modifierMask:[[attrs objectForKey:@"modifierMask"] intValue]
679 action:[attrs objectForKey:@"action"]
680 isAlternate:[[attrs objectForKey:@"isAlternate"] boolValue]];
681 } else if (RemoveMenuItemMsgID == msgid) {
682 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
683 [self removeMenuItemWithDescriptor:[attrs objectForKey:@"descriptor"]];
684 } else if (EnableMenuItemMsgID == msgid) {
685 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
686 [self enableMenuItemWithDescriptor:[attrs objectForKey:@"descriptor"]
687 state:[[attrs objectForKey:@"enable"] boolValue]];
688 } else if (ShowToolbarMsgID == msgid) {
689 const void *bytes = [data bytes];
690 int enable = *((int*)bytes); bytes += sizeof(int);
691 int flags = *((int*)bytes); bytes += sizeof(int);
693 int mode = NSToolbarDisplayModeDefault;
694 if (flags & ToolbarLabelFlag) {
695 mode = flags & ToolbarIconFlag ? NSToolbarDisplayModeIconAndLabel
696 : NSToolbarDisplayModeLabelOnly;
697 } else if (flags & ToolbarIconFlag) {
698 mode = NSToolbarDisplayModeIconOnly;
701 int size = flags & ToolbarSizeRegularFlag ? NSToolbarSizeModeRegular
702 : NSToolbarSizeModeSmall;
704 [windowController showToolbar:enable size:size mode:mode];
705 } else if (CreateScrollbarMsgID == msgid) {
706 const void *bytes = [data bytes];
707 long ident = *((long*)bytes); bytes += sizeof(long);
708 int type = *((int*)bytes); bytes += sizeof(int);
710 [windowController createScrollbarWithIdentifier:ident type:type];
711 } else if (DestroyScrollbarMsgID == msgid) {
712 const void *bytes = [data bytes];
713 long ident = *((long*)bytes); bytes += sizeof(long);
715 [windowController destroyScrollbarWithIdentifier:ident];
716 } else if (ShowScrollbarMsgID == msgid) {
717 const void *bytes = [data bytes];
718 long ident = *((long*)bytes); bytes += sizeof(long);
719 int visible = *((int*)bytes); bytes += sizeof(int);
721 [windowController showScrollbarWithIdentifier:ident state:visible];
722 } else if (SetScrollbarPositionMsgID == msgid) {
723 const void *bytes = [data bytes];
724 long ident = *((long*)bytes); bytes += sizeof(long);
725 int pos = *((int*)bytes); bytes += sizeof(int);
726 int len = *((int*)bytes); bytes += sizeof(int);
728 [windowController setScrollbarPosition:pos length:len
730 } else if (SetScrollbarThumbMsgID == msgid) {
731 const void *bytes = [data bytes];
732 long ident = *((long*)bytes); bytes += sizeof(long);
733 float val = *((float*)bytes); bytes += sizeof(float);
734 float prop = *((float*)bytes); bytes += sizeof(float);
736 [windowController setScrollbarThumbValue:val proportion:prop
738 } else if (SetFontMsgID == msgid) {
739 const void *bytes = [data bytes];
740 float size = *((float*)bytes); bytes += sizeof(float);
741 int len = *((int*)bytes); bytes += sizeof(int);
742 NSString *name = [[NSString alloc]
743 initWithBytes:(void*)bytes length:len
744 encoding:NSUTF8StringEncoding];
745 NSFont *font = [NSFont fontWithName:name size:size];
747 // This should only happen if the default font was not loaded in
748 // which case we fall back on using the Cocoa default fixed width
750 font = [NSFont userFixedPitchFontOfSize:size];
753 [windowController setFont:font];
755 } else if (SetWideFontMsgID == msgid) {
756 const void *bytes = [data bytes];
757 float size = *((float*)bytes); bytes += sizeof(float);
758 int len = *((int*)bytes); bytes += sizeof(int);
760 NSString *name = [[NSString alloc]
761 initWithBytes:(void*)bytes length:len
762 encoding:NSUTF8StringEncoding];
763 NSFont *font = [NSFont fontWithName:name size:size];
764 [windowController setWideFont:font];
768 [windowController setWideFont:nil];
770 } else if (SetDefaultColorsMsgID == msgid) {
771 const void *bytes = [data bytes];
772 unsigned bg = *((unsigned*)bytes); bytes += sizeof(unsigned);
773 unsigned fg = *((unsigned*)bytes); bytes += sizeof(unsigned);
774 NSColor *back = [NSColor colorWithArgbInt:bg];
775 NSColor *fore = [NSColor colorWithRgbInt:fg];
777 [windowController setDefaultColorsBackground:back foreground:fore];
778 } else if (ExecuteActionMsgID == msgid) {
779 const void *bytes = [data bytes];
780 int len = *((int*)bytes); bytes += sizeof(int);
781 NSString *actionName = [[NSString alloc]
782 initWithBytes:(void*)bytes length:len
783 encoding:NSUTF8StringEncoding];
785 SEL sel = NSSelectorFromString(actionName);
786 [NSApp sendAction:sel to:nil from:self];
788 [actionName release];
789 } else if (ShowPopupMenuMsgID == msgid) {
790 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
792 // The popup menu enters a modal loop so delay this call so that we
793 // don't block inside processCommandQueue:.
794 [self performSelectorOnMainThread:@selector(popupMenuWithAttributes:)
797 modes:[NSArray arrayWithObject:
798 NSDefaultRunLoopMode]];
799 } else if (SetMouseShapeMsgID == msgid) {
800 const void *bytes = [data bytes];
801 int shape = *((int*)bytes); bytes += sizeof(int);
803 [windowController setMouseShape:shape];
804 } else if (AdjustLinespaceMsgID == msgid) {
805 const void *bytes = [data bytes];
806 int linespace = *((int*)bytes); bytes += sizeof(int);
808 [windowController adjustLinespace:linespace];
809 } else if (ActivateMsgID == msgid) {
810 //NSLog(@"ActivateMsgID");
811 [NSApp activateIgnoringOtherApps:YES];
812 [[windowController window] makeKeyAndOrderFront:self];
813 } else if (SetServerNameMsgID == msgid) {
814 NSString *name = [[NSString alloc] initWithData:data
815 encoding:NSUTF8StringEncoding];
816 [self setServerName:name];
818 } else if (EnterFullscreenMsgID == msgid) {
819 const void *bytes = [data bytes];
820 int fuoptions = *((int*)bytes); bytes += sizeof(int);
821 int bg = *((int*)bytes);
822 NSColor *back = [NSColor colorWithArgbInt:bg];
824 [windowController enterFullscreen:fuoptions backgroundColor:back];
825 } else if (LeaveFullscreenMsgID == msgid) {
826 [windowController leaveFullscreen];
827 } else if (BuffersNotModifiedMsgID == msgid) {
828 [windowController setBuffersModified:NO];
829 } else if (BuffersModifiedMsgID == msgid) {
830 [windowController setBuffersModified:YES];
831 } else if (SetPreEditPositionMsgID == msgid) {
832 const int *dim = (const int*)[data bytes];
833 [[[windowController vimView] textView] setPreEditRow:dim[0]
835 } else if (EnableAntialiasMsgID == msgid) {
836 [[[windowController vimView] textView] setAntialias:YES];
837 } else if (DisableAntialiasMsgID == msgid) {
838 [[[windowController vimView] textView] setAntialias:NO];
839 } else if (SetVimStateMsgID == msgid) {
840 NSDictionary *dict = [NSDictionary dictionaryWithData:data];
843 vimState = [dict retain];
845 } else if (CloseWindowMsgID == msgid) {
846 [self scheduleClose];
847 } else if (SetFullscreenColorMsgID == msgid) {
848 const int *bg = (const int*)[data bytes];
849 NSColor *color = [NSColor colorWithRgbInt:*bg];
851 [windowController setFullscreenBackgroundColor:color];
852 } else if (ShowFindReplaceDialogMsgID == msgid) {
853 NSDictionary *dict = [NSDictionary dictionaryWithData:data];
855 [[MMFindReplaceController sharedInstance]
856 showWithText:[dict objectForKey:@"text"]
857 flags:[[dict objectForKey:@"flags"] intValue]];
859 } else if (ActivateKeyScriptID == msgid) {
860 KeyScript(smKeySysScript);
861 } else if (DeactivateKeyScriptID == msgid) {
862 KeyScript(smKeyRoman);
863 } else if (BrowseForFileMsgID == msgid) {
864 NSDictionary *dict = [NSDictionary dictionaryWithData:data];
866 [self handleBrowseForFile:dict];
867 } else if (ShowDialogMsgID == msgid) {
868 NSDictionary *dict = [NSDictionary dictionaryWithData:data];
870 [self handleShowDialog:dict];
871 // IMPORTANT: When adding a new message, make sure to update
872 // isUnsafeMessage() if necessary!
874 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
878 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
879 context:(void *)context
881 NSString *path = (code == NSOKButton) ? [panel filename] : nil;
883 // NOTE! setDialogReturn: is a synchronous call so set a proper timeout to
884 // avoid waiting forever for it to finish. We make this a synchronous call
885 // so that we can be fairly certain that Vim doesn't think the dialog box
886 // is still showing when MacVim has in fact already dismissed it.
887 NSConnection *conn = [backendProxy connectionForProxy];
888 NSTimeInterval oldTimeout = [conn requestTimeout];
889 [conn setRequestTimeout:MMSetDialogReturnTimeout];
892 [backendProxy setDialogReturn:path];
894 // Add file to the "Recent Files" menu (this ensures that files that
895 // are opened/saved from a :browse command are added to this menu).
897 [[NSDocumentController sharedDocumentController]
898 noteNewRecentFilePath:path];
900 @catch (NSException *e) {
901 NSLog(@"Exception caught in %s %@", _cmd, e);
904 [conn setRequestTimeout:oldTimeout];
908 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context
912 code = code - NSAlertFirstButtonReturn + 1;
914 if ([alert isKindOfClass:[MMAlert class]] && [alert textField]) {
915 ret = [NSArray arrayWithObjects:[NSNumber numberWithInt:code],
916 [[alert textField] stringValue], nil];
918 ret = [NSArray arrayWithObject:[NSNumber numberWithInt:code]];
922 [backendProxy setDialogReturn:ret];
924 @catch (NSException *e) {
925 NSLog(@"Exception caught in %s %@", _cmd, e);
929 - (NSMenuItem *)menuItemForDescriptor:(NSArray *)desc
931 if (!(desc && [desc count] > 0)) return nil;
933 NSString *rootName = [desc objectAtIndex:0];
934 NSArray *rootItems = [rootName hasPrefix:@"PopUp"] ? popupMenuItems
935 : [mainMenu itemArray];
937 NSMenuItem *item = nil;
938 int i, count = [rootItems count];
939 for (i = 0; i < count; ++i) {
940 item = [rootItems objectAtIndex:i];
941 if ([[item title] isEqual:rootName])
945 if (i == count) return nil;
947 count = [desc count];
948 for (i = 1; i < count; ++i) {
949 item = [[item submenu] itemWithTitle:[desc objectAtIndex:i]];
950 if (!item) return nil;
956 - (NSMenu *)parentMenuForDescriptor:(NSArray *)desc
958 if (!(desc && [desc count] > 0)) return nil;
960 NSString *rootName = [desc objectAtIndex:0];
961 NSArray *rootItems = [rootName hasPrefix:@"PopUp"] ? popupMenuItems
962 : [mainMenu itemArray];
965 int i, count = [rootItems count];
966 for (i = 0; i < count; ++i) {
967 NSMenuItem *item = [rootItems objectAtIndex:i];
968 if ([[item title] isEqual:rootName]) {
969 menu = [item submenu];
974 if (!menu) return nil;
976 count = [desc count] - 1;
977 for (i = 1; i < count; ++i) {
978 NSMenuItem *item = [menu itemWithTitle:[desc objectAtIndex:i]];
979 menu = [item submenu];
980 if (!menu) return nil;
986 - (NSMenu *)topLevelMenuForTitle:(NSString *)title
988 // Search only the top-level menus.
990 unsigned i, count = [popupMenuItems count];
991 for (i = 0; i < count; ++i) {
992 NSMenuItem *item = [popupMenuItems objectAtIndex:i];
993 if ([title isEqual:[item title]])
994 return [item submenu];
997 count = [mainMenu numberOfItems];
998 for (i = 0; i < count; ++i) {
999 NSMenuItem *item = [mainMenu itemAtIndex:i];
1000 if ([title isEqual:[item title]])
1001 return [item submenu];
1007 - (void)addMenuWithDescriptor:(NSArray *)desc atIndex:(int)idx
1009 if (!(desc && [desc count] > 0 && idx >= 0)) return;
1011 NSString *rootName = [desc objectAtIndex:0];
1012 if ([rootName isEqual:@"ToolBar"]) {
1013 // The toolbar only has one menu, we take this as a hint to create a
1014 // toolbar, then we return.
1016 // NOTE! Each toolbar must have a unique identifier, else each
1017 // window will have the same toolbar.
1018 NSString *ident = [NSString stringWithFormat:@"%d", (int)self];
1019 toolbar = [[NSToolbar alloc] initWithIdentifier:ident];
1021 [toolbar setShowsBaselineSeparator:NO];
1022 [toolbar setDelegate:self];
1023 [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
1024 [toolbar setSizeMode:NSToolbarSizeModeSmall];
1026 [windowController setToolbar:toolbar];
1032 // This is either a main menu item or a popup menu item.
1033 NSString *title = [desc lastObject];
1034 NSMenuItem *item = [[NSMenuItem alloc] init];
1035 NSMenu *menu = [[NSMenu alloc] initWithTitle:title];
1037 [item setTitle:title];
1038 [item setSubmenu:menu];
1040 NSMenu *parent = [self parentMenuForDescriptor:desc];
1041 if (!parent && [rootName hasPrefix:@"PopUp"]) {
1042 if ([popupMenuItems count] <= idx) {
1043 [popupMenuItems addObject:item];
1045 [popupMenuItems insertObject:item atIndex:idx];
1048 // If descriptor has no parent and its not a popup (or toolbar) menu,
1049 // then it must belong to main menu.
1050 if (!parent) parent = mainMenu;
1052 if ([parent numberOfItems] <= idx) {
1053 [parent addItem:item];
1055 [parent insertItem:item atIndex:idx];
1063 - (void)addMenuItemWithDescriptor:(NSArray *)desc
1066 icon:(NSString *)icon
1067 keyEquivalent:(NSString *)keyEquivalent
1068 modifierMask:(int)modifierMask
1069 action:(NSString *)action
1070 isAlternate:(BOOL)isAlternate
1072 if (!(desc && [desc count] > 1 && idx >= 0)) return;
1074 NSString *title = [desc lastObject];
1075 NSString *rootName = [desc objectAtIndex:0];
1077 if ([rootName isEqual:@"ToolBar"]) {
1078 if (toolbar && [desc count] == 2)
1079 [self addToolbarItemWithLabel:title tip:tip icon:icon atIndex:idx];
1083 NSMenu *parent = [self parentMenuForDescriptor:desc];
1085 NSLog(@"WARNING: Menu item '%@' has no parent",
1086 [desc componentsJoinedByString:@"->"]);
1090 NSMenuItem *item = nil;
1091 if (0 == [title length]
1092 || ([title hasPrefix:@"-"] && [title hasSuffix:@"-"])) {
1093 item = [NSMenuItem separatorItem];
1094 [item setTitle:title];
1096 item = [[[NSMenuItem alloc] init] autorelease];
1097 [item setTitle:title];
1099 // Note: It is possible to set the action to a message that "doesn't
1100 // exist" without problems. We take advantage of this when adding
1101 // "dummy items" e.g. when dealing with the "Recent Files" menu (in
1102 // which case a recentFilesDummy: action is set, although it is never
1104 if ([action length] > 0)
1105 [item setAction:NSSelectorFromString(action)];
1107 [item setAction:@selector(vimMenuItemAction:)];
1108 if ([tip length] > 0) [item setToolTip:tip];
1109 if ([keyEquivalent length] > 0) {
1110 [item setKeyEquivalent:keyEquivalent];
1111 [item setKeyEquivalentModifierMask:modifierMask];
1113 [item setAlternate:isAlternate];
1115 // The tag is used to indicate whether Vim thinks a menu item should be
1116 // enabled or disabled. By default Vim thinks menu items are enabled.
1120 if ([parent numberOfItems] <= idx) {
1121 [parent addItem:item];
1123 [parent insertItem:item atIndex:idx];
1127 - (void)removeMenuItemWithDescriptor:(NSArray *)desc
1129 if (!(desc && [desc count] > 0)) return;
1131 NSString *title = [desc lastObject];
1132 NSString *rootName = [desc objectAtIndex:0];
1133 if ([rootName isEqual:@"ToolBar"]) {
1135 // Only remove toolbar items, never actually remove the toolbar
1136 // itself or strange things may happen.
1137 if ([desc count] == 2) {
1138 int idx = [toolbar indexOfItemWithItemIdentifier:title];
1139 if (idx != NSNotFound)
1140 [toolbar removeItemAtIndex:idx];
1146 NSMenuItem *item = [self menuItemForDescriptor:desc];
1148 NSLog(@"Failed to remove menu item, descriptor not found: %@",
1149 [desc componentsJoinedByString:@"->"]);
1155 if ([item menu] == [NSApp mainMenu] || ![item menu]) {
1156 // NOTE: To be on the safe side we try to remove the item from
1157 // both arrays (it is ok to call removeObject: even if an array
1158 // does not contain the object to remove).
1159 [popupMenuItems removeObject:item];
1163 [[item menu] removeItem:item];
1168 - (void)enableMenuItemWithDescriptor:(NSArray *)desc state:(BOOL)on
1170 if (!(desc && [desc count] > 0)) return;
1172 /*NSLog(@"%sable item %@", on ? "En" : "Dis",
1173 [desc componentsJoinedByString:@"->"]);*/
1175 NSString *rootName = [desc objectAtIndex:0];
1176 if ([rootName isEqual:@"ToolBar"]) {
1177 if (toolbar && [desc count] == 2) {
1178 NSString *title = [desc lastObject];
1179 [[toolbar itemWithItemIdentifier:title] setEnabled:on];
1182 // Use tag to set whether item is enabled or disabled instead of
1183 // calling setEnabled:. This way the menus can autoenable themselves
1184 // but at the same time Vim can set if a menu is enabled whenever it
1186 [[self menuItemForDescriptor:desc] setTag:on];
1190 - (void)addToolbarItemToDictionaryWithLabel:(NSString *)title
1191 toolTip:(NSString *)tip
1192 icon:(NSString *)icon
1194 // If the item corresponds to a separator then do nothing, since it is
1195 // already defined by Cocoa.
1196 if (!title || [title isEqual:NSToolbarSeparatorItemIdentifier]
1197 || [title isEqual:NSToolbarSpaceItemIdentifier]
1198 || [title isEqual:NSToolbarFlexibleSpaceItemIdentifier])
1201 NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:title];
1202 [item setLabel:title];
1203 [item setToolTip:tip];
1204 [item setAction:@selector(vimToolbarItemAction:)];
1205 [item setAutovalidates:NO];
1207 NSImage *img = [NSImage imageNamed:icon];
1209 img = [[[NSImage alloc] initByReferencingFile:icon] autorelease];
1210 if (!(img && [img isValid]))
1214 NSLog(@"WARNING: Could not find image with name '%@' to use as toolbar"
1215 " image for identifier '%@';"
1216 " using default toolbar icon '%@' instead.",
1217 icon, title, MMDefaultToolbarImageName);
1219 img = [NSImage imageNamed:MMDefaultToolbarImageName];
1222 [item setImage:img];
1224 [toolbarItemDict setObject:item forKey:title];
1229 - (void)addToolbarItemWithLabel:(NSString *)label
1231 icon:(NSString *)icon
1234 if (!toolbar) return;
1236 // Check for separator items.
1238 label = NSToolbarSeparatorItemIdentifier;
1239 } else if ([label length] >= 2 && [label hasPrefix:@"-"]
1240 && [label hasSuffix:@"-"]) {
1241 // The label begins and ends with '-'; decided which kind of separator
1242 // item it is by looking at the prefix.
1243 if ([label hasPrefix:@"-space"]) {
1244 label = NSToolbarSpaceItemIdentifier;
1245 } else if ([label hasPrefix:@"-flexspace"]) {
1246 label = NSToolbarFlexibleSpaceItemIdentifier;
1248 label = NSToolbarSeparatorItemIdentifier;
1252 [self addToolbarItemToDictionaryWithLabel:label toolTip:tip icon:icon];
1254 int maxIdx = [[toolbar items] count];
1255 if (maxIdx < idx) idx = maxIdx;
1257 [toolbar insertItemWithItemIdentifier:label atIndex:idx];
1260 - (void)popupMenuWithDescriptor:(NSArray *)desc
1261 atRow:(NSNumber *)row
1262 column:(NSNumber *)col
1264 NSMenu *menu = [[self menuItemForDescriptor:desc] submenu];
1267 id textView = [[windowController vimView] textView];
1270 // TODO: Let textView convert (row,col) to NSPoint.
1271 int r = [row intValue];
1272 int c = [col intValue];
1273 NSSize cellSize = [textView cellSize];
1274 pt = NSMakePoint((c+1)*cellSize.width, (r+1)*cellSize.height);
1275 pt = [textView convertPoint:pt toView:nil];
1277 pt = [[windowController window] mouseLocationOutsideOfEventStream];
1280 NSEvent *event = [NSEvent mouseEventWithType:NSRightMouseDown
1284 windowNumber:[[windowController window] windowNumber]
1290 [NSMenu popUpContextMenu:menu withEvent:event forView:textView];
1293 - (void)popupMenuWithAttributes:(NSDictionary *)attrs
1297 [self popupMenuWithDescriptor:[attrs objectForKey:@"descriptor"]
1298 atRow:[attrs objectForKey:@"row"]
1299 column:[attrs objectForKey:@"column"]];
1302 - (void)connectionDidDie:(NSNotification *)notification
1304 //NSLog(@"%@ %s%@", [self className], _cmd, notification);
1305 [self scheduleClose];
1308 - (void)scheduleClose
1310 // NOTE! This message can arrive at pretty much anytime, e.g. while
1311 // the run loop is the 'event tracking' mode. This means that Cocoa may
1312 // well be in the middle of processing some message while this message is
1313 // received. If we were to remove the vim controller straight away we may
1314 // free objects that Cocoa is currently using (e.g. view objects). The
1315 // following call ensures that the vim controller is not released until the
1316 // run loop is back in the 'default' mode.
1317 [[MMAppController sharedInstance]
1318 performSelectorOnMainThread:@selector(removeVimController:)
1321 modes:[NSArray arrayWithObject:
1322 NSDefaultRunLoopMode]];
1325 - (void)handleBrowseForFile:(NSDictionary *)attr
1327 if (!isInitialized) return;
1329 BOOL inDefaultMode = [[[NSRunLoop currentRunLoop] currentMode]
1330 isEqual:NSDefaultRunLoopMode];
1331 if (!inDefaultMode) {
1332 // Delay call until run loop is in default mode.
1333 [self performSelectorOnMainThread:@selector(handleBrowseForFile:)
1336 modes:[NSArray arrayWithObject:
1337 NSDefaultRunLoopMode]];
1341 NSString *dir = [attr objectForKey:@"dir"];
1342 BOOL saving = [[attr objectForKey:@"saving"] boolValue];
1345 // 'dir == nil' means: set dir to the pwd of the Vim process, or let
1346 // open dialog decide (depending on the below user default).
1347 BOOL trackPwd = [[NSUserDefaults standardUserDefaults]
1348 boolForKey:MMDialogsTrackPwdKey];
1350 dir = [vimState objectForKey:@"pwd"];
1354 [[NSSavePanel savePanel] beginSheetForDirectory:dir file:nil
1355 modalForWindow:[windowController window]
1357 didEndSelector:@selector(savePanelDidEnd:code:context:)
1360 NSOpenPanel *panel = [NSOpenPanel openPanel];
1361 [panel setAllowsMultipleSelection:NO];
1362 [panel setAccessoryView:openPanelAccessoryView()];
1364 [panel beginSheetForDirectory:dir file:nil types:nil
1365 modalForWindow:[windowController window]
1367 didEndSelector:@selector(savePanelDidEnd:code:context:)
1372 - (void)handleShowDialog:(NSDictionary *)attr
1374 if (!isInitialized) return;
1376 BOOL inDefaultMode = [[[NSRunLoop currentRunLoop] currentMode]
1377 isEqual:NSDefaultRunLoopMode];
1378 if (!inDefaultMode) {
1379 // Delay call until run loop is in default mode.
1380 [self performSelectorOnMainThread:@selector(handleShowDialog:)
1383 modes:[NSArray arrayWithObject:
1384 NSDefaultRunLoopMode]];
1388 NSArray *buttonTitles = [attr objectForKey:@"buttonTitles"];
1389 if (!(buttonTitles && [buttonTitles count])) return;
1391 int style = [[attr objectForKey:@"alertStyle"] intValue];
1392 NSString *message = [attr objectForKey:@"messageText"];
1393 NSString *text = [attr objectForKey:@"informativeText"];
1394 NSString *textFieldString = [attr objectForKey:@"textFieldString"];
1395 MMAlert *alert = [[MMAlert alloc] init];
1397 // NOTE! This has to be done before setting the informative text.
1398 if (textFieldString)
1399 [alert setTextFieldString:textFieldString];
1401 [alert setAlertStyle:style];
1404 [alert setMessageText:message];
1406 // If no message text is specified 'Alert' is used, which we don't
1407 // want, so set an empty string as message text.
1408 [alert setMessageText:@""];
1412 [alert setInformativeText:text];
1413 } else if (textFieldString) {
1414 // Make sure there is always room for the input text field.
1415 [alert setInformativeText:@""];
1418 unsigned i, count = [buttonTitles count];
1419 for (i = 0; i < count; ++i) {
1420 NSString *title = [buttonTitles objectAtIndex:i];
1421 // NOTE: The title of the button may contain the character '&' to
1422 // indicate that the following letter should be the key equivalent
1423 // associated with the button. Extract this letter and lowercase it.
1424 NSString *keyEquivalent = nil;
1425 NSRange hotkeyRange = [title rangeOfString:@"&"];
1426 if (NSNotFound != hotkeyRange.location) {
1427 if ([title length] > NSMaxRange(hotkeyRange)) {
1428 NSRange keyEquivRange = NSMakeRange(hotkeyRange.location+1, 1);
1429 keyEquivalent = [[title substringWithRange:keyEquivRange]
1433 NSMutableString *string = [NSMutableString stringWithString:title];
1434 [string deleteCharactersInRange:hotkeyRange];
1438 [alert addButtonWithTitle:title];
1440 // Set key equivalent for the button, but only if NSAlert hasn't
1441 // already done so. (Check the documentation for
1442 // - [NSAlert addButtonWithTitle:] to see what key equivalents are
1443 // automatically assigned.)
1444 NSButton *btn = [[alert buttons] lastObject];
1445 if ([[btn keyEquivalent] length] == 0 && keyEquivalent) {
1446 [btn setKeyEquivalent:keyEquivalent];
1450 [alert beginSheetModalForWindow:[windowController window]
1452 didEndSelector:@selector(alertDidEnd:code:context:)
1459 @end // MMVimController (Private)
1464 @implementation MMAlert
1467 [textField release]; textField = nil;
1471 - (void)setTextFieldString:(NSString *)textFieldString
1473 [textField release];
1474 textField = [[NSTextField alloc] init];
1475 [textField setStringValue:textFieldString];
1478 - (NSTextField *)textField
1483 - (void)setInformativeText:(NSString *)text
1486 // HACK! Add some space for the text field.
1487 [super setInformativeText:[text stringByAppendingString:@"\n\n\n"]];
1489 [super setInformativeText:text];
1493 - (void)beginSheetModalForWindow:(NSWindow *)window
1494 modalDelegate:(id)delegate
1495 didEndSelector:(SEL)didEndSelector
1496 contextInfo:(void *)contextInfo
1498 [super beginSheetModalForWindow:window
1499 modalDelegate:delegate
1500 didEndSelector:didEndSelector
1501 contextInfo:contextInfo];
1503 // HACK! Place the input text field at the bottom of the informative text
1504 // (which has been made a bit larger by adding newline characters).
1505 NSView *contentView = [[self window] contentView];
1506 NSRect rect = [contentView frame];
1507 rect.origin.y = rect.size.height;
1509 NSArray *subviews = [contentView subviews];
1510 unsigned i, count = [subviews count];
1511 for (i = 0; i < count; ++i) {
1512 NSView *view = [subviews objectAtIndex:i];
1513 if ([view isKindOfClass:[NSTextField class]]
1514 && [view frame].origin.y < rect.origin.y) {
1515 // NOTE: The informative text field is the lowest NSTextField in
1516 // the alert dialog.
1517 rect = [view frame];
1521 rect.size.height = MMAlertTextFieldHeight;
1522 [textField setFrame:rect];
1523 [contentView addSubview:textField];
1524 [textField becomeFirstResponder];
1533 isUnsafeMessage(int msgid)
1535 // Messages that may release Cocoa objects must be added to this list. For
1536 // example, UpdateTabBarMsgID may delete NSTabViewItem objects so it goes
1538 static int unsafeMessages[] = { // REASON MESSAGE IS ON THIS LIST:
1539 //OpenWindowMsgID, // Changes lots of state
1540 UpdateTabBarMsgID, // May delete NSTabViewItem
1541 RemoveMenuItemMsgID, // Deletes NSMenuItem
1542 DestroyScrollbarMsgID, // Deletes NSScroller
1543 ExecuteActionMsgID, // Impossible to predict
1544 ShowPopupMenuMsgID, // Enters modal loop
1546 EnterFullscreenMsgID, // Modifies delegate of window controller
1547 LeaveFullscreenMsgID, // Modifies delegate of window controller
1548 CloseWindowMsgID, // See note below
1551 // NOTE about CloseWindowMsgID: If this arrives at the same time as say
1552 // ExecuteActionMsgID, then the "execute" message will be lost due to it
1553 // being queued and handled after the "close" message has caused the
1554 // controller to cleanup...UNLESS we add CloseWindowMsgID to the list of
1555 // unsafe messages. This is the _only_ reason it is on this list (since
1556 // all that happens in response to it is that we schedule another message
1557 // for later handling).
1559 int i, count = sizeof(unsafeMessages)/sizeof(unsafeMessages[0]);
1560 for (i = 0; i < count; ++i)
1561 if (msgid == unsafeMessages[i])