Fix problems with 'fullscreen' and :mksession
[MacVim.git] / src / MacVim / MMWindowController.m
blob1b44a5d7def4b2921591bf233346e9b10ec6cdd2
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 "MMFullscreenWindow.h"
61 #import "MMTextView.h"
62 #import "MMTypesetter.h"
63 #import "MMVimController.h"
64 #import "MMVimView.h"
65 #import "MMWindow.h"
66 #import "MMWindowController.h"
67 #import "Miscellaneous.h"
68 #import <PSMTabBarControl.h>
72 @interface MMWindowController (Private)
73 - (NSSize)contentSize;
74 - (void)resizeWindowToFitContentSize:(NSSize)contentSize;
75 - (NSSize)constrainContentSizeToScreenSize:(NSSize)contentSize;
76 - (void)updateResizeConstraints;
77 - (NSTabViewItem *)addNewTabViewItem;
78 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb;
79 - (void)hideTablineSeparator:(BOOL)hide;
80 - (void)doFindNext:(BOOL)next;
81 @end
84 @interface NSWindow (NSWindowPrivate)
85 // Note: This hack allows us to set content shadowing separately from
86 // the window shadow.  This is apparently what webkit and terminal do.
87 - (void)_setContentHasShadow:(BOOL)shadow; // new Tiger private method
89 // This is a private api that makes textured windows not have rounded corners.
90 // We want this on Leopard.
91 - (void)setBottomCornerRounded:(BOOL)rounded;
92 @end
95 @interface NSWindow (NSLeopardOnly)
96 // Note: These functions are Leopard-only, use -[NSObject respondsToSelector:]
97 // before calling them to make sure everything works on Tiger too.
99 #ifndef CGFLOAT_DEFINED
100     // On Leopard, CGFloat is float on 32bit and double on 64bit. On Tiger,
101     // we can't use this anyways, so it's just here to keep the compiler happy.
102     // However, when we're compiling for Tiger and running on Leopard, we
103     // might need the correct typedef, so this piece is copied from ATSTypes.h
104 # ifdef __LP64__
105     typedef double CGFloat;
106 # else
107     typedef float CGFloat;
108 # endif
109 #endif
110 - (void)setAutorecalculatesContentBorderThickness:(BOOL)b forEdge:(NSRectEdge)e;
111 - (void)setContentBorderThickness:(CGFloat)b forEdge:(NSRectEdge)e;
112 @end
117 @implementation MMWindowController
119 - (id)initWithVimController:(MMVimController *)controller
121 #ifndef NSAppKitVersionNumber10_4  // needed for non-10.5 sdk
122 # define NSAppKitVersionNumber10_4 824
123 #endif
124     unsigned styleMask = NSTitledWindowMask | NSClosableWindowMask
125             | NSMiniaturizableWindowMask | NSResizableWindowMask
126             | NSUnifiedTitleAndToolbarWindowMask;
128     // Use textured background on Leopard or later (skip the 'if' on Tiger for
129     // polished metal window).
130     if ([[NSUserDefaults standardUserDefaults] boolForKey:MMTexturedWindowKey]
131             || (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_4))
132         styleMask |= NSTexturedBackgroundWindowMask;
134     // NOTE: The content rect is only used the very first time MacVim is
135     // started (or rather, when ~/Library/Preferences/org.vim.MacVim.plist does
136     // not exist).  The chosen values will put the window somewhere near the
137     // top and in the middle of a 1024x768 screen.
138     MMWindow *win = [[MMWindow alloc]
139             initWithContentRect:NSMakeRect(242,364,480,360)
140                       styleMask:styleMask
141                         backing:NSBackingStoreBuffered
142                           defer:YES];
143     [win autorelease];
145     self = [super initWithWindow:win];
146     if (!self) return nil;
148     vimController = controller;
149     decoratedWindow = [win retain];
151     // Window cascading is handled by MMAppController.
152     [self setShouldCascadeWindows:NO];
154     // NOTE: Autoresizing is enabled for the content view, but only used
155     // for the tabline separator.  The vim view must be resized manually
156     // because of full-screen considerations, and because its size depends
157     // on whether the tabline separator is visible or not.
158     NSView *contentView = [win contentView];
159     [contentView setAutoresizesSubviews:YES];
161     vimView = [[MMVimView alloc] initWithFrame:[contentView frame]
162                                  vimController:vimController];
163     [vimView setAutoresizingMask:NSViewNotSizable];
164     [contentView addSubview:vimView];
166     [win setDelegate:self];
167     [win setInitialFirstResponder:[vimView textView]];
168     
169     if ([win styleMask] & NSTexturedBackgroundWindowMask) {
170         // On Leopard, we want to have a textured window to have nice
171         // looking tabs. But the textured window look implies rounded
172         // corners, which looks really weird -- disable them. This is a
173         // private api, though.
174         if ([win respondsToSelector:@selector(setBottomCornerRounded:)])
175             [win setBottomCornerRounded:NO];
177         // When the tab bar is toggled, it changes color for the fraction
178         // of a second, probably because vim sends us events in a strange
179         // order, confusing appkit's content border heuristic for a short
180         // while.  This can be worked around with these two methods.  There
181         // might be a better way, but it's good enough.
182         if ([win respondsToSelector:@selector(
183                 setAutorecalculatesContentBorderThickness:forEdge:)])
184             [win setAutorecalculatesContentBorderThickness:NO
185                                                    forEdge:NSMaxYEdge];
186         if ([win respondsToSelector:
187                 @selector(setContentBorderThickness:forEdge:)])
188             [win setContentBorderThickness:0 forEdge:NSMaxYEdge];
189     }
191     // Make us safe on pre-tiger OSX
192     if ([win respondsToSelector:@selector(_setContentHasShadow:)])
193         [win _setContentHasShadow:NO];
195     return self;
198 - (void)dealloc
200     LOG_DEALLOC
202     [decoratedWindow release];  decoratedWindow = nil;
203     [windowAutosaveKey release];  windowAutosaveKey = nil;
204     [vimView release];  vimView = nil;
206     [super dealloc];
209 - (NSString *)description
211     NSString *format =
212         @"%@ : setupDone=%d windowAutosaveKey=%@ vimController=%@";
213     return [NSString stringWithFormat:format,
214         [self className], setupDone, windowAutosaveKey, vimController];
217 - (MMVimController *)vimController
219     return vimController;
222 - (MMVimView *)vimView
224     return vimView;
227 - (NSString *)windowAutosaveKey
229     return windowAutosaveKey;
232 - (void)setWindowAutosaveKey:(NSString *)key
234     [windowAutosaveKey autorelease];
235     windowAutosaveKey = [key copy];
238 - (void)cleanup
240     //NSLog(@"%@ %s", [self className], _cmd);
242     if (fullscreenEnabled) {
243         // If we are closed while still in fullscreen, end fullscreen mode,
244         // release ourselves (because this won't happen in MMWindowController)
245         // and perform close operation on the original window.
246         [self leaveFullscreen];
247     }
249     setupDone = NO;
250     vimController = nil;
252     [vimView removeFromSuperviewWithoutNeedingDisplay];
253     [vimView cleanup];
255     // It is feasible (though unlikely) that the user quits before the window
256     // controller is released, make sure the edit flag is cleared so no warning
257     // dialog is displayed.
258     [decoratedWindow setDocumentEdited:NO];
260     [[self window] orderOut:self];
263 - (void)openWindow
265     // Indicates that the window is ready to be displayed, but do not display
266     // (or place) it yet -- that is done in showWindow.
268     [self addNewTabViewItem];
270     setupDone = YES;
272     [self updateResizeConstraints];
273     [self resizeWindowToFitContentSize:[vimView desiredSize]];
276 - (void)showWindow
278     // Actually show the window on screen.  However, if openWindow hasn't
279     // already been called nothing will happen (the window will be displayed
280     // later).
281     if (!setupDone) return;
283     [[MMAppController sharedInstance] windowControllerWillOpen:self];
284     [[self window] makeKeyAndOrderFront:self];
287 - (void)updateTabsWithData:(NSData *)data
289     [vimView updateTabsWithData:data];
292 - (void)selectTabWithIndex:(int)idx
294     [vimView selectTabWithIndex:idx];
297 - (void)setTextDimensionsWithRows:(int)rows columns:(int)cols live:(BOOL)live
299     //NSLog(@"setTextDimensionsWithRows:%d columns:%d live:%s", rows, cols,
300     //        live ? "YES" : "NO");
302     // NOTE: The only place where the (rows,columns) of the vim view are
303     // modified is here and when entering/leaving full-screen.  Setting these
304     // values have no immediate effect, the actual resizing of the view is done
305     // in processCommandQueueDidFinish.
306     //
307     // The 'live' flag indicates that this resize originated from a live
308     // resize; it may very well happen that the view is no longer in live
309     // resize when this message is received.  We refrain from changing the view
310     // size when this flag is set, otherwise the window might jitter when the
311     // user drags to resize the window.
313     [vimView setDesiredRows:rows columns:cols];
315     if (setupDone && !live)
316         shouldResizeVimView = YES;
319 - (void)setTitle:(NSString *)title
321     if (title)
322         [decoratedWindow setTitle:title];
325 - (void)setDocumentFilename:(NSString *)filename
327     if (!filename)
328         return;
330     // Ensure file really exists or the path to the proxy icon will look weird.
331     // If the file does not exists, don't show a proxy icon.
332     if (![[NSFileManager defaultManager] fileExistsAtPath:filename])
333         filename = @"";
335     [decoratedWindow setRepresentedFilename:filename];
338 - (void)setToolbar:(NSToolbar *)toolbar
340     // The full-screen window has no toolbar.
341     [decoratedWindow setToolbar:toolbar];
343     // HACK! Redirect the pill button so that we can ask Vim to hide the
344     // toolbar.
345     NSButton *pillButton = [decoratedWindow
346             standardWindowButton:NSWindowToolbarButton];
347     if (pillButton) {
348         [pillButton setAction:@selector(toggleToolbar:)];
349         [pillButton setTarget:self];
350     }
353 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
355     [vimView createScrollbarWithIdentifier:ident type:type];
358 - (BOOL)destroyScrollbarWithIdentifier:(long)ident
360     BOOL scrollbarHidden = [vimView destroyScrollbarWithIdentifier:ident];   
361     shouldResizeVimView = shouldResizeVimView || scrollbarHidden;
363     return scrollbarHidden;
366 - (BOOL)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
368     BOOL scrollbarToggled = [vimView showScrollbarWithIdentifier:ident
369                                                            state:visible];
370     shouldResizeVimView = shouldResizeVimView || scrollbarToggled;
372     return scrollbarToggled;
375 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
377     [vimView setScrollbarPosition:pos length:len identifier:ident];
380 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
381                     identifier:(long)ident
383     [vimView setScrollbarThumbValue:val proportion:prop identifier:ident];
386 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
388     // NOTE: This is called when the transparency changes so set the opacity
389     // flag on the window here (should be faster if the window is opaque).
390     BOOL isOpaque = [back alphaComponent] == 1.0f;
391     [decoratedWindow setOpaque:isOpaque];
392     if (fullscreenEnabled)
393         [fullscreenWindow setOpaque:isOpaque];
395     [vimView setDefaultColorsBackground:back foreground:fore];
398 - (void)setFont:(NSFont *)font
400     [[NSFontManager sharedFontManager] setSelectedFont:font isMultiple:NO];
401     [[vimView textView] setFont:font];
402     [self updateResizeConstraints];
405 - (void)setWideFont:(NSFont *)font
407     [[vimView textView] setWideFont:font];
410 - (void)processCommandQueueDidFinish
412     // IMPORTANT!  No synchronous DO calls are allowed in this method.  They
413     // may cause the command queue to get processed out of order.
415     // NOTE: Resizing is delayed until after all commands have been processed
416     // since it often happens that more than one command will cause a resize.
417     // If we were to immediately resize then the vim view size would jitter
418     // (e.g.  hiding/showing scrollbars often happens several time in one
419     // update).
421     if (shouldResizeVimView) {
422         shouldResizeVimView = NO;
424         NSSize contentSize = [vimView desiredSize];
425         contentSize = [self constrainContentSizeToScreenSize:contentSize];
426         contentSize = [vimView constrainRows:NULL columns:NULL
427                                       toSize:contentSize];
428         [vimView setFrameSize:contentSize];
430         if (fullscreenEnabled) {
431             [[fullscreenWindow contentView] setNeedsDisplay:YES];
432             [fullscreenWindow centerView];
433         } else {
434             [self resizeWindowToFitContentSize:contentSize];
435         }
436     }
439 - (void)showTabBar:(BOOL)on
441     [[vimView tabBarControl] setHidden:!on];
443     // Showing the tabline may result in the tabline separator being hidden or
444     // shown; this does not apply to full-screen mode.
445     if (!on) {
446         NSToolbar *toolbar = [decoratedWindow toolbar]; 
447         if (([decoratedWindow styleMask] & NSTexturedBackgroundWindowMask)
448                 == 0) {
449             [self hideTablineSeparator:![toolbar isVisible]];
450         } else {
451             [self hideTablineSeparator:NO];
452         }
453     } else {
454         if (([decoratedWindow styleMask] & NSTexturedBackgroundWindowMask)
455                 == 0) {
456             [self hideTablineSeparator:on];
457         } else {
458             [self hideTablineSeparator:YES];
459         }
460     }
463 - (void)showToolbar:(BOOL)on size:(int)size mode:(int)mode
465     NSToolbar *toolbar = [decoratedWindow toolbar];
466     if (!toolbar) return;
468     [toolbar setSizeMode:size];
469     [toolbar setDisplayMode:mode];
470     [toolbar setVisible:on];
472     if (([decoratedWindow styleMask] & NSTexturedBackgroundWindowMask) == 0) {
473         if (!on) {
474             [self hideTablineSeparator:YES];
475         } else {
476             [self hideTablineSeparator:![[vimView tabBarControl] isHidden]];
477         }
478     } else {
479         // Textured windows don't have a line below there title bar, so we
480         // need the separator in this case as well. In fact, the only case
481         // where we don't need the separator is when the tab bar control
482         // is visible (because it brings its own separator).
483         [self hideTablineSeparator:![[vimView tabBarControl] isHidden]];
484     }
487 - (void)setMouseShape:(int)shape
489     [[vimView textView] setMouseShape:shape];
492 - (void)adjustLinespace:(int)linespace
494     if (vimView && [vimView textView]) {
495         [[vimView textView] setLinespace:(float)linespace];
496         shouldResizeVimView = YES;
497     }
500 - (void)liveResizeWillStart
502     // Save the original title, if we haven't already.
503     if (lastSetTitle == nil) {
504         lastSetTitle = [[decoratedWindow title] retain];
505     }
508 - (void)liveResizeDidEnd
510     if (!setupDone) return;
512     // NOTE: During live resize messages from MacVim to Vim are often dropped
513     // (because too many messages are sent at once).  This may lead to
514     // inconsistent states between Vim and MacVim; to avoid this we send a
515     // synchronous resize message to Vim now (this is not fool-proof, but it
516     // does seem to work quite well).
518     int constrained[2];
519     NSSize textViewSize = [[vimView textView] frame].size;
520     [[vimView textView] constrainRows:&constrained[0] columns:&constrained[1]
521                                toSize:textViewSize];
523     //NSLog(@"End of live resize, notify Vim that text dimensions are %dx%d",
524     //       constrained[1], constrained[0]);
526     NSData *data = [NSData dataWithBytes:constrained length:2*sizeof(int)];
527     BOOL sendOk = [vimController sendMessageNow:SetTextDimensionsMsgID
528                                            data:data
529                                         timeout:.5];
531     if (!sendOk) {
532         // Sending of synchronous message failed.  Force the window size to
533         // match the last dimensions received from Vim, otherwise we end up
534         // with inconsistent states.
535         [self resizeWindowToFitContentSize:[vimView desiredSize]];
536     }
538     // If we saved the original title while resizing, restore it.
539     if (lastSetTitle != nil) {
540         [decoratedWindow setTitle:lastSetTitle];
541         [lastSetTitle release];
542         lastSetTitle = nil;
543     }
546 - (void)enterFullscreen:(int)fuoptions backgroundColor:(NSColor *)back
548     if (fullscreenEnabled) return;
550     fullscreenWindow = [[MMFullscreenWindow alloc]
551         initWithWindow:decoratedWindow view:vimView backgroundColor:back];
552     [fullscreenWindow enterFullscreen:fuoptions];    
553     [fullscreenWindow setDelegate:self];
554     fullscreenEnabled = YES;
556     // The resize handle disappears so the vim view needs to update the
557     // scrollbars.
558     shouldResizeVimView = YES;
561 - (void)leaveFullscreen
563     if (!fullscreenEnabled) return;
565     fullscreenEnabled = NO;
566     [fullscreenWindow leaveFullscreen];    
567     [fullscreenWindow release];
568     fullscreenWindow = nil;
570     // The vim view may be too large to fit the screen, so update it.
571     shouldResizeVimView = YES;
574 - (void)setBuffersModified:(BOOL)mod
576     // NOTE: We only set the document edited flag on the decorated window since
577     // the full-screen window has no close button anyway.  (It also saves us
578     // from keeping track of the flag in two different places.)
579     [decoratedWindow setDocumentEdited:mod];
583 - (IBAction)addNewTab:(id)sender
585     [vimView addNewTab:sender];
588 - (IBAction)toggleToolbar:(id)sender
590     [vimController sendMessage:ToggleToolbarMsgID data:nil];
593 - (IBAction)performClose:(id)sender
595     // NOTE: With the introduction of :macmenu it is possible to bind
596     // File.Close to ":conf q" but at the same time have it send off the
597     // performClose: action.  For this reason we no longer need the CloseMsgID
598     // message.  However, we still need File.Close to send performClose:
599     // otherwise Cmd-w will not work on dialogs.
600     [self vimMenuItemAction:sender];
603 - (IBAction)findNext:(id)sender
605     [self doFindNext:YES];
608 - (IBAction)findPrevious:(id)sender
610     [self doFindNext:NO];
613 - (IBAction)vimMenuItemAction:(id)sender
615     if (![sender isKindOfClass:[NSMenuItem class]]) return;
617     // TODO: Make into category on NSMenuItem which returns descriptor.
618     NSMenuItem *item = (NSMenuItem*)sender;
619     NSMutableArray *desc = [NSMutableArray arrayWithObject:[item title]];
621     NSMenu *menu = [item menu];
622     while (menu) {
623         [desc insertObject:[menu title] atIndex:0];
624         menu = [menu supermenu];
625     }
627     // The "MainMenu" item is part of the Cocoa menu and should not be part of
628     // the descriptor.
629     if ([[desc objectAtIndex:0] isEqual:@"MainMenu"])
630         [desc removeObjectAtIndex:0];
632     NSDictionary *attrs = [NSDictionary dictionaryWithObject:desc
633                                                       forKey:@"descriptor"];
634     [vimController sendMessage:ExecuteMenuMsgID data:[attrs dictionaryAsData]];
637 - (IBAction)vimToolbarItemAction:(id)sender
639     NSArray *desc = [NSArray arrayWithObjects:@"ToolBar", [sender label], nil];
640     NSDictionary *attrs = [NSDictionary dictionaryWithObject:desc
641                                                       forKey:@"descriptor"];
642     [vimController sendMessage:ExecuteMenuMsgID data:[attrs dictionaryAsData]];
645 - (IBAction)fontSizeUp:(id)sender
647     [[NSFontManager sharedFontManager] modifyFont:
648             [NSNumber numberWithInt:NSSizeUpFontAction]];
651 - (IBAction)fontSizeDown:(id)sender
653     [[NSFontManager sharedFontManager] modifyFont:
654             [NSNumber numberWithInt:NSSizeDownFontAction]];
657 - (BOOL)validateMenuItem:(NSMenuItem *)item
659     if ([item action] == @selector(vimMenuItemAction:)
660             || [item action] == @selector(performClose:))
661         return [item tag];
663     return YES;
666 // -- NSWindow delegate ------------------------------------------------------
668 - (void)windowDidBecomeMain:(NSNotification *)notification
670     [[MMAppController sharedInstance] setMainMenu:[vimController mainMenu]];
671     [vimController sendMessage:GotFocusMsgID data:nil];
673     if ([vimView textView]) {
674         NSFontManager *fm = [NSFontManager sharedFontManager];
675         [fm setSelectedFont:[[vimView textView] font] isMultiple:NO];
676     }
679 - (void)windowDidResignMain:(NSNotification *)notification
681     [vimController sendMessage:LostFocusMsgID data:nil];
684 - (BOOL)windowShouldClose:(id)sender
686     // Don't close the window now; Instead let Vim decide whether to close the
687     // window or not.
688     [vimController sendMessage:VimShouldCloseMsgID data:nil];
689     return NO;
692 - (void)windowDidMove:(NSNotification *)notification
694     if (setupDone && windowAutosaveKey) {
695         NSRect frame = [decoratedWindow frame];
696         NSPoint topLeft = { frame.origin.x, NSMaxY(frame) };
697         NSString *topLeftString = NSStringFromPoint(topLeft);
699         [[NSUserDefaults standardUserDefaults]
700             setObject:topLeftString forKey:windowAutosaveKey];
701     }
704 - (void)windowDidResize:(id)sender
706     if (!setupDone || fullscreenEnabled) return;
708     // NOTE: Since we have no control over when the window may resize (Cocoa
709     // may resize automatically) we simply set the view to fill the entire
710     // window.  The vim view takes care of notifying Vim if the number of
711     // (rows,columns) changed.
712     [vimView setFrameSize:[self contentSize]];
715 - (NSRect)windowWillUseStandardFrame:(NSWindow *)win
716                         defaultFrame:(NSRect)frame
718     // By default the window is maximized in the vertical direction only.
719     // Holding down the Cmd key maximizes the window in the horizontal
720     // direction.  If the MMZoomBoth user default is set, then the window
721     // maximizes in both directions by default, unless the Cmd key is held in
722     // which case the window only maximizes in the vertical direction.
724     NSEvent *event = [NSApp currentEvent];
725     BOOL cmdLeftClick = [event type] == NSLeftMouseUp
726             && [event modifierFlags] & NSCommandKeyMask;
727     BOOL zoomBoth = [[NSUserDefaults standardUserDefaults]
728             boolForKey:MMZoomBothKey];
730     if (!((zoomBoth && !cmdLeftClick) || (!zoomBoth && cmdLeftClick))) {
731         // Zoom in horizontal direction only.
732         NSRect currentFrame = [win frame];
733         frame.size.width = currentFrame.size.width;
734         frame.origin.x = currentFrame.origin.x;
735     }
737     // HACK! The window frame is often higher than the 'defaultFrame' (the fact
738     // that 'defaultFrame' doesn't cover the entire area up to the menu bar
739     // seems like a Cocoa bug).  To ensure that the window doesn't move
740     // downwards when the zoom button is clicked we check for this situation
741     // and return a frame whose max Y coordinate is no lower than the current
742     // max Y coordinate.
743     float delta = NSMaxY([win frame]) - NSMaxY(frame);
744     if (delta > 0)
745         frame.origin.y += delta;
747     return frame;
753 // -- Services menu delegate -------------------------------------------------
755 - (id)validRequestorForSendType:(NSString *)sendType
756                      returnType:(NSString *)returnType
758     if ([sendType isEqual:NSStringPboardType]
759             && [self askBackendForStarRegister:nil])
760         return self;
762     return [super validRequestorForSendType:sendType returnType:returnType];
765 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
766                              types:(NSArray *)types
768     if (![types containsObject:NSStringPboardType])
769         return NO;
771     return [self askBackendForStarRegister:pboard];
774 @end // MMWindowController
778 @implementation MMWindowController (Private)
780 - (NSSize)contentSize
782     // NOTE: Never query the content view directly for its size since it may
783     // not return the same size as contentRectForFrameRect: (e.g. when in
784     // windowed mode and the tabline separator is visible)!
785     NSWindow *win = [self window];
786     return [win contentRectForFrameRect:[win frame]].size;
789 - (void)resizeWindowToFitContentSize:(NSSize)contentSize
791     NSRect frame = [decoratedWindow frame];
792     NSRect contentRect = [decoratedWindow contentRectForFrameRect:frame];
794     // Keep top-left corner of the window fixed when resizing.
795     contentRect.origin.y -= contentSize.height - contentRect.size.height;
796     contentRect.size = contentSize;
798     frame = [decoratedWindow frameRectForContentRect:contentRect];
799     [decoratedWindow setFrame:frame display:YES];
802 - (NSSize)constrainContentSizeToScreenSize:(NSSize)contentSize
804     NSWindow *win = [self window];
805     NSRect rect = [win contentRectForFrameRect:[[win screen] visibleFrame]];
807     if (contentSize.height > rect.size.height)
808         contentSize.height = rect.size.height;
809     if (contentSize.width > rect.size.width)
810         contentSize.width = rect.size.width;
812     return contentSize;
815 - (void)updateResizeConstraints
817     if (!setupDone) return;
819     // Set the resize increments to exactly match the font size; this way the
820     // window will always hold an integer number of (rows,columns).
821     NSSize cellSize = [[vimView textView] cellSize];
822     [decoratedWindow setContentResizeIncrements:cellSize];
824     NSSize minSize = [vimView minSize];
825     [decoratedWindow setContentMinSize:minSize];
828 - (NSTabViewItem *)addNewTabViewItem
830     return [vimView addNewTabViewItem];
833 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb
835     // TODO: Can this be done with evaluateExpression: instead?
836     BOOL reply = NO;
837     id backendProxy = [vimController backendProxy];
839     if (backendProxy) {
840         @try {
841             reply = [backendProxy starRegisterToPasteboard:pb];
842         }
843         @catch (NSException *e) {
844             NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
845         }
846     }
848     return reply;
851 - (void)hideTablineSeparator:(BOOL)hide
853     // The full-screen window has no tabline separator so we operate on
854     // decoratedWindow instead of [self window].
855     if ([decoratedWindow hideTablineSeparator:hide]) {
856         // The tabline separator was toggled so the content view must change
857         // size.
858         [self updateResizeConstraints];
859         shouldResizeVimView = YES;
860     }
863 - (void)doFindNext:(BOOL)next
865     NSString *query = nil;
867 #if 0
868     // Use current query if the search field is selected.
869     id searchField = [[self searchFieldItem] view];
870     if (searchField && [[searchField stringValue] length] > 0 &&
871             [decoratedWindow firstResponder] == [searchField currentEditor])
872         query = [searchField stringValue];
873 #endif
875     if (!query) {
876         // Use find pasteboard for next query.
877         NSPasteboard *pb = [NSPasteboard pasteboardWithName:NSFindPboard];
878         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
879         if ([pb availableTypeFromArray:types])
880             query = [pb stringForType:NSStringPboardType];
881     }
883     NSString *input = nil;
884     if (query) {
885         // NOTE: The '/' register holds the last search string.  By setting it
886         // (using the '@/' syntax) we fool Vim into thinking that it has
887         // already searched for that string and then we can simply use 'n' or
888         // 'N' to find the next/previous match.
889         input = [NSString stringWithFormat:@"<C-\\><C-N>:let @/='%@'<CR>%c",
890                 query, next ? 'n' : 'N'];
891     } else {
892         input = next ? @"<C-\\><C-N>n" : @"<C-\\><C-N>N"; 
893     }
895     [vimController addVimInput:input];
898 @end // MMWindowController (Private)