Constrain window size during live resize
[MacVim.git] / src / MacVim / MMWindowController.m
blob0b821e12acf91cdcff1a3ea3bfe27f177c44c332
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 "MMAppController.h"
59 #import "MMAtsuiTextView.h"
60 #import "MMFindReplaceController.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 "MMWindowController.h"
68 #import "Miscellaneous.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     LOG_DEALLOC
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     // Indicates that the window is ready to be displayed, but do not display
267     // (or place) it yet -- that is done in showWindow.
269     [self addNewTabViewItem];
271     setupDone = YES;
273     [self updateResizeConstraints];
274     [self resizeWindowToFitContentSize:[vimView desiredSize]];
277 - (void)showWindow
279     // Actually show the window on screen.  However, if openWindow hasn't
280     // already been called nothing will happen (the window will be displayed
281     // later).
282     if (!setupDone) return;
284     [[MMAppController sharedInstance] windowControllerWillOpen:self];
285     [[self window] makeKeyAndOrderFront:self];
288 - (void)updateTabsWithData:(NSData *)data
290     [vimView updateTabsWithData:data];
293 - (void)selectTabWithIndex:(int)idx
295     [vimView selectTabWithIndex:idx];
298 - (void)setTextDimensionsWithRows:(int)rows columns:(int)cols live:(BOOL)live
300     //NSLog(@"setTextDimensionsWithRows:%d columns:%d live:%s", rows, cols,
301     //        live ? "YES" : "NO");
303     // NOTE: The only place where the (rows,columns) of the vim view are
304     // modified is here and when entering/leaving full-screen.  Setting these
305     // values have no immediate effect, the actual resizing of the view is done
306     // in processCommandQueueDidFinish.
307     //
308     // The 'live' flag indicates that this resize originated from a live
309     // resize; it may very well happen that the view is no longer in live
310     // resize when this message is received.  We refrain from changing the view
311     // size when this flag is set, otherwise the window might jitter when the
312     // user drags to resize the window.
314     [vimView setDesiredRows:rows columns:cols];
316     if (setupDone && !live)
317         shouldResizeVimView = YES;
320 - (void)setTitle:(NSString *)title
322     if (title)
323         [decoratedWindow setTitle:title];
326 - (void)setDocumentFilename:(NSString *)filename
328     if (!filename)
329         return;
331     // Ensure file really exists or the path to the proxy icon will look weird.
332     // If the file does not exists, don't show a proxy icon.
333     if (![[NSFileManager defaultManager] fileExistsAtPath:filename])
334         filename = @"";
336     [decoratedWindow setRepresentedFilename:filename];
339 - (void)setToolbar:(NSToolbar *)toolbar
341     // The full-screen window has no toolbar.
342     [decoratedWindow setToolbar:toolbar];
344     // HACK! Redirect the pill button so that we can ask Vim to hide the
345     // toolbar.
346     NSButton *pillButton = [decoratedWindow
347             standardWindowButton:NSWindowToolbarButton];
348     if (pillButton) {
349         [pillButton setAction:@selector(toggleToolbar:)];
350         [pillButton setTarget:self];
351     }
354 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
356     [vimView createScrollbarWithIdentifier:ident type:type];
359 - (BOOL)destroyScrollbarWithIdentifier:(long)ident
361     BOOL scrollbarHidden = [vimView destroyScrollbarWithIdentifier:ident];   
362     shouldResizeVimView = shouldResizeVimView || scrollbarHidden;
364     return scrollbarHidden;
367 - (BOOL)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
369     BOOL scrollbarToggled = [vimView showScrollbarWithIdentifier:ident
370                                                            state:visible];
371     shouldResizeVimView = shouldResizeVimView || scrollbarToggled;
373     return scrollbarToggled;
376 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
378     [vimView setScrollbarPosition:pos length:len identifier:ident];
381 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
382                     identifier:(long)ident
384     [vimView setScrollbarThumbValue:val proportion:prop identifier:ident];
387 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
389     // NOTE: This is called when the transparency changes so set the opacity
390     // flag on the window here (should be faster if the window is opaque).
391     BOOL isOpaque = [back alphaComponent] == 1.0f;
392     [decoratedWindow setOpaque:isOpaque];
393     if (fullscreenEnabled)
394         [fullscreenWindow setOpaque:isOpaque];
396     [vimView setDefaultColorsBackground:back foreground:fore];
399 - (void)setFont:(NSFont *)font
401     [[NSFontManager sharedFontManager] setSelectedFont:font isMultiple:NO];
402     [[vimView textView] setFont:font];
403     [self updateResizeConstraints];
406 - (void)setWideFont:(NSFont *)font
408     [[vimView textView] setWideFont:font];
411 - (void)processCommandQueueDidFinish
413     // IMPORTANT!  No synchronous DO calls are allowed in this method.  They
414     // may cause the command queue to get processed out of order.
416     // NOTE: Resizing is delayed until after all commands have been processed
417     // since it often happens that more than one command will cause a resize.
418     // If we were to immediately resize then the vim view size would jitter
419     // (e.g.  hiding/showing scrollbars often happens several time in one
420     // update).
422     if (shouldResizeVimView) {
423         shouldResizeVimView = NO;
425         NSSize contentSize = [vimView desiredSize];
426         contentSize = [self constrainContentSizeToScreenSize:contentSize];
427         contentSize = [vimView constrainRows:NULL columns:NULL
428                                       toSize:contentSize];
429         [vimView setFrameSize:contentSize];
431         if (fullscreenEnabled) {
432             [[fullscreenWindow contentView] setNeedsDisplay:YES];
433             [fullscreenWindow centerView];
434         } else {
435             [self resizeWindowToFitContentSize:contentSize];
436         }
437     }
440 - (void)showTabBar:(BOOL)on
442     [[vimView tabBarControl] setHidden:!on];
444     // Showing the tabline may result in the tabline separator being hidden or
445     // shown; this does not apply to full-screen mode.
446     if (!on) {
447         NSToolbar *toolbar = [decoratedWindow toolbar]; 
448         if (([decoratedWindow styleMask] & NSTexturedBackgroundWindowMask)
449                 == 0) {
450             [self hideTablineSeparator:![toolbar isVisible]];
451         } else {
452             [self hideTablineSeparator:NO];
453         }
454     } else {
455         if (([decoratedWindow styleMask] & NSTexturedBackgroundWindowMask)
456                 == 0) {
457             [self hideTablineSeparator:on];
458         } else {
459             [self hideTablineSeparator:YES];
460         }
461     }
464 - (void)showToolbar:(BOOL)on size:(int)size mode:(int)mode
466     NSToolbar *toolbar = [decoratedWindow toolbar];
467     if (!toolbar) return;
469     [toolbar setSizeMode:size];
470     [toolbar setDisplayMode:mode];
471     [toolbar setVisible:on];
473     if (([decoratedWindow styleMask] & NSTexturedBackgroundWindowMask) == 0) {
474         if (!on) {
475             [self hideTablineSeparator:YES];
476         } else {
477             [self hideTablineSeparator:![[vimView tabBarControl] isHidden]];
478         }
479     } else {
480         // Textured windows don't have a line below there title bar, so we
481         // need the separator in this case as well. In fact, the only case
482         // where we don't need the separator is when the tab bar control
483         // is visible (because it brings its own separator).
484         [self hideTablineSeparator:![[vimView tabBarControl] isHidden]];
485     }
488 - (void)setMouseShape:(int)shape
490     [[vimView textView] setMouseShape:shape];
493 - (void)adjustLinespace:(int)linespace
495     if (vimView && [vimView textView]) {
496         [[vimView textView] setLinespace:(float)linespace];
497         shouldResizeVimView = YES;
498     }
501 - (void)liveResizeWillStart
503     // Save the original title, if we haven't already.
504     if (lastSetTitle == nil) {
505         lastSetTitle = [[decoratedWindow title] retain];
506     }
509 - (void)liveResizeDidEnd
511     if (!setupDone) return;
513     // NOTE: During live resize messages from MacVim to Vim are often dropped
514     // (because too many messages are sent at once).  This may lead to
515     // inconsistent states between Vim and MacVim; to avoid this we send a
516     // synchronous resize message to Vim now (this is not fool-proof, but it
517     // does seem to work quite well).
519     int constrained[2];
520     NSSize textViewSize = [[vimView textView] frame].size;
521     [[vimView textView] constrainRows:&constrained[0] columns:&constrained[1]
522                                toSize:textViewSize];
524     //NSLog(@"End of live resize, notify Vim that text dimensions are %dx%d",
525     //       constrained[1], constrained[0]);
527     NSData *data = [NSData dataWithBytes:constrained length:2*sizeof(int)];
528     BOOL sendOk = [vimController sendMessageNow:SetTextDimensionsMsgID
529                                            data:data
530                                         timeout:.5];
532     if (!sendOk) {
533         // Sending of synchronous message failed.  Force the window size to
534         // match the last dimensions received from Vim, otherwise we end up
535         // with inconsistent states.
536         [self resizeWindowToFitContentSize:[vimView desiredSize]];
537     }
539     // If we saved the original title while resizing, restore it.
540     if (lastSetTitle != nil) {
541         [decoratedWindow setTitle:lastSetTitle];
542         [lastSetTitle release];
543         lastSetTitle = nil;
544     }
547 - (void)enterFullscreen:(int)fuoptions backgroundColor:(NSColor *)back
549     if (fullscreenEnabled) return;
551     fullscreenWindow = [[MMFullscreenWindow alloc]
552         initWithWindow:decoratedWindow view:vimView backgroundColor:back];
553     [fullscreenWindow enterFullscreen:fuoptions];    
554     [fullscreenWindow setDelegate:self];
555     fullscreenEnabled = YES;
557     // The resize handle disappears so the vim view needs to update the
558     // scrollbars.
559     shouldResizeVimView = YES;
562 - (void)leaveFullscreen
564     if (!fullscreenEnabled) return;
566     fullscreenEnabled = NO;
567     [fullscreenWindow leaveFullscreen];    
568     [fullscreenWindow release];
569     fullscreenWindow = nil;
571     // The vim view may be too large to fit the screen, so update it.
572     shouldResizeVimView = YES;
575 - (void)setFullscreenBackgroundColor:(NSColor *)back
577     if (fullscreenEnabled)
578         [fullscreenWindow setBackgroundColor:back];
581 - (void)setBuffersModified:(BOOL)mod
583     // NOTE: We only set the document edited flag on the decorated window since
584     // the full-screen window has no close button anyway.  (It also saves us
585     // from keeping track of the flag in two different places.)
586     [decoratedWindow setDocumentEdited:mod];
590 - (IBAction)addNewTab:(id)sender
592     [vimView addNewTab:sender];
595 - (IBAction)toggleToolbar:(id)sender
597     [vimController sendMessage:ToggleToolbarMsgID data:nil];
600 - (IBAction)performClose:(id)sender
602     // NOTE: With the introduction of :macmenu it is possible to bind
603     // File.Close to ":conf q" but at the same time have it send off the
604     // performClose: action.  For this reason we no longer need the CloseMsgID
605     // message.  However, we still need File.Close to send performClose:
606     // otherwise Cmd-w will not work on dialogs.
607     [self vimMenuItemAction:sender];
610 - (IBAction)findNext:(id)sender
612     [self doFindNext:YES];
615 - (IBAction)findPrevious:(id)sender
617     [self doFindNext:NO];
620 - (IBAction)vimMenuItemAction:(id)sender
622     if (![sender isKindOfClass:[NSMenuItem class]]) return;
624     // TODO: Make into category on NSMenuItem which returns descriptor.
625     NSMenuItem *item = (NSMenuItem*)sender;
626     NSMutableArray *desc = [NSMutableArray arrayWithObject:[item title]];
628     NSMenu *menu = [item menu];
629     while (menu) {
630         [desc insertObject:[menu title] atIndex:0];
631         menu = [menu supermenu];
632     }
634     // The "MainMenu" item is part of the Cocoa menu and should not be part of
635     // the descriptor.
636     if ([[desc objectAtIndex:0] isEqual:@"MainMenu"])
637         [desc removeObjectAtIndex:0];
639     NSDictionary *attrs = [NSDictionary dictionaryWithObject:desc
640                                                       forKey:@"descriptor"];
641     [vimController sendMessage:ExecuteMenuMsgID data:[attrs dictionaryAsData]];
644 - (IBAction)vimToolbarItemAction:(id)sender
646     NSArray *desc = [NSArray arrayWithObjects:@"ToolBar", [sender label], nil];
647     NSDictionary *attrs = [NSDictionary dictionaryWithObject:desc
648                                                       forKey:@"descriptor"];
649     [vimController sendMessage:ExecuteMenuMsgID data:[attrs dictionaryAsData]];
652 - (IBAction)fontSizeUp:(id)sender
654     [[NSFontManager sharedFontManager] modifyFont:
655             [NSNumber numberWithInt:NSSizeUpFontAction]];
658 - (IBAction)fontSizeDown:(id)sender
660     [[NSFontManager sharedFontManager] modifyFont:
661             [NSNumber numberWithInt:NSSizeDownFontAction]];
664 - (IBAction)findAndReplace:(id)sender
666     int tag = [sender tag];
667     MMFindReplaceController *fr = [MMFindReplaceController sharedInstance];
668     int flags = 0;
670     // NOTE: The 'flags' values must match the FRD_ defines in gui.h (except
671     // for 0x100 which we use to indicate a backward search).
672     switch (tag) {
673         case 1: flags = 0x100; break;
674         case 2: flags = 3; break;
675         case 3: flags = 4; break;
676     }
678     if ([fr matchWord])
679         flags |= 0x08;
680     if (![fr ignoreCase])
681         flags |= 0x10;
683     NSDictionary *args = [NSDictionary dictionaryWithObjectsAndKeys:
684             [fr findString],                @"find",
685             [fr replaceString],             @"replace",
686             [NSNumber numberWithInt:flags], @"flags",
687             nil];
689     [vimController sendMessage:FindReplaceMsgID data:[args dictionaryAsData]];
692 - (BOOL)validateMenuItem:(NSMenuItem *)item
694     if ([item action] == @selector(vimMenuItemAction:)
695             || [item action] == @selector(performClose:))
696         return [item tag];
698     return YES;
701 // -- NSWindow delegate ------------------------------------------------------
703 - (void)windowDidBecomeMain:(NSNotification *)notification
705     [[MMAppController sharedInstance] setMainMenu:[vimController mainMenu]];
706     [vimController sendMessage:GotFocusMsgID data:nil];
708     if ([vimView textView]) {
709         NSFontManager *fm = [NSFontManager sharedFontManager];
710         [fm setSelectedFont:[[vimView textView] font] isMultiple:NO];
711     }
714 - (void)windowDidResignMain:(NSNotification *)notification
716     [vimController sendMessage:LostFocusMsgID data:nil];
719 - (BOOL)windowShouldClose:(id)sender
721     // Don't close the window now; Instead let Vim decide whether to close the
722     // window or not.
723     [vimController sendMessage:VimShouldCloseMsgID data:nil];
724     return NO;
727 - (void)windowDidMove:(NSNotification *)notification
729     if (setupDone && windowAutosaveKey) {
730         NSRect frame = [decoratedWindow frame];
731         NSPoint topLeft = { frame.origin.x, NSMaxY(frame) };
732         NSString *topLeftString = NSStringFromPoint(topLeft);
734         [[NSUserDefaults standardUserDefaults]
735             setObject:topLeftString forKey:windowAutosaveKey];
736     }
739 - (NSSize)windowWillResize:(NSWindow *)win toSize:(NSSize)proposedFrameSize
741     // Make sure the window isn't resized to be larger than the "visible frame"
742     // for the current screen.  Do this here instead of setting the window max
743     // size in updateResizeConstraints since the screen's visible frame may
744     // change at any time (dock could move, resolution could change, window
745     // could be moved to another screen, ...).
747     NSRect maxFrame = [[win screen] visibleFrame];
748     NSRect rect = [win contentRectForFrameRect:maxFrame];
749     rect.size = [vimView constrainRows:NULL columns:NULL toSize:rect.size];
750     maxFrame = [win frameRectForContentRect:rect];
752     if (proposedFrameSize.width > maxFrame.size.width)
753         proposedFrameSize.width = maxFrame.size.width;
754     if (proposedFrameSize.height > maxFrame.size.height)
755         proposedFrameSize.height = maxFrame.size.height;
757     return proposedFrameSize;
760 - (void)windowDidResize:(id)sender
762     if (!setupDone || fullscreenEnabled) return;
764     // NOTE: Since we have no control over when the window may resize (Cocoa
765     // may resize automatically) we simply set the view to fill the entire
766     // window.  The vim view takes care of notifying Vim if the number of
767     // (rows,columns) changed.
768     [vimView setFrameSize:[self contentSize]];
771 - (NSRect)windowWillUseStandardFrame:(NSWindow *)win
772                         defaultFrame:(NSRect)frame
774     // By default the window is maximized in the vertical direction only.
775     // Holding down the Cmd key maximizes the window in the horizontal
776     // direction.  If the MMZoomBoth user default is set, then the window
777     // maximizes in both directions by default, unless the Cmd key is held in
778     // which case the window only maximizes in the vertical direction.
780     NSEvent *event = [NSApp currentEvent];
781     BOOL cmdLeftClick = [event type] == NSLeftMouseUp
782             && [event modifierFlags] & NSCommandKeyMask;
783     BOOL zoomBoth = [[NSUserDefaults standardUserDefaults]
784             boolForKey:MMZoomBothKey];
786     // The "default frame" represents the maximal size of a zoomed window.
787     // Constrain this frame so that the content fits an even number of rows and
788     // columns.
789     NSRect contentRect = [decoratedWindow contentRectForFrameRect:frame];
790     NSSize constrainedSize = [vimView constrainRows:NULL
791                                             columns:NULL
792                                              toSize:contentRect.size];
794     contentRect.origin.y += contentRect.size.height - constrainedSize.height;
795     contentRect.size = constrainedSize;
797     frame = [decoratedWindow frameRectForContentRect:contentRect];
799     if (!((zoomBoth && !cmdLeftClick) || (!zoomBoth && cmdLeftClick))) {
800         // Zoom in horizontal direction only.
801         NSRect currentFrame = [win frame];
802         frame.size.width = currentFrame.size.width;
803         frame.origin.x = currentFrame.origin.x;
804     }
806     return frame;
812 // -- Services menu delegate -------------------------------------------------
814 - (id)validRequestorForSendType:(NSString *)sendType
815                      returnType:(NSString *)returnType
817     if ([sendType isEqual:NSStringPboardType]
818             && [self askBackendForStarRegister:nil])
819         return self;
821     return [super validRequestorForSendType:sendType returnType:returnType];
824 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
825                              types:(NSArray *)types
827     if (![types containsObject:NSStringPboardType])
828         return NO;
830     return [self askBackendForStarRegister:pboard];
833 @end // MMWindowController
837 @implementation MMWindowController (Private)
839 - (NSSize)contentSize
841     // NOTE: Never query the content view directly for its size since it may
842     // not return the same size as contentRectForFrameRect: (e.g. when in
843     // windowed mode and the tabline separator is visible)!
844     NSWindow *win = [self window];
845     return [win contentRectForFrameRect:[win frame]].size;
848 - (void)resizeWindowToFitContentSize:(NSSize)contentSize
850     NSRect frame = [decoratedWindow frame];
851     NSRect contentRect = [decoratedWindow contentRectForFrameRect:frame];
853     // Keep top-left corner of the window fixed when resizing.
854     contentRect.origin.y -= contentSize.height - contentRect.size.height;
855     contentRect.size = contentSize;
857     frame = [decoratedWindow frameRectForContentRect:contentRect];
858     [decoratedWindow setFrame:frame display:YES];
861 - (NSSize)constrainContentSizeToScreenSize:(NSSize)contentSize
863     NSWindow *win = [self window];
864     NSRect rect = [win contentRectForFrameRect:[[win screen] visibleFrame]];
866     if (contentSize.height > rect.size.height)
867         contentSize.height = rect.size.height;
868     if (contentSize.width > rect.size.width)
869         contentSize.width = rect.size.width;
871     return contentSize;
874 - (void)updateResizeConstraints
876     if (!setupDone) return;
878     // Set the resize increments to exactly match the font size; this way the
879     // window will always hold an integer number of (rows,columns).
880     NSSize cellSize = [[vimView textView] cellSize];
881     [decoratedWindow setContentResizeIncrements:cellSize];
883     NSSize minSize = [vimView minSize];
884     [decoratedWindow setContentMinSize:minSize];
887 - (NSTabViewItem *)addNewTabViewItem
889     return [vimView addNewTabViewItem];
892 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb
894     // TODO: Can this be done with evaluateExpression: instead?
895     BOOL reply = NO;
896     id backendProxy = [vimController backendProxy];
898     if (backendProxy) {
899         @try {
900             reply = [backendProxy starRegisterToPasteboard:pb];
901         }
902         @catch (NSException *e) {
903             NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
904         }
905     }
907     return reply;
910 - (void)hideTablineSeparator:(BOOL)hide
912     // The full-screen window has no tabline separator so we operate on
913     // decoratedWindow instead of [self window].
914     if ([decoratedWindow hideTablineSeparator:hide]) {
915         // The tabline separator was toggled so the content view must change
916         // size.
917         [self updateResizeConstraints];
918         shouldResizeVimView = YES;
919     }
922 - (void)doFindNext:(BOOL)next
924     NSString *query = nil;
926 #if 0
927     // Use current query if the search field is selected.
928     id searchField = [[self searchFieldItem] view];
929     if (searchField && [[searchField stringValue] length] > 0 &&
930             [decoratedWindow firstResponder] == [searchField currentEditor])
931         query = [searchField stringValue];
932 #endif
934     if (!query) {
935         // Use find pasteboard for next query.
936         NSPasteboard *pb = [NSPasteboard pasteboardWithName:NSFindPboard];
937         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
938         if ([pb availableTypeFromArray:types])
939             query = [pb stringForType:NSStringPboardType];
940     }
942     NSString *input = nil;
943     if (query) {
944         // NOTE: The '/' register holds the last search string.  By setting it
945         // (using the '@/' syntax) we fool Vim into thinking that it has
946         // already searched for that string and then we can simply use 'n' or
947         // 'N' to find the next/previous match.
948         input = [NSString stringWithFormat:@"<C-\\><C-N>:let @/='%@'<CR>%c",
949                 query, next ? 'n' : 'N'];
950     } else {
951         input = next ? @"<C-\\><C-N>n" : @"<C-\\><C-N>N"; 
952     }
954     [vimController addVimInput:input];
957 @end // MMWindowController (Private)