Guard against reentrant calls to processCommandQueue:
[MacVim.git] / src / MacVim / MMWindowController.m
blob476ecd48fea065758ad7d3345d45df8a6eeeb419
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
6  * Do ":help uganda"  in Vim to read copying and usage conditions.
7  * Do ":help credits" in Vim to see a list of people who contributed.
8  * See README.txt for an overview of the Vim source code.
9  */
11  * MMWindowController
12  *
13  * Handles resizing of windows, acts as an mediator between MMVimView and
14  * MMVimController.
15  *
16  * Resizing in windowed mode:
17  *
18  * In windowed mode resizing can occur either due to the window frame changing
19  * size (e.g. when the user drags to resize), or due to Vim changing the number
20  * of (rows,columns).  The former case is dealt with by letting the vim view
21  * fill the entire content view when the window has resized.  In the latter
22  * case we ensure that vim view fits on the screen.
23  *
24  * The vim view notifies Vim if the number of (rows,columns) does not match the
25  * current number whenver the view size is about to change.  Upon receiving a
26  * dimension change message, Vim notifies the window controller and the window
27  * resizes.  However, the window is never resized programmatically during a
28  * live resize (in order to avoid jittering).
29  *
30  * The window size is constrained to not become too small during live resize,
31  * and it is also constrained to always fit an integer number of
32  * (rows,columns).
33  *
34  * In windowed mode we have to manually draw a tabline separator (due to bugs
35  * in the way Cocoa deals with the toolbar separator) when certain conditions
36  * are met.  The rules for this are as follows:
37  *
38  *   Tabline visible & Toolbar visible  =>  Separator visible
39  *   =====================================================================
40  *         NO        &        NO        =>  YES, if the window is textured
41  *                                           NO, otherwise
42  *         NO        &       YES        =>  YES
43  *        YES        &        NO        =>   NO
44  *        YES        &       YES        =>   NO
45  *
46  *
47  * Resizing in full-screen mode:
48  *
49  * The window never resizes since it fills the screen, however the vim view may
50  * change size, e.g. when the user types ":set lines=60", or when a scrollbar
51  * is toggled.
52  *
53  * It is ensured that the vim view never becomes larger than the screen size
54  * and that it always stays in the center of the screen.
55  *  
56  */
58 #import "MMWindowController.h"
59 #import "MMAppController.h"
60 #import "MMAtsuiTextView.h"
61 #import "MMFullscreenWindow.h"
62 #import "MMTextView.h"
63 #import "MMTypesetter.h"
64 #import "MMVimController.h"
65 #import "MMVimView.h"
66 #import "MMWindow.h"
67 #import "MacVim.h"
69 #import <PSMTabBarControl.h>
73 @interface MMWindowController (Private)
74 - (NSSize)contentSize;
75 - (void)resizeWindowToFitContentSize:(NSSize)contentSize;
76 - (NSSize)constrainContentSizeToScreenSize:(NSSize)contentSize;
77 - (void)updateResizeConstraints;
78 - (NSTabViewItem *)addNewTabViewItem;
79 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb;
80 - (void)hideTablineSeparator:(BOOL)hide;
81 - (void)doFindNext:(BOOL)next;
82 @end
85 @interface NSWindow (NSWindowPrivate)
86 // Note: This hack allows us to set content shadowing separately from
87 // the window shadow.  This is apparently what webkit and terminal do.
88 - (void)_setContentHasShadow:(BOOL)shadow; // new Tiger private method
90 // This is a private api that makes textured windows not have rounded corners.
91 // We want this on Leopard.
92 - (void)setBottomCornerRounded:(BOOL)rounded;
93 @end
96 @interface NSWindow (NSLeopardOnly)
97 // Note: These functions are Leopard-only, use -[NSObject respondsToSelector:]
98 // before calling them to make sure everything works on Tiger too.
100 #ifndef CGFLOAT_DEFINED
101     // On Leopard, CGFloat is float on 32bit and double on 64bit. On Tiger,
102     // we can't use this anyways, so it's just here to keep the compiler happy.
103     // However, when we're compiling for Tiger and running on Leopard, we
104     // might need the correct typedef, so this piece is copied from ATSTypes.h
105 # ifdef __LP64__
106     typedef double CGFloat;
107 # else
108     typedef float CGFloat;
109 # endif
110 #endif
111 - (void)setAutorecalculatesContentBorderThickness:(BOOL)b forEdge:(NSRectEdge)e;
112 - (void)setContentBorderThickness:(CGFloat)b forEdge:(NSRectEdge)e;
113 @end
118 @implementation MMWindowController
120 - (id)initWithVimController:(MMVimController *)controller
122 #ifndef NSAppKitVersionNumber10_4  // needed for non-10.5 sdk
123 # define NSAppKitVersionNumber10_4 824
124 #endif
125     unsigned styleMask = NSTitledWindowMask | NSClosableWindowMask
126             | NSMiniaturizableWindowMask | NSResizableWindowMask
127             | NSUnifiedTitleAndToolbarWindowMask;
129     // Use textured background on Leopard or later (skip the 'if' on Tiger for
130     // polished metal window).
131     if ([[NSUserDefaults standardUserDefaults] boolForKey:MMTexturedWindowKey]
132             || (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_4))
133         styleMask |= NSTexturedBackgroundWindowMask;
135     // NOTE: The content rect is only used the very first time MacVim is
136     // started (or rather, when ~/Library/Preferences/org.vim.MacVim.plist does
137     // not exist).  The chosen values will put the window somewhere near the
138     // top and in the middle of a 1024x768 screen.
139     MMWindow *win = [[MMWindow alloc]
140             initWithContentRect:NSMakeRect(242,364,480,360)
141                       styleMask:styleMask
142                         backing:NSBackingStoreBuffered
143                           defer:YES];
144     [win autorelease];
146     self = [super initWithWindow:win];
147     if (!self) return nil;
149     vimController = controller;
150     decoratedWindow = [win retain];
152     // Window cascading is handled by MMAppController.
153     [self setShouldCascadeWindows:NO];
155     // NOTE: Autoresizing is enabled for the content view, but only used
156     // for the tabline separator.  The vim view must be resized manually
157     // because of full-screen considerations, and because its size depends
158     // on whether the tabline separator is visible or not.
159     NSView *contentView = [win contentView];
160     [contentView setAutoresizesSubviews:YES];
162     vimView = [[MMVimView alloc] initWithFrame:[contentView frame]
163                                  vimController:vimController];
164     [vimView setAutoresizingMask:NSViewNotSizable];
165     [contentView addSubview:vimView];
167     [win setDelegate:self];
168     [win setInitialFirstResponder:[vimView textView]];
169     
170     if ([win styleMask] & NSTexturedBackgroundWindowMask) {
171         // On Leopard, we want to have a textured window to have nice
172         // looking tabs. But the textured window look implies rounded
173         // corners, which looks really weird -- disable them. This is a
174         // private api, though.
175         if ([win respondsToSelector:@selector(setBottomCornerRounded:)])
176             [win setBottomCornerRounded:NO];
178         // When the tab bar is toggled, it changes color for the fraction
179         // of a second, probably because vim sends us events in a strange
180         // order, confusing appkit's content border heuristic for a short
181         // while.  This can be worked around with these two methods.  There
182         // might be a better way, but it's good enough.
183         if ([win respondsToSelector:@selector(
184                 setAutorecalculatesContentBorderThickness:forEdge:)])
185             [win setAutorecalculatesContentBorderThickness:NO
186                                                    forEdge:NSMaxYEdge];
187         if ([win respondsToSelector:
188                 @selector(setContentBorderThickness:forEdge:)])
189             [win setContentBorderThickness:0 forEdge:NSMaxYEdge];
190     }
192     // Make us safe on pre-tiger OSX
193     if ([win respondsToSelector:@selector(_setContentHasShadow:)])
194         [win _setContentHasShadow:NO];
196     return self;
199 - (void)dealloc
201     //NSLog(@"%@ %s", [self className], _cmd);
203     [decoratedWindow release];  decoratedWindow = nil;
204     [windowAutosaveKey release];  windowAutosaveKey = nil;
205     [vimView release];  vimView = nil;
207     [super dealloc];
210 - (NSString *)description
212     NSString *format =
213         @"%@ : setupDone=%d windowAutosaveKey=%@ vimController=%@";
214     return [NSString stringWithFormat:format,
215         [self className], setupDone, windowAutosaveKey, vimController];
218 - (MMVimController *)vimController
220     return vimController;
223 - (MMVimView *)vimView
225     return vimView;
228 - (NSString *)windowAutosaveKey
230     return windowAutosaveKey;
233 - (void)setWindowAutosaveKey:(NSString *)key
235     [windowAutosaveKey autorelease];
236     windowAutosaveKey = [key copy];
239 - (void)cleanup
241     //NSLog(@"%@ %s", [self className], _cmd);
243     if (fullscreenEnabled) {
244         // If we are closed while still in fullscreen, end fullscreen mode,
245         // release ourselves (because this won't happen in MMWindowController)
246         // and perform close operation on the original window.
247         [self leaveFullscreen];
248     }
250     setupDone = NO;
251     vimController = nil;
253     [vimView removeFromSuperviewWithoutNeedingDisplay];
254     [vimView cleanup];
256     // It is feasible (though unlikely) that the user quits before the window
257     // controller is released, make sure the edit flag is cleared so no warning
258     // dialog is displayed.
259     [decoratedWindow setDocumentEdited:NO];
261     [[self window] orderOut:self];
264 - (void)openWindow
266     [[MMAppController sharedInstance] windowControllerWillOpen:self];
268     [self addNewTabViewItem];
270     setupDone = YES;
272     [self updateResizeConstraints];
273     [self resizeWindowToFitContentSize:[vimView desiredSize]];
274     [[self window] makeKeyAndOrderFront:self];
277 - (void)updateTabsWithData:(NSData *)data
279     [vimView updateTabsWithData:data];
282 - (void)selectTabWithIndex:(int)idx
284     [vimView selectTabWithIndex:idx];
287 - (void)setTextDimensionsWithRows:(int)rows columns:(int)cols live:(BOOL)live
289     //NSLog(@"setTextDimensionsWithRows:%d columns:%d live:%s", rows, cols,
290     //        live ? "YES" : "NO");
292     // NOTE: This is the only place where the (rows,columns) of the vim view
293     // are modified.  Setting these values have no immediate effect, the actual
294     // resizing of the view is done in processCommandQueueDidFinish.
295     //
296     // The 'live' flag indicates that this resize originated from a live
297     // resize; it may very well happen that the view is no longer in live
298     // resize when this message is received.  We refrain from changing the view
299     // size when this flag is set, otherwise the window might jitter when the
300     // user drags to resize the window.
302     [vimView setDesiredRows:rows columns:cols];
304     if (setupDone && !live)
305         shouldResizeVimView = YES;
308 - (void)setTitle:(NSString *)title
310     if (title) {
311         [decoratedWindow setTitle:title];
312         [fullscreenWindow setTitle:title];
313     }
316 - (void)setToolbar:(NSToolbar *)toolbar
318     // The full-screen window has no toolbar.
319     [decoratedWindow setToolbar:toolbar];
321     // HACK! Redirect the pill button so that we can ask Vim to hide the
322     // toolbar.
323     NSButton *pillButton = [decoratedWindow
324             standardWindowButton:NSWindowToolbarButton];
325     if (pillButton) {
326         [pillButton setAction:@selector(toggleToolbar:)];
327         [pillButton setTarget:self];
328     }
331 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
333     [vimView createScrollbarWithIdentifier:ident type:type];
336 - (BOOL)destroyScrollbarWithIdentifier:(long)ident
338     BOOL scrollbarHidden = [vimView destroyScrollbarWithIdentifier:ident];   
339     shouldResizeVimView = shouldResizeVimView || scrollbarHidden;
341     return scrollbarHidden;
344 - (BOOL)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
346     BOOL scrollbarToggled = [vimView showScrollbarWithIdentifier:ident
347                                                            state:visible];
348     shouldResizeVimView = shouldResizeVimView || scrollbarToggled;
350     return scrollbarToggled;
353 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
355     [vimView setScrollbarPosition:pos length:len identifier:ident];
358 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
359                     identifier:(long)ident
361     [vimView setScrollbarThumbValue:val proportion:prop identifier:ident];
364 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
366     // NOTE: This is called when the transparency changes so set the opacity
367     // flag on the window here (should be faster if the window is opaque).
368     BOOL isOpaque = [back alphaComponent] == 1.0f;
369     [decoratedWindow setOpaque:isOpaque];
370     if (fullscreenEnabled)
371         [fullscreenWindow setOpaque:isOpaque];
373     [vimView setDefaultColorsBackground:back foreground:fore];
376 - (void)setFont:(NSFont *)font
378     [[NSFontManager sharedFontManager] setSelectedFont:font isMultiple:NO];
379     [[vimView textView] setFont:font];
380     [self updateResizeConstraints];
383 - (void)setWideFont:(NSFont *)font
385     [[vimView textView] setWideFont:font];
388 - (void)processCommandQueueDidFinish
390     // IMPORTANT!  No synchronous DO calls are allowed in this method.  They
391     // may cause the command queue to get processed out of order.
393     // NOTE: Resizing is delayed until after all commands have been processed
394     // since it often happens that more than one command will cause a resize.
395     // If we were to immediately resize then the vim view size would jitter
396     // (e.g.  hiding/showing scrollbars often happens several time in one
397     // update).
399     if (shouldResizeVimView) {
400         shouldResizeVimView = NO;
402         NSSize contentSize = [vimView desiredSize];
403         contentSize = [self constrainContentSizeToScreenSize:contentSize];
404         contentSize = [vimView constrainRows:NULL columns:NULL
405                                       toSize:contentSize];
406         [vimView setFrameSize:contentSize];
408         if (fullscreenEnabled) {
409             [[fullscreenWindow contentView] setNeedsDisplay:YES];
410             [fullscreenWindow centerView];
411         } else {
412             [self resizeWindowToFitContentSize:contentSize];
413         }
414     }
417 - (void)showTabBar:(BOOL)on
419     [[vimView tabBarControl] setHidden:!on];
421     // Showing the tabline may result in the tabline separator being hidden or
422     // shown; this does not apply to full-screen mode.
423     if (!on) {
424         NSToolbar *toolbar = [decoratedWindow toolbar]; 
425         if (([decoratedWindow styleMask] & NSTexturedBackgroundWindowMask)
426                 == 0) {
427             [self hideTablineSeparator:![toolbar isVisible]];
428         } else {
429             [self hideTablineSeparator:NO];
430         }
431     } else {
432         if (([decoratedWindow styleMask] & NSTexturedBackgroundWindowMask)
433                 == 0) {
434             [self hideTablineSeparator:on];
435         } else {
436             [self hideTablineSeparator:YES];
437         }
438     }
441 - (void)showToolbar:(BOOL)on size:(int)size mode:(int)mode
443     NSToolbar *toolbar = [decoratedWindow toolbar];
444     if (!toolbar) return;
446     [toolbar setSizeMode:size];
447     [toolbar setDisplayMode:mode];
448     [toolbar setVisible:on];
450     if (([decoratedWindow styleMask] & NSTexturedBackgroundWindowMask) == 0) {
451         if (!on) {
452             [self hideTablineSeparator:YES];
453         } else {
454             [self hideTablineSeparator:![[vimView tabBarControl] isHidden]];
455         }
456     } else {
457         // Textured windows don't have a line below there title bar, so we
458         // need the separator in this case as well. In fact, the only case
459         // where we don't need the separator is when the tab bar control
460         // is visible (because it brings its own separator).
461         [self hideTablineSeparator:![[vimView tabBarControl] isHidden]];
462     }
465 - (void)setMouseShape:(int)shape
467     [[vimView textView] setMouseShape:shape];
470 - (void)adjustLinespace:(int)linespace
472     if (vimView && [vimView textView]) {
473         [[vimView textView] setLinespace:(float)linespace];
474         shouldResizeVimView = YES;
475     }
478 - (void)liveResizeWillStart
480     // Save the original title, if we haven't already.
481     if (lastSetTitle == nil) {
482         lastSetTitle = [[decoratedWindow title] retain];
483     }
486 - (void)liveResizeDidEnd
488     if (!setupDone) return;
490     // NOTE: During live resize messages from MacVim to Vim are often dropped
491     // (because too many messages are sent at once).  This may lead to
492     // inconsistent states between Vim and MacVim; to avoid this we send a
493     // synchronous resize message to Vim now (this is not fool-proof, but it
494     // does seem to work quite well).
496     int constrained[2];
497     NSSize textViewSize = [[vimView textView] frame].size;
498     [[vimView textView] constrainRows:&constrained[0] columns:&constrained[1]
499                                toSize:textViewSize];
501     //NSLog(@"End of live resize, notify Vim that text dimensions are %dx%d",
502     //       constrained[1], constrained[0]);
504     NSData *data = [NSData dataWithBytes:constrained length:2*sizeof(int)];
505     BOOL sendOk = [vimController sendMessageNow:SetTextDimensionsMsgID
506                                            data:data
507                                         timeout:.5];
509     if (!sendOk) {
510         // Sending of synchronous message failed.  Force the window size to
511         // match the last dimensions received from Vim, otherwise we end up
512         // with inconsistent states.
513         [self resizeWindowToFitContentSize:[vimView desiredSize]];
514     }
516     // If we saved the original title while resizing, restore it.
517     if (lastSetTitle != nil) {
518         [decoratedWindow setTitle:lastSetTitle];
519         [lastSetTitle release];
520         lastSetTitle = nil;
521     }
524 - (void)enterFullscreen:(int)fuoptions backgroundColor:(NSColor *)back
526     if (fullscreenEnabled) return;
528     fullscreenWindow = [[MMFullscreenWindow alloc]
529         initWithWindow:decoratedWindow view:vimView backgroundColor:back];
530     [fullscreenWindow enterFullscreen:fuoptions];    
531     [fullscreenWindow setDelegate:self];
532     fullscreenEnabled = YES;
534     // The resize handle disappears so the vim view needs to update the
535     // scrollbars.
536     shouldResizeVimView = YES;
539 - (void)leaveFullscreen
541     if (!fullscreenEnabled) return;
543     fullscreenEnabled = NO;
544     [fullscreenWindow leaveFullscreen];    
545     [fullscreenWindow release];
546     fullscreenWindow = nil;
548     // The vim view may be too large to fit the screen, so update it.
549     shouldResizeVimView = YES;
552 - (void)setBuffersModified:(BOOL)mod
554     // NOTE: We only set the document edited flag on the decorated window since
555     // the full-screen window has no close button anyway.  (It also saves us
556     // from keeping track of the flag in two different places.)
557     [decoratedWindow setDocumentEdited:mod];
561 - (IBAction)addNewTab:(id)sender
563     [vimView addNewTab:sender];
566 - (IBAction)toggleToolbar:(id)sender
568     [vimController sendMessage:ToggleToolbarMsgID data:nil];
571 - (IBAction)performClose:(id)sender
573     // NOTE: With the introduction of :macmenu it is possible to bind
574     // File.Close to ":conf q" but at the same time have it send off the
575     // performClose: action.  For this reason we no longer need the CloseMsgID
576     // message.  However, we still need File.Close to send performClose:
577     // otherwise Cmd-w will not work on dialogs.
578     [self vimMenuItemAction:sender];
581 - (IBAction)findNext:(id)sender
583     [self doFindNext:YES];
586 - (IBAction)findPrevious:(id)sender
588     [self doFindNext:NO];
591 - (IBAction)vimMenuItemAction:(id)sender
593     if (![sender isKindOfClass:[NSMenuItem class]]) return;
595     // TODO: Make into category on NSMenuItem which returns descriptor.
596     NSMenuItem *item = (NSMenuItem*)sender;
597     NSMutableArray *desc = [NSMutableArray arrayWithObject:[item title]];
599     NSMenu *menu = [item menu];
600     while (menu) {
601         [desc insertObject:[menu title] atIndex:0];
602         menu = [menu supermenu];
603     }
605     // The "MainMenu" item is part of the Cocoa menu and should not be part of
606     // the descriptor.
607     if ([[desc objectAtIndex:0] isEqual:@"MainMenu"])
608         [desc removeObjectAtIndex:0];
610     NSDictionary *attrs = [NSDictionary dictionaryWithObject:desc
611                                                       forKey:@"descriptor"];
612     [vimController sendMessage:ExecuteMenuMsgID data:[attrs dictionaryAsData]];
615 - (IBAction)vimToolbarItemAction:(id)sender
617     NSArray *desc = [NSArray arrayWithObjects:@"ToolBar", [sender label], nil];
618     NSDictionary *attrs = [NSDictionary dictionaryWithObject:desc
619                                                       forKey:@"descriptor"];
620     [vimController sendMessage:ExecuteMenuMsgID data:[attrs dictionaryAsData]];
623 - (IBAction)fontSizeUp:(id)sender
625     [[NSFontManager sharedFontManager] modifyFont:
626             [NSNumber numberWithInt:NSSizeUpFontAction]];
629 - (IBAction)fontSizeDown:(id)sender
631     [[NSFontManager sharedFontManager] modifyFont:
632             [NSNumber numberWithInt:NSSizeDownFontAction]];
635 - (BOOL)validateMenuItem:(NSMenuItem *)item
637     if ([item action] == @selector(vimMenuItemAction:)
638             || [item action] == @selector(performClose:))
639         return [item tag];
641     return YES;
644 // -- NSWindow delegate ------------------------------------------------------
646 - (void)windowDidBecomeMain:(NSNotification *)notification
648     [[MMAppController sharedInstance] setMainMenu:[vimController mainMenu]];
649     [vimController sendMessage:GotFocusMsgID data:nil];
651     if ([vimView textView]) {
652         NSFontManager *fm = [NSFontManager sharedFontManager];
653         [fm setSelectedFont:[[vimView textView] font] isMultiple:NO];
654     }
657 - (void)windowDidResignMain:(NSNotification *)notification
659     [vimController sendMessage:LostFocusMsgID data:nil];
661     if ([vimView textView])
662         [[vimView textView] hideMarkedTextField];
665 - (BOOL)windowShouldClose:(id)sender
667     // Don't close the window now; Instead let Vim decide whether to close the
668     // window or not.
669     [vimController sendMessage:VimShouldCloseMsgID data:nil];
670     return NO;
673 - (void)windowDidMove:(NSNotification *)notification
675     if (setupDone && windowAutosaveKey) {
676         NSRect frame = [decoratedWindow frame];
677         NSPoint topLeft = { frame.origin.x, NSMaxY(frame) };
678         NSString *topLeftString = NSStringFromPoint(topLeft);
680         [[NSUserDefaults standardUserDefaults]
681             setObject:topLeftString forKey:windowAutosaveKey];
682     }
685 - (void)windowDidResize:(id)sender
687     if (!setupDone || fullscreenEnabled) return;
689     // NOTE: Since we have no control over when the window may resize (Cocoa
690     // may resize automatically) we simply set the view to fill the entire
691     // window.  The vim view takes care of notifying Vim if the number of
692     // (rows,columns) changed.
693     [vimView setFrameSize:[self contentSize]];
696 - (NSRect)windowWillUseStandardFrame:(NSWindow *)win
697                         defaultFrame:(NSRect)frame
699     // By default the window is maximized in the vertical direction only.
700     // Holding down the Cmd key maximizes the window in the horizontal
701     // direction.  If the MMZoomBoth user default is set, then the window
702     // maximizes in both directions by default, unless the Cmd key is held in
703     // which case the window only maximizes in the vertical direction.
705     NSEvent *event = [NSApp currentEvent];
706     BOOL cmdLeftClick = [event type] == NSLeftMouseUp
707             && [event modifierFlags] & NSCommandKeyMask;
708     BOOL zoomBoth = [[NSUserDefaults standardUserDefaults]
709             boolForKey:MMZoomBothKey];
711     if (!((zoomBoth && !cmdLeftClick) || (!zoomBoth && cmdLeftClick))) {
712         // Zoom in horizontal direction only.
713         NSRect currentFrame = [win frame];
714         frame.size.width = currentFrame.size.width;
715         frame.origin.x = currentFrame.origin.x;
716     }
718     // HACK! The window frame is often higher than the 'defaultFrame' (the fact
719     // that 'defaultFrame' doesn't cover the entire area up to the menu bar
720     // seems like a Cocoa bug).  To ensure that the window doesn't move
721     // downwards when the zoom button is clicked we check for this situation
722     // and return a frame whose max Y coordinate is no lower than the current
723     // max Y coordinate.
724     float delta = NSMaxY([win frame]) - NSMaxY(frame);
725     if (delta > 0)
726         frame.origin.y += delta;
728     return frame;
734 // -- Services menu delegate -------------------------------------------------
736 - (id)validRequestorForSendType:(NSString *)sendType
737                      returnType:(NSString *)returnType
739     if ([sendType isEqual:NSStringPboardType]
740             && [self askBackendForStarRegister:nil])
741         return self;
743     return [super validRequestorForSendType:sendType returnType:returnType];
746 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
747                              types:(NSArray *)types
749     if (![types containsObject:NSStringPboardType])
750         return NO;
752     return [self askBackendForStarRegister:pboard];
755 @end // MMWindowController
759 @implementation MMWindowController (Private)
761 - (NSSize)contentSize
763     // NOTE: Never query the content view directly for its size since it may
764     // not return the same size as contentRectForFrameRect: (e.g. when in
765     // windowed mode and the tabline separator is visible)!
766     NSWindow *win = [self window];
767     return [win contentRectForFrameRect:[win frame]].size;
770 - (void)resizeWindowToFitContentSize:(NSSize)contentSize
772     NSRect frame = [decoratedWindow frame];
773     NSRect contentRect = [decoratedWindow contentRectForFrameRect:frame];
775     // Keep top-left corner of the window fixed when resizing.
776     contentRect.origin.y -= contentSize.height - contentRect.size.height;
777     contentRect.size = contentSize;
779     frame = [decoratedWindow frameRectForContentRect:contentRect];
780     [decoratedWindow setFrame:frame display:YES];
783 - (NSSize)constrainContentSizeToScreenSize:(NSSize)contentSize
785     NSWindow *win = [self window];
786     NSRect rect = [win contentRectForFrameRect:[[win screen] visibleFrame]];
788     if (contentSize.height > rect.size.height)
789         contentSize.height = rect.size.height;
790     if (contentSize.width > rect.size.width)
791         contentSize.width = rect.size.width;
793     return contentSize;
796 - (void)updateResizeConstraints
798     if (!setupDone) return;
800     // Set the resize increments to exactly match the font size; this way the
801     // window will always hold an integer number of (rows,columns).
802     NSSize cellSize = [[vimView textView] cellSize];
803     [decoratedWindow setContentResizeIncrements:cellSize];
805     NSSize minSize = [vimView minSize];
806     [decoratedWindow setContentMinSize:minSize];
809 - (NSTabViewItem *)addNewTabViewItem
811     return [vimView addNewTabViewItem];
814 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb
816     // TODO: Can this be done with evaluateExpression: instead?
817     BOOL reply = NO;
818     id backendProxy = [vimController backendProxy];
820     if (backendProxy) {
821         @try {
822             reply = [backendProxy starRegisterToPasteboard:pb];
823         }
824         @catch (NSException *e) {
825             NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
826         }
827     }
829     return reply;
832 - (void)hideTablineSeparator:(BOOL)hide
834     // The full-screen window has no tabline separator so we operate on
835     // decoratedWindow instead of [self window].
836     if ([decoratedWindow hideTablineSeparator:hide]) {
837         // The tabline separator was toggled so the content view must change
838         // size.
839         [self updateResizeConstraints];
840         shouldResizeVimView = YES;
841     }
844 - (void)doFindNext:(BOOL)next
846     NSString *query = nil;
848 #if 0
849     // Use current query if the search field is selected.
850     id searchField = [[self searchFieldItem] view];
851     if (searchField && [[searchField stringValue] length] > 0 &&
852             [decoratedWindow firstResponder] == [searchField currentEditor])
853         query = [searchField stringValue];
854 #endif
856     if (!query) {
857         // Use find pasteboard for next query.
858         NSPasteboard *pb = [NSPasteboard pasteboardWithName:NSFindPboard];
859         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
860         if ([pb availableTypeFromArray:types])
861             query = [pb stringForType:NSStringPboardType];
862     }
864     NSString *input = nil;
865     if (query) {
866         // NOTE: The '/' register holds the last search string.  By setting it
867         // (using the '@/' syntax) we fool Vim into thinking that it has
868         // already searched for that string and then we can simply use 'n' or
869         // 'N' to find the next/previous match.
870         input = [NSString stringWithFormat:@"<C-\\><C-N>:let @/='%@'<CR>%c",
871                 query, next ? 'n' : 'N'];
872     } else {
873         input = next ? @"<C-\\><C-N>n" : @"<C-\\><C-N>N"; 
874     }
876     [vimController addVimInput:input];
879 @end // MMWindowController (Private)