Validate screen before constraining window
[MacVim.git] / src / MacVim / MMWindowController.m
blob6d00d298ef0017b0d7ace22934a9fa0629ad7512
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                         keepOnScreen:(BOOL)onScreen;
77 - (NSSize)constrainContentSizeToScreenSize:(NSSize)contentSize;
78 - (NSRect)constrainFrame:(NSRect)frame;
79 - (void)updateResizeConstraints;
80 - (NSTabViewItem *)addNewTabViewItem;
81 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb;
82 - (void)hideTablineSeparator:(BOOL)hide;
83 - (void)doFindNext:(BOOL)next;
84 - (void)updateToolbar;
85 @end
88 @interface NSWindow (NSWindowPrivate)
89 // Note: This hack allows us to set content shadowing separately from
90 // the window shadow.  This is apparently what webkit and terminal do.
91 - (void)_setContentHasShadow:(BOOL)shadow; // new Tiger private method
93 // This is a private api that makes textured windows not have rounded corners.
94 // We want this on Leopard.
95 - (void)setBottomCornerRounded:(BOOL)rounded;
96 @end
99 @interface NSWindow (NSLeopardOnly)
100 // Note: These functions are Leopard-only, use -[NSObject respondsToSelector:]
101 // before calling them to make sure everything works on Tiger too.
103 #ifndef CGFLOAT_DEFINED
104     // On Leopard, CGFloat is float on 32bit and double on 64bit. On Tiger,
105     // we can't use this anyways, so it's just here to keep the compiler happy.
106     // However, when we're compiling for Tiger and running on Leopard, we
107     // might need the correct typedef, so this piece is copied from ATSTypes.h
108 # ifdef __LP64__
109     typedef double CGFloat;
110 # else
111     typedef float CGFloat;
112 # endif
113 #endif
114 - (void)setAutorecalculatesContentBorderThickness:(BOOL)b forEdge:(NSRectEdge)e;
115 - (void)setContentBorderThickness:(CGFloat)b forEdge:(NSRectEdge)e;
116 @end
121 @implementation MMWindowController
123 - (id)initWithVimController:(MMVimController *)controller
125 #ifndef NSAppKitVersionNumber10_4  // needed for non-10.5 sdk
126 # define NSAppKitVersionNumber10_4 824
127 #endif
128     unsigned styleMask = NSTitledWindowMask | NSClosableWindowMask
129             | NSMiniaturizableWindowMask | NSResizableWindowMask
130             | NSUnifiedTitleAndToolbarWindowMask;
132     // Use textured background on Leopard or later (skip the 'if' on Tiger for
133     // polished metal window).
134     if ([[NSUserDefaults standardUserDefaults] boolForKey:MMTexturedWindowKey]
135             || (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_4))
136         styleMask |= NSTexturedBackgroundWindowMask;
138     // NOTE: The content rect is only used the very first time MacVim is
139     // started (or rather, when ~/Library/Preferences/org.vim.MacVim.plist does
140     // not exist).  The chosen values will put the window somewhere near the
141     // top and in the middle of a 1024x768 screen.
142     MMWindow *win = [[MMWindow alloc]
143             initWithContentRect:NSMakeRect(242,364,480,360)
144                       styleMask:styleMask
145                         backing:NSBackingStoreBuffered
146                           defer:YES];
147     [win autorelease];
149     self = [super initWithWindow:win];
150     if (!self) return nil;
152     vimController = controller;
153     decoratedWindow = [win retain];
155     // Window cascading is handled by MMAppController.
156     [self setShouldCascadeWindows:NO];
158     // NOTE: Autoresizing is enabled for the content view, but only used
159     // for the tabline separator.  The vim view must be resized manually
160     // because of full-screen considerations, and because its size depends
161     // on whether the tabline separator is visible or not.
162     NSView *contentView = [win contentView];
163     [contentView setAutoresizesSubviews:YES];
165     vimView = [[MMVimView alloc] initWithFrame:[contentView frame]
166                                  vimController:vimController];
167     [vimView setAutoresizingMask:NSViewNotSizable];
168     [contentView addSubview:vimView];
170     [win setDelegate:self];
171     [win setInitialFirstResponder:[vimView textView]];
172     
173     if ([win styleMask] & NSTexturedBackgroundWindowMask) {
174         // On Leopard, we want to have a textured window to have nice
175         // looking tabs. But the textured window look implies rounded
176         // corners, which looks really weird -- disable them. This is a
177         // private api, though.
178         if ([win respondsToSelector:@selector(setBottomCornerRounded:)])
179             [win setBottomCornerRounded:NO];
181         // When the tab bar is toggled, it changes color for the fraction
182         // of a second, probably because vim sends us events in a strange
183         // order, confusing appkit's content border heuristic for a short
184         // while.  This can be worked around with these two methods.  There
185         // might be a better way, but it's good enough.
186         if ([win respondsToSelector:@selector(
187                 setAutorecalculatesContentBorderThickness:forEdge:)])
188             [win setAutorecalculatesContentBorderThickness:NO
189                                                    forEdge:NSMaxYEdge];
190         if ([win respondsToSelector:
191                 @selector(setContentBorderThickness:forEdge:)])
192             [win setContentBorderThickness:0 forEdge:NSMaxYEdge];
193     }
195     // Make us safe on pre-tiger OSX
196     if ([win respondsToSelector:@selector(_setContentHasShadow:)])
197         [win _setContentHasShadow:NO];
199     return self;
202 - (void)dealloc
204     LOG_DEALLOC
206     [decoratedWindow release];  decoratedWindow = nil;
207     [windowAutosaveKey release];  windowAutosaveKey = nil;
208     [vimView release];  vimView = nil;
210     [super dealloc];
213 - (NSString *)description
215     NSString *format =
216         @"%@ : setupDone=%d windowAutosaveKey=%@ vimController=%@";
217     return [NSString stringWithFormat:format,
218         [self className], setupDone, windowAutosaveKey, vimController];
221 - (MMVimController *)vimController
223     return vimController;
226 - (MMVimView *)vimView
228     return vimView;
231 - (NSString *)windowAutosaveKey
233     return windowAutosaveKey;
236 - (void)setWindowAutosaveKey:(NSString *)key
238     [windowAutosaveKey autorelease];
239     windowAutosaveKey = [key copy];
242 - (void)cleanup
244     //NSLog(@"%@ %s", [self className], _cmd);
246     if (fullscreenEnabled) {
247         // If we are closed while still in fullscreen, end fullscreen mode,
248         // release ourselves (because this won't happen in MMWindowController)
249         // and perform close operation on the original window.
250         [self leaveFullscreen];
251     }
253     setupDone = NO;
254     vimController = nil;
256     [vimView removeFromSuperviewWithoutNeedingDisplay];
257     [vimView cleanup];
259     // It is feasible (though unlikely) that the user quits before the window
260     // controller is released, make sure the edit flag is cleared so no warning
261     // dialog is displayed.
262     [decoratedWindow setDocumentEdited:NO];
264     [[self window] orderOut:self];
267 - (void)openWindow
269     // Indicates that the window is ready to be displayed, but do not display
270     // (or place) it yet -- that is done in showWindow.
272     [self addNewTabViewItem];
274     setupDone = YES;
276     [self updateResizeConstraints];
277     [self resizeWindowToFitContentSize:[vimView desiredSize]
278                           keepOnScreen:YES];
281 - (void)showWindow
283     // Actually show the window on screen.  However, if openWindow hasn't
284     // already been called nothing will happen (the window will be displayed
285     // later).
286     if (!setupDone) return;
288     [[MMAppController sharedInstance] windowControllerWillOpen:self];
289     [[self window] makeKeyAndOrderFront:self];
292 - (void)updateTabsWithData:(NSData *)data
294     [vimView updateTabsWithData:data];
297 - (void)selectTabWithIndex:(int)idx
299     [vimView selectTabWithIndex:idx];
302 - (void)setTextDimensionsWithRows:(int)rows columns:(int)cols isLive:(BOOL)live
303                           isReply:(BOOL)reply
305     //NSLog(@"setTextDimensionsWithRows:%d columns:%d isLive:%d isReply:%d",
306     //        rows, cols, live, reply);
308     // NOTE: The only place where the (rows,columns) of the vim view are
309     // modified is here and when entering/leaving full-screen.  Setting these
310     // values have no immediate effect, the actual resizing of the view is done
311     // in processCommandQueueDidFinish.
312     //
313     // The 'live' flag indicates that this resize originated from a live
314     // resize; it may very well happen that the view is no longer in live
315     // resize when this message is received.  We refrain from changing the view
316     // size when this flag is set, otherwise the window might jitter when the
317     // user drags to resize the window.
318     //
319     // The 'reply' flag indicates that this resize originated in MacVim and
320     // that Vim is now replying to that resize to make sure that it comes into
321     // effect.
323     [vimView setDesiredRows:rows columns:cols];
325     if (setupDone && !live) {
326         shouldResizeVimView = YES;
327         keepOnScreen = !reply;
328     }
331 - (void)setTitle:(NSString *)title
333     if (title)
334         [decoratedWindow setTitle:title];
337 - (void)setDocumentFilename:(NSString *)filename
339     if (!filename)
340         return;
342     // Ensure file really exists or the path to the proxy icon will look weird.
343     // If the file does not exists, don't show a proxy icon.
344     if (![[NSFileManager defaultManager] fileExistsAtPath:filename])
345         filename = @"";
347     [decoratedWindow setRepresentedFilename:filename];
350 - (void)setToolbar:(NSToolbar *)toolbar
352     // The full-screen window has no toolbar.
353     [decoratedWindow setToolbar:toolbar];
355     // HACK! Redirect the pill button so that we can ask Vim to hide the
356     // toolbar.
357     NSButton *pillButton = [decoratedWindow
358             standardWindowButton:NSWindowToolbarButton];
359     if (pillButton) {
360         [pillButton setAction:@selector(toggleToolbar:)];
361         [pillButton setTarget:self];
362     }
365 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
367     [vimView createScrollbarWithIdentifier:ident type:type];
370 - (BOOL)destroyScrollbarWithIdentifier:(long)ident
372     BOOL scrollbarHidden = [vimView destroyScrollbarWithIdentifier:ident];   
373     shouldResizeVimView = shouldResizeVimView || scrollbarHidden;
375     return scrollbarHidden;
378 - (BOOL)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
380     BOOL scrollbarToggled = [vimView showScrollbarWithIdentifier:ident
381                                                            state:visible];
382     shouldResizeVimView = shouldResizeVimView || scrollbarToggled;
384     return scrollbarToggled;
387 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
389     [vimView setScrollbarPosition:pos length:len identifier:ident];
392 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
393                     identifier:(long)ident
395     [vimView setScrollbarThumbValue:val proportion:prop identifier:ident];
398 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
400     // NOTE: This is called when the transparency changes so set the opacity
401     // flag on the window here (should be faster if the window is opaque).
402     BOOL isOpaque = [back alphaComponent] == 1.0f;
403     [decoratedWindow setOpaque:isOpaque];
404     if (fullscreenEnabled)
405         [fullscreenWindow setOpaque:isOpaque];
407     [vimView setDefaultColorsBackground:back foreground:fore];
410 - (void)setFont:(NSFont *)font
412     [[NSFontManager sharedFontManager] setSelectedFont:font isMultiple:NO];
413     [[vimView textView] setFont:font];
414     [self updateResizeConstraints];
417 - (void)setWideFont:(NSFont *)font
419     [[vimView textView] setWideFont:font];
422 - (void)processCommandQueueDidFinish
424     // IMPORTANT!  No synchronous DO calls are allowed in this method.  They
425     // may cause the command queue to get processed out of order.
427     // NOTE: Resizing is delayed until after all commands have been processed
428     // since it often happens that more than one command will cause a resize.
429     // If we were to immediately resize then the vim view size would jitter
430     // (e.g.  hiding/showing scrollbars often happens several time in one
431     // update).
432     // Also delay toggling the toolbar until after scrollbars otherwise
433     // problems arise when showing toolbar and scrollbar at the same time, i.e.
434     // on "set go+=rT".
436     if (shouldResizeVimView) {
437         shouldResizeVimView = NO;
439         NSSize contentSize = [vimView desiredSize];
440         contentSize = [self constrainContentSizeToScreenSize:contentSize];
441         contentSize = [vimView constrainRows:NULL columns:NULL
442                                       toSize:contentSize];
443         [vimView setFrameSize:contentSize];
445         if (fullscreenEnabled) {
446             [[fullscreenWindow contentView] setNeedsDisplay:YES];
447             [fullscreenWindow centerView];
448         } else {
449             [self resizeWindowToFitContentSize:contentSize
450                                   keepOnScreen:keepOnScreen];
451         }
453         keepOnScreen = NO;
454     }
456     if (shouldUpdateToolbar != 0) {
457         [self updateToolbar];
458         shouldUpdateToolbar = 0;
459     }
462 - (void)showTabBar:(BOOL)on
464     [[vimView tabBarControl] setHidden:!on];
466     // Showing the tabline may result in the tabline separator being hidden or
467     // shown; this does not apply to full-screen mode.
468     if (!on) {
469         NSToolbar *toolbar = [decoratedWindow toolbar]; 
470         if (([decoratedWindow styleMask] & NSTexturedBackgroundWindowMask)
471                 == 0) {
472             [self hideTablineSeparator:![toolbar isVisible]];
473         } else {
474             [self hideTablineSeparator:NO];
475         }
476     } else {
477         if (([decoratedWindow styleMask] & NSTexturedBackgroundWindowMask)
478                 == 0) {
479             [self hideTablineSeparator:on];
480         } else {
481             [self hideTablineSeparator:YES];
482         }
483     }
486 - (void)showToolbar:(BOOL)on size:(int)size mode:(int)mode
488     NSToolbar *toolbar = [decoratedWindow toolbar];
489     if (!toolbar) return;
491     [toolbar setSizeMode:size];
492     [toolbar setDisplayMode:mode];
494     // NOTE: If the window is not visible we must toggle the toolbar
495     // immediately, otherwise "set go-=T" in .gvimrc will lead to the toolbar
496     // showing its hide animation every time a new window is opened.  (See
497     // processCommandQueueDidFinish for the reason why we need to delay
498     // toggling the toolbar when the window is visible.)
499     if ([decoratedWindow isVisible]) {
500         shouldUpdateToolbar = on ? 1 : -1;
501     } else {
502         [self updateToolbar];
503     }
506 - (void)setMouseShape:(int)shape
508     [[vimView textView] setMouseShape:shape];
511 - (void)adjustLinespace:(int)linespace
513     if (vimView && [vimView textView]) {
514         [[vimView textView] setLinespace:(float)linespace];
515         shouldResizeVimView = YES;
516     }
519 - (void)liveResizeWillStart
521     if (!setupDone) return;
523     // Save the original title, if we haven't already.
524     if (lastSetTitle == nil) {
525         lastSetTitle = [[decoratedWindow title] retain];
526     }
528     // NOTE: During live resize Cocoa goes into "event tracking mode".  We have
529     // to add the backend connection to this mode in order for resize messages
530     // from Vim to reach MacVim.  We do not wish to always listen to requests
531     // in event tracking mode since then MacVim could receive DO messages at
532     // unexpected times (e.g. when a key equivalent is pressed and the menu bar
533     // momentarily lights up).
534     id proxy = [vimController backendProxy];
535     NSConnection *connection = [(NSDistantObject*)proxy connectionForProxy];
536     [connection addRequestMode:NSEventTrackingRunLoopMode];
539 - (void)liveResizeDidEnd
541     if (!setupDone) return;
543     // See comment above regarding event tracking mode.
544     id proxy = [vimController backendProxy];
545     NSConnection *connection = [(NSDistantObject*)proxy connectionForProxy];
546     [connection removeRequestMode:NSEventTrackingRunLoopMode];
548     // NOTE: During live resize messages from MacVim to Vim are often dropped
549     // (because too many messages are sent at once).  This may lead to
550     // inconsistent states between Vim and MacVim; to avoid this we send a
551     // synchronous resize message to Vim now (this is not fool-proof, but it
552     // does seem to work quite well).
554     int constrained[2];
555     NSSize textViewSize = [[vimView textView] frame].size;
556     [[vimView textView] constrainRows:&constrained[0] columns:&constrained[1]
557                                toSize:textViewSize];
559     //NSLog(@"End of live resize, notify Vim that text dimensions are %dx%d",
560     //       constrained[1], constrained[0]);
562     NSData *data = [NSData dataWithBytes:constrained length:2*sizeof(int)];
563     BOOL sendOk = [vimController sendMessageNow:SetTextDimensionsMsgID
564                                            data:data
565                                         timeout:.5];
567     if (!sendOk) {
568         // Sending of synchronous message failed.  Force the window size to
569         // match the last dimensions received from Vim, otherwise we end up
570         // with inconsistent states.
571         [self resizeWindowToFitContentSize:[vimView desiredSize]
572                               keepOnScreen:NO];
573     }
575     // If we saved the original title while resizing, restore it.
576     if (lastSetTitle != nil) {
577         [decoratedWindow setTitle:lastSetTitle];
578         [lastSetTitle release];
579         lastSetTitle = nil;
580     }
583 - (void)enterFullscreen:(int)fuoptions backgroundColor:(NSColor *)back
585     if (fullscreenEnabled) return;
587     fullscreenWindow = [[MMFullscreenWindow alloc]
588         initWithWindow:decoratedWindow view:vimView backgroundColor:back];
589     [fullscreenWindow enterFullscreen:fuoptions];    
590     [fullscreenWindow setDelegate:self];
591     fullscreenEnabled = YES;
593     // The resize handle disappears so the vim view needs to update the
594     // scrollbars.
595     shouldResizeVimView = YES;
598 - (void)leaveFullscreen
600     if (!fullscreenEnabled) return;
602     fullscreenEnabled = NO;
603     [fullscreenWindow leaveFullscreen];    
604     [fullscreenWindow release];
605     fullscreenWindow = nil;
607     // The vim view may be too large to fit the screen, so update it.
608     shouldResizeVimView = YES;
611 - (void)setFullscreenBackgroundColor:(NSColor *)back
613     if (fullscreenEnabled)
614         [fullscreenWindow setBackgroundColor:back];
617 - (void)setBuffersModified:(BOOL)mod
619     // NOTE: We only set the document edited flag on the decorated window since
620     // the full-screen window has no close button anyway.  (It also saves us
621     // from keeping track of the flag in two different places.)
622     [decoratedWindow setDocumentEdited:mod];
626 - (IBAction)addNewTab:(id)sender
628     [vimView addNewTab:sender];
631 - (IBAction)toggleToolbar:(id)sender
633     [vimController sendMessage:ToggleToolbarMsgID data:nil];
636 - (IBAction)performClose:(id)sender
638     // NOTE: With the introduction of :macmenu it is possible to bind
639     // File.Close to ":conf q" but at the same time have it send off the
640     // performClose: action.  For this reason we no longer need the CloseMsgID
641     // message.  However, we still need File.Close to send performClose:
642     // otherwise Cmd-w will not work on dialogs.
643     [self vimMenuItemAction:sender];
646 - (IBAction)findNext:(id)sender
648     [self doFindNext:YES];
651 - (IBAction)findPrevious:(id)sender
653     [self doFindNext:NO];
656 - (IBAction)vimMenuItemAction:(id)sender
658     if (![sender isKindOfClass:[NSMenuItem class]]) return;
660     // TODO: Make into category on NSMenuItem which returns descriptor.
661     NSMenuItem *item = (NSMenuItem*)sender;
662     NSMutableArray *desc = [NSMutableArray arrayWithObject:[item title]];
664     NSMenu *menu = [item menu];
665     while (menu) {
666         [desc insertObject:[menu title] atIndex:0];
667         menu = [menu supermenu];
668     }
670     // The "MainMenu" item is part of the Cocoa menu and should not be part of
671     // the descriptor.
672     if ([[desc objectAtIndex:0] isEqual:@"MainMenu"])
673         [desc removeObjectAtIndex:0];
675     NSDictionary *attrs = [NSDictionary dictionaryWithObject:desc
676                                                       forKey:@"descriptor"];
677     [vimController sendMessage:ExecuteMenuMsgID data:[attrs dictionaryAsData]];
680 - (IBAction)vimToolbarItemAction:(id)sender
682     NSArray *desc = [NSArray arrayWithObjects:@"ToolBar", [sender label], nil];
683     NSDictionary *attrs = [NSDictionary dictionaryWithObject:desc
684                                                       forKey:@"descriptor"];
685     [vimController sendMessage:ExecuteMenuMsgID data:[attrs dictionaryAsData]];
688 - (IBAction)fontSizeUp:(id)sender
690     [[NSFontManager sharedFontManager] modifyFont:
691             [NSNumber numberWithInt:NSSizeUpFontAction]];
694 - (IBAction)fontSizeDown:(id)sender
696     [[NSFontManager sharedFontManager] modifyFont:
697             [NSNumber numberWithInt:NSSizeDownFontAction]];
700 - (IBAction)findAndReplace:(id)sender
702     int tag = [sender tag];
703     MMFindReplaceController *fr = [MMFindReplaceController sharedInstance];
704     int flags = 0;
706     // NOTE: The 'flags' values must match the FRD_ defines in gui.h (except
707     // for 0x100 which we use to indicate a backward search).
708     switch (tag) {
709         case 1: flags = 0x100; break;
710         case 2: flags = 3; break;
711         case 3: flags = 4; break;
712     }
714     if ([fr matchWord])
715         flags |= 0x08;
716     if (![fr ignoreCase])
717         flags |= 0x10;
719     NSDictionary *args = [NSDictionary dictionaryWithObjectsAndKeys:
720             [fr findString],                @"find",
721             [fr replaceString],             @"replace",
722             [NSNumber numberWithInt:flags], @"flags",
723             nil];
725     [vimController sendMessage:FindReplaceMsgID data:[args dictionaryAsData]];
728 - (BOOL)validateMenuItem:(NSMenuItem *)item
730     if ([item action] == @selector(vimMenuItemAction:)
731             || [item action] == @selector(performClose:))
732         return [item tag];
734     return YES;
737 // -- NSWindow delegate ------------------------------------------------------
739 - (void)windowDidBecomeMain:(NSNotification *)notification
741     [[MMAppController sharedInstance] setMainMenu:[vimController mainMenu]];
742     [vimController sendMessage:GotFocusMsgID data:nil];
744     if ([vimView textView]) {
745         NSFontManager *fm = [NSFontManager sharedFontManager];
746         [fm setSelectedFont:[[vimView textView] font] isMultiple:NO];
747     }
750 - (void)windowDidResignMain:(NSNotification *)notification
752     [vimController sendMessage:LostFocusMsgID data:nil];
755 - (BOOL)windowShouldClose:(id)sender
757     // Don't close the window now; Instead let Vim decide whether to close the
758     // window or not.
759     [vimController sendMessage:VimShouldCloseMsgID data:nil];
760     return NO;
763 - (void)windowDidMove:(NSNotification *)notification
765     if (!setupDone)
766         return;
768     if (windowAutosaveKey) {
769         NSRect frame = [decoratedWindow frame];
770         NSPoint topLeft = { frame.origin.x, NSMaxY(frame) };
771         NSString *topLeftString = NSStringFromPoint(topLeft);
773         [[NSUserDefaults standardUserDefaults]
774             setObject:topLeftString forKey:windowAutosaveKey];
775     }
778 - (NSSize)windowWillResize:(NSWindow *)win toSize:(NSSize)proposedFrameSize
780     // Make sure the window isn't resized to be larger than the "visible frame"
781     // for the current screen.  Do this here instead of setting the window max
782     // size in updateResizeConstraints since the screen's visible frame may
783     // change at any time (dock could move, resolution could change, window
784     // could be moved to another screen, ...).
785     if (![win screen])
786         return proposedFrameSize;
788     // NOTE: Not called in full-screen mode so use "visibleFrame" instead of
789     // "frame".
790     NSRect maxFrame = [self constrainFrame:[[win screen] visibleFrame]];
792     if (proposedFrameSize.width > maxFrame.size.width)
793         proposedFrameSize.width = maxFrame.size.width;
794     if (proposedFrameSize.height > maxFrame.size.height)
795         proposedFrameSize.height = maxFrame.size.height;
797     return proposedFrameSize;
800 - (void)windowDidResize:(id)sender
802     if (!setupDone || fullscreenEnabled) return;
804     // NOTE: Since we have no control over when the window may resize (Cocoa
805     // may resize automatically) we simply set the view to fill the entire
806     // window.  The vim view takes care of notifying Vim if the number of
807     // (rows,columns) changed.
808     [vimView setFrameSize:[self contentSize]];
811 - (NSRect)windowWillUseStandardFrame:(NSWindow *)win
812                         defaultFrame:(NSRect)frame
814     // By default the window is maximized in the vertical direction only.
815     // Holding down the Cmd key maximizes the window in the horizontal
816     // direction.  If the MMZoomBoth user default is set, then the window
817     // maximizes in both directions by default, unless the Cmd key is held in
818     // which case the window only maximizes in the vertical direction.
820     NSEvent *event = [NSApp currentEvent];
821     BOOL cmdLeftClick = [event type] == NSLeftMouseUp
822             && [event modifierFlags] & NSCommandKeyMask;
823     BOOL zoomBoth = [[NSUserDefaults standardUserDefaults]
824             boolForKey:MMZoomBothKey];
826     // The "default frame" represents the maximal size of a zoomed window.
827     // Constrain this frame so that the content fits an even number of rows and
828     // columns.
829     frame = [self constrainFrame:frame];
831     if (!((zoomBoth && !cmdLeftClick) || (!zoomBoth && cmdLeftClick))) {
832         // Zoom in horizontal direction only.
833         NSRect currentFrame = [win frame];
834         frame.size.width = currentFrame.size.width;
835         frame.origin.x = currentFrame.origin.x;
836     }
838     return frame;
844 // -- Services menu delegate -------------------------------------------------
846 - (id)validRequestorForSendType:(NSString *)sendType
847                      returnType:(NSString *)returnType
849     if ([sendType isEqual:NSStringPboardType]
850             && [self askBackendForStarRegister:nil])
851         return self;
853     return [super validRequestorForSendType:sendType returnType:returnType];
856 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
857                              types:(NSArray *)types
859     if (![types containsObject:NSStringPboardType])
860         return NO;
862     return [self askBackendForStarRegister:pboard];
865 - (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pboard
867     // Replace the current selection with the text on the pasteboard.
868     NSArray *types = [pboard types];
869     if ([types containsObject:NSStringPboardType]) {
870         NSString *input = [NSString stringWithFormat:@"s%@",
871                  [pboard stringForType:NSStringPboardType]];
872         [vimController addVimInput:input];
873         return YES;
874     }
876     return NO;
879 @end // MMWindowController
883 @implementation MMWindowController (Private)
885 - (NSSize)contentSize
887     // NOTE: Never query the content view directly for its size since it may
888     // not return the same size as contentRectForFrameRect: (e.g. when in
889     // windowed mode and the tabline separator is visible)!
890     NSWindow *win = [self window];
891     return [win contentRectForFrameRect:[win frame]].size;
894 - (void)resizeWindowToFitContentSize:(NSSize)contentSize
895                         keepOnScreen:(BOOL)onScreen
897     NSRect frame = [decoratedWindow frame];
898     NSRect contentRect = [decoratedWindow contentRectForFrameRect:frame];
900     // Keep top-left corner of the window fixed when resizing.
901     contentRect.origin.y -= contentSize.height - contentRect.size.height;
902     contentRect.size = contentSize;
904     NSRect newFrame = [decoratedWindow frameRectForContentRect:contentRect];
906     if ([decoratedWindow screen]) {
907         // Ensure that the window fits inside the visible part of the screen.
908         // NOTE: Not called in full-screen mode so use "visibleFrame' instead
909         // of "frame".
910         NSRect maxFrame = [[decoratedWindow screen] visibleFrame];
911         maxFrame = [self constrainFrame:maxFrame];
913         if (newFrame.size.width > maxFrame.size.width) {
914             newFrame.size.width = maxFrame.size.width;
915             newFrame.origin.x = maxFrame.origin.x;
916         }
917         if (newFrame.size.height > maxFrame.size.height) {
918             newFrame.size.height = maxFrame.size.height;
919             newFrame.origin.y = maxFrame.origin.y;
920         }
922         if (onScreen) {
923             if (newFrame.origin.y < maxFrame.origin.y)
924                 newFrame.origin.y = maxFrame.origin.y;
925             if (NSMaxX(newFrame) > NSMaxX(maxFrame))
926                 newFrame.origin.x = NSMaxX(maxFrame) - newFrame.size.width;
927         }
928     }
930     [decoratedWindow setFrame:newFrame display:YES];
933 - (NSSize)constrainContentSizeToScreenSize:(NSSize)contentSize
935     NSWindow *win = [self window];
936     if (![win screen])
937         return contentSize;
939     // NOTE: This may be called in both windowed and full-screen mode.  The
940     // "visibleFrame" method does not overlap menu and dock so should not be
941     // used in full-screen.
942     NSRect screenRect = fullscreenEnabled ? [[win screen] frame]
943                                           : [[win screen] visibleFrame];
944     NSRect rect = [win contentRectForFrameRect:screenRect];
946     if (contentSize.height > rect.size.height)
947         contentSize.height = rect.size.height;
948     if (contentSize.width > rect.size.width)
949         contentSize.width = rect.size.width;
951     return contentSize;
954 - (NSRect)constrainFrame:(NSRect)frame
956     // Constrain the given (window) frame so that it fits an even number of
957     // rows and columns.
958     NSRect contentRect = [decoratedWindow contentRectForFrameRect:frame];
959     NSSize constrainedSize = [vimView constrainRows:NULL
960                                             columns:NULL
961                                              toSize:contentRect.size];
963     contentRect.origin.y += contentRect.size.height - constrainedSize.height;
964     contentRect.size = constrainedSize;
966     return [decoratedWindow frameRectForContentRect:contentRect];
969 - (void)updateResizeConstraints
971     if (!setupDone) return;
973     // Set the resize increments to exactly match the font size; this way the
974     // window will always hold an integer number of (rows,columns).
975     NSSize cellSize = [[vimView textView] cellSize];
976     [decoratedWindow setContentResizeIncrements:cellSize];
978     NSSize minSize = [vimView minSize];
979     [decoratedWindow setContentMinSize:minSize];
982 - (NSTabViewItem *)addNewTabViewItem
984     return [vimView addNewTabViewItem];
987 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb
989     // TODO: Can this be done with evaluateExpression: instead?
990     BOOL reply = NO;
991     id backendProxy = [vimController backendProxy];
993     if (backendProxy) {
994         @try {
995             reply = [backendProxy starRegisterToPasteboard:pb];
996         }
997         @catch (NSException *e) {
998             NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
999         }
1000     }
1002     return reply;
1005 - (void)hideTablineSeparator:(BOOL)hide
1007     // The full-screen window has no tabline separator so we operate on
1008     // decoratedWindow instead of [self window].
1009     if ([decoratedWindow hideTablineSeparator:hide]) {
1010         // The tabline separator was toggled so the content view must change
1011         // size.
1012         [self updateResizeConstraints];
1013         shouldResizeVimView = YES;
1014     }
1017 - (void)doFindNext:(BOOL)next
1019     NSString *query = nil;
1021 #if 0
1022     // Use current query if the search field is selected.
1023     id searchField = [[self searchFieldItem] view];
1024     if (searchField && [[searchField stringValue] length] > 0 &&
1025             [decoratedWindow firstResponder] == [searchField currentEditor])
1026         query = [searchField stringValue];
1027 #endif
1029     if (!query) {
1030         // Use find pasteboard for next query.
1031         NSPasteboard *pb = [NSPasteboard pasteboardWithName:NSFindPboard];
1032         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1033         if ([pb availableTypeFromArray:types])
1034             query = [pb stringForType:NSStringPboardType];
1035     }
1037     NSString *input = nil;
1038     if (query) {
1039         // NOTE: The '/' register holds the last search string.  By setting it
1040         // (using the '@/' syntax) we fool Vim into thinking that it has
1041         // already searched for that string and then we can simply use 'n' or
1042         // 'N' to find the next/previous match.
1043         input = [NSString stringWithFormat:@"<C-\\><C-N>:let @/='%@'<CR>%c",
1044                 query, next ? 'n' : 'N'];
1045     } else {
1046         input = next ? @"<C-\\><C-N>n" : @"<C-\\><C-N>N"; 
1047     }
1049     [vimController addVimInput:input];
1052 - (void)updateToolbar
1054     NSToolbar *toolbar = [decoratedWindow toolbar];
1055     if (!toolbar) return;
1057     BOOL on = shouldUpdateToolbar > 0 ? YES : NO;
1058     [toolbar setVisible:on];
1060     if (([decoratedWindow styleMask] & NSTexturedBackgroundWindowMask) == 0) {
1061         if (!on) {
1062             [self hideTablineSeparator:YES];
1063         } else {
1064             [self hideTablineSeparator:![[vimView tabBarControl] isHidden]];
1065         }
1066     } else {
1067         // Textured windows don't have a line below there title bar, so we
1068         // need the separator in this case as well. In fact, the only case
1069         // where we don't need the separator is when the tab bar control
1070         // is visible (because it brings its own separator).
1071         [self hideTablineSeparator:![[vimView tabBarControl] isHidden]];
1072     }
1075 @end // MMWindowController (Private)