1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
3 * VIM - Vi IMproved by Bram Moolenaar
4 * MacVim GUI port by Bjorn Winckler
6 * Do ":help uganda" in Vim to read copying and usage conditions.
7 * Do ":help credits" in Vim to see a list of people who contributed.
8 * See README.txt for an overview of the Vim source code.
13 * Handles resizing of windows, acts as an mediator between MMVimView and
16 * Resizing in windowed mode:
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.
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).
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
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:
38 * Tabline visible & Toolbar visible => Separator visible
39 * =====================================================================
40 * NO & NO => YES, if the window is textured
47 * Resizing in full-screen mode:
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
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.
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"
67 #import "MMWindowController.h"
68 #import "Miscellaneous.h"
69 #import <PSMTabBarControl/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;
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;
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.
102 - (void)setAutorecalculatesContentBorderThickness:(BOOL)b forEdge:(NSRectEdge)e;
103 - (void)setContentBorderThickness:(CGFloat)b forEdge:(NSRectEdge)e;
109 @implementation MMWindowController
111 - (id)initWithVimController:(MMVimController *)controller
113 unsigned styleMask = NSTitledWindowMask | NSClosableWindowMask
114 | NSMiniaturizableWindowMask | NSResizableWindowMask
115 | NSUnifiedTitleAndToolbarWindowMask;
117 // Use textured background on Leopard or later (skip the 'if' on Tiger for
118 // polished metal window).
119 if ([[NSUserDefaults standardUserDefaults] boolForKey:MMTexturedWindowKey]
120 || (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_4))
121 styleMask |= NSTexturedBackgroundWindowMask;
123 // NOTE: The content rect is only used the very first time MacVim is
124 // started (or rather, when ~/Library/Preferences/org.vim.MacVim.plist does
125 // not exist). The chosen values will put the window somewhere near the
126 // top and in the middle of a 1024x768 screen.
127 MMWindow *win = [[MMWindow alloc]
128 initWithContentRect:NSMakeRect(242,364,480,360)
130 backing:NSBackingStoreBuffered
134 self = [super initWithWindow:win];
135 if (!self) return nil;
137 vimController = controller;
138 decoratedWindow = [win retain];
140 // Window cascading is handled by MMAppController.
141 [self setShouldCascadeWindows:NO];
143 // NOTE: Autoresizing is enabled for the content view, but only used
144 // for the tabline separator. The vim view must be resized manually
145 // because of full-screen considerations, and because its size depends
146 // on whether the tabline separator is visible or not.
147 NSView *contentView = [win contentView];
148 [contentView setAutoresizesSubviews:YES];
150 vimView = [[MMVimView alloc] initWithFrame:[contentView frame]
151 vimController:vimController];
152 [vimView setAutoresizingMask:NSViewNotSizable];
153 [contentView addSubview:vimView];
155 [win setDelegate:self];
156 [win setInitialFirstResponder:[vimView textView]];
158 if ([win styleMask] & NSTexturedBackgroundWindowMask) {
159 // On Leopard, we want to have a textured window to have nice
160 // looking tabs. But the textured window look implies rounded
161 // corners, which looks really weird -- disable them. This is a
162 // private api, though.
163 if ([win respondsToSelector:@selector(setBottomCornerRounded:)])
164 [win setBottomCornerRounded:NO];
166 // When the tab bar is toggled, it changes color for the fraction
167 // of a second, probably because vim sends us events in a strange
168 // order, confusing appkit's content border heuristic for a short
169 // while. This can be worked around with these two methods. There
170 // might be a better way, but it's good enough.
171 if ([win respondsToSelector:@selector(
172 setAutorecalculatesContentBorderThickness:forEdge:)])
173 [win setAutorecalculatesContentBorderThickness:NO
175 if ([win respondsToSelector:
176 @selector(setContentBorderThickness:forEdge:)])
177 [win setContentBorderThickness:0 forEdge:NSMaxYEdge];
180 // Make us safe on pre-tiger OSX
181 if ([win respondsToSelector:@selector(_setContentHasShadow:)])
182 [win _setContentHasShadow:NO];
191 [decoratedWindow release]; decoratedWindow = nil;
192 [windowAutosaveKey release]; windowAutosaveKey = nil;
193 [vimView release]; vimView = nil;
198 - (NSString *)description
201 @"%@ : setupDone=%d windowAutosaveKey=%@ vimController=%@";
202 return [NSString stringWithFormat:format,
203 [self className], setupDone, windowAutosaveKey, vimController];
206 - (MMVimController *)vimController
208 return vimController;
211 - (MMVimView *)vimView
216 - (NSString *)windowAutosaveKey
218 return windowAutosaveKey;
221 - (void)setWindowAutosaveKey:(NSString *)key
223 [windowAutosaveKey autorelease];
224 windowAutosaveKey = [key copy];
229 ASLogDebug(@"%@ %s", [self className], _cmd);
231 if (fullscreenEnabled) {
232 // If we are closed while still in fullscreen, end fullscreen mode,
233 // release ourselves (because this won't happen in MMWindowController)
234 // and perform close operation on the original window.
235 [self leaveFullscreen];
241 [vimView removeFromSuperviewWithoutNeedingDisplay];
244 // It is feasible (though unlikely) that the user quits before the window
245 // controller is released, make sure the edit flag is cleared so no warning
246 // dialog is displayed.
247 [decoratedWindow setDocumentEdited:NO];
249 [[self window] orderOut:self];
254 // Indicates that the window is ready to be displayed, but do not display
255 // (or place) it yet -- that is done in showWindow.
257 [self addNewTabViewItem];
261 [self updateResizeConstraints];
262 [self resizeWindowToFitContentSize:[vimView desiredSize]
268 // Actually show the window on screen. However, if openWindow hasn't
269 // already been called nothing will happen (the window will be displayed
271 if (!setupDone) return;
274 [[MMAppController sharedInstance] windowControllerWillOpen:self];
275 [[self window] makeKeyAndOrderFront:self];
278 - (void)updateTabsWithData:(NSData *)data
280 [vimView updateTabsWithData:data];
283 - (void)selectTabWithIndex:(int)idx
285 [vimView selectTabWithIndex:idx];
288 - (void)setTextDimensionsWithRows:(int)rows columns:(int)cols isLive:(BOOL)live
289 keepOnScreen:(BOOL)onScreen
291 //ASLogDebug(@"setTextDimensionsWithRows:%d columns:%d isLive:%d "
292 // "keepOnScreen:%d", rows, cols, live, onScreen);
294 // NOTE: The only place where the (rows,columns) of the vim view are
295 // modified is here and when entering/leaving full-screen. Setting these
296 // values have no immediate effect, the actual resizing of the view is done
297 // in processInputQueueDidFinish.
299 // The 'live' flag indicates that this resize originated from a live
300 // resize; it may very well happen that the view is no longer in live
301 // resize when this message is received. We refrain from changing the view
302 // size when this flag is set, otherwise the window might jitter when the
303 // user drags to resize the window.
305 [vimView setDesiredRows:rows columns:cols];
307 if (setupDone && !live) {
308 shouldResizeVimView = YES;
309 keepOnScreen = onScreen;
312 if (windowAutosaveKey) {
313 // Autosave rows and columns (only done for window which also autosaves
315 id tv = [vimView textView];
316 int rows = [tv maxRows];
317 int cols = [tv maxColumns];
318 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
319 [ud setInteger:rows forKey:MMAutosaveRowsKey];
320 [ud setInteger:cols forKey:MMAutosaveColumnsKey];
325 - (void)zoomWithRows:(int)rows columns:(int)cols state:(int)state
327 [self setTextDimensionsWithRows:rows
332 // NOTE: If state==0 then the window should be put in the non-zoomed
333 // "user state". That is, move the window back to the last stored
334 // position. If the window is in the zoomed state, the call to change the
335 // dimensions above will also reposition the window to ensure it fits on
336 // the screen. However, since resizing of the window is delayed we also
337 // delay repositioning so that both happen at the same time (this avoid
338 // situations where the window woud appear to "jump").
339 if (!state && !NSEqualPoints(NSZeroPoint, userTopLeft))
340 shouldRestoreUserTopLeft = YES;
343 - (void)setTitle:(NSString *)title
346 [decoratedWindow setTitle:title];
349 - (void)setDocumentFilename:(NSString *)filename
354 // Ensure file really exists or the path to the proxy icon will look weird.
355 // If the file does not exists, don't show a proxy icon.
356 if (![[NSFileManager defaultManager] fileExistsAtPath:filename])
359 [decoratedWindow setRepresentedFilename:filename];
362 - (void)setToolbar:(NSToolbar *)toolbar
364 // The full-screen window has no toolbar.
365 [decoratedWindow setToolbar:toolbar];
367 // HACK! Redirect the pill button so that we can ask Vim to hide the
369 NSButton *pillButton = [decoratedWindow
370 standardWindowButton:NSWindowToolbarButton];
372 [pillButton setAction:@selector(toggleToolbar:)];
373 [pillButton setTarget:self];
377 - (void)createScrollbarWithIdentifier:(int32_t)ident type:(int)type
379 [vimView createScrollbarWithIdentifier:ident type:type];
382 - (BOOL)destroyScrollbarWithIdentifier:(int32_t)ident
384 BOOL scrollbarHidden = [vimView destroyScrollbarWithIdentifier:ident];
385 shouldResizeVimView = shouldResizeVimView || scrollbarHidden;
387 return scrollbarHidden;
390 - (BOOL)showScrollbarWithIdentifier:(int32_t)ident state:(BOOL)visible
392 BOOL scrollbarToggled = [vimView showScrollbarWithIdentifier:ident
394 shouldResizeVimView = shouldResizeVimView || scrollbarToggled;
396 return scrollbarToggled;
399 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(int32_t)ident
401 [vimView setScrollbarPosition:pos length:len identifier:ident];
404 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
405 identifier:(int32_t)ident
407 [vimView setScrollbarThumbValue:val proportion:prop identifier:ident];
410 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
412 // NOTE: This is called when the transparency changes so set the opacity
413 // flag on the window here (should be faster if the window is opaque).
414 BOOL isOpaque = [back alphaComponent] == 1.0f;
415 [decoratedWindow setOpaque:isOpaque];
416 if (fullscreenEnabled)
417 [fullscreenWindow setOpaque:isOpaque];
419 [vimView setDefaultColorsBackground:back foreground:fore];
422 - (void)setFont:(NSFont *)font
424 [[NSFontManager sharedFontManager] setSelectedFont:font isMultiple:NO];
425 [[vimView textView] setFont:font];
426 [self updateResizeConstraints];
429 - (void)setWideFont:(NSFont *)font
431 [[vimView textView] setWideFont:font];
434 - (void)processInputQueueDidFinish
436 // NOTE: Resizing is delayed until after all commands have been processed
437 // since it often happens that more than one command will cause a resize.
438 // If we were to immediately resize then the vim view size would jitter
439 // (e.g. hiding/showing scrollbars often happens several time in one
441 // Also delay toggling the toolbar until after scrollbars otherwise
442 // problems arise when showing toolbar and scrollbar at the same time, i.e.
445 // Update toolbar before resizing, since showing the toolbar may require
446 // the view size to become smaller.
447 if (updateToolbarFlag != 0)
448 [self updateToolbar];
450 if (shouldResizeVimView) {
451 shouldResizeVimView = NO;
453 NSSize originalSize = [vimView frame].size;
454 NSSize contentSize = [vimView desiredSize];
456 contentSize = [self constrainContentSizeToScreenSize:contentSize];
457 contentSize = [vimView constrainRows:NULL columns:NULL
459 [vimView setFrameSize:contentSize];
461 if (fullscreenEnabled) {
462 // NOTE! Don't mark the fullscreen content view as needing an
463 // update unless absolutely necessary since when it is updated the
464 // entire screen is cleared. This may cause some parts of the Vim
465 // view to be cleared but not redrawn since Vim does not realize
466 // that we've erased part of the view.
467 if (!NSEqualSizes(originalSize, contentSize)) {
468 [[fullscreenWindow contentView] setNeedsDisplay:YES];
469 [fullscreenWindow centerView];
472 [self resizeWindowToFitContentSize:contentSize
473 keepOnScreen:keepOnScreen];
480 - (void)showTabBar:(BOOL)on
482 [[vimView tabBarControl] setHidden:!on];
484 // Showing the tabline may result in the tabline separator being hidden or
485 // shown; this does not apply to full-screen mode.
487 NSToolbar *toolbar = [decoratedWindow toolbar];
488 if (([decoratedWindow styleMask] & NSTexturedBackgroundWindowMask)
490 [self hideTablineSeparator:![toolbar isVisible]];
492 [self hideTablineSeparator:NO];
495 if (([decoratedWindow styleMask] & NSTexturedBackgroundWindowMask)
497 [self hideTablineSeparator:on];
499 [self hideTablineSeparator:YES];
504 - (void)showToolbar:(BOOL)on size:(int)size mode:(int)mode
506 NSToolbar *toolbar = [decoratedWindow toolbar];
507 if (!toolbar) return;
509 [toolbar setSizeMode:size];
510 [toolbar setDisplayMode:mode];
512 // Positive flag shows toolbar, negative hides it.
513 updateToolbarFlag = on ? 1 : -1;
515 // NOTE: If the window is not visible we must toggle the toolbar
516 // immediately, otherwise "set go-=T" in .gvimrc will lead to the toolbar
517 // showing its hide animation every time a new window is opened. (See
518 // processInputQueueDidFinish for the reason why we need to delay toggling
519 // the toolbar when the window is visible.)
520 if (![decoratedWindow isVisible])
521 [self updateToolbar];
524 - (void)setMouseShape:(int)shape
526 [[vimView textView] setMouseShape:shape];
529 - (void)adjustLinespace:(int)linespace
531 if (vimView && [vimView textView]) {
532 [[vimView textView] setLinespace:(float)linespace];
533 shouldResizeVimView = YES;
537 - (void)liveResizeWillStart
539 if (!setupDone) return;
541 // Save the original title, if we haven't already.
542 if (lastSetTitle == nil) {
543 lastSetTitle = [[decoratedWindow title] retain];
546 // NOTE: During live resize Cocoa goes into "event tracking mode". We have
547 // to add the backend connection to this mode in order for resize messages
548 // from Vim to reach MacVim. We do not wish to always listen to requests
549 // in event tracking mode since then MacVim could receive DO messages at
550 // unexpected times (e.g. when a key equivalent is pressed and the menu bar
551 // momentarily lights up).
552 id proxy = [vimController backendProxy];
553 NSConnection *connection = [(NSDistantObject*)proxy connectionForProxy];
554 [connection addRequestMode:NSEventTrackingRunLoopMode];
557 - (void)liveResizeDidEnd
559 if (!setupDone) return;
561 // See comment above regarding event tracking mode.
562 id proxy = [vimController backendProxy];
563 NSConnection *connection = [(NSDistantObject*)proxy connectionForProxy];
564 [connection removeRequestMode:NSEventTrackingRunLoopMode];
566 // NOTE: During live resize messages from MacVim to Vim are often dropped
567 // (because too many messages are sent at once). This may lead to
568 // inconsistent states between Vim and MacVim; to avoid this we send a
569 // synchronous resize message to Vim now (this is not fool-proof, but it
570 // does seem to work quite well).
573 NSSize textViewSize = [[vimView textView] frame].size;
574 [[vimView textView] constrainRows:&constrained[0] columns:&constrained[1]
575 toSize:textViewSize];
577 ASLogDebug(@"End of live resize, notify Vim that text dimensions are %dx%d",
578 constrained[1], constrained[0]);
580 NSData *data = [NSData dataWithBytes:constrained length:2*sizeof(int)];
581 BOOL sendOk = [vimController sendMessageNow:SetTextDimensionsMsgID
586 // Sending of synchronous message failed. Force the window size to
587 // match the last dimensions received from Vim, otherwise we end up
588 // with inconsistent states.
589 [self resizeWindowToFitContentSize:[vimView desiredSize]
593 // If we saved the original title while resizing, restore it.
594 if (lastSetTitle != nil) {
595 [decoratedWindow setTitle:lastSetTitle];
596 [lastSetTitle release];
601 - (void)enterFullscreen:(int)fuoptions backgroundColor:(NSColor *)back
603 if (fullscreenEnabled) return;
605 fullscreenWindow = [[MMFullscreenWindow alloc]
606 initWithWindow:decoratedWindow view:vimView backgroundColor:back];
607 [fullscreenWindow enterFullscreen:fuoptions];
608 [fullscreenWindow setDelegate:self];
609 fullscreenEnabled = YES;
611 // The resize handle disappears so the vim view needs to update the
613 shouldResizeVimView = YES;
616 - (void)leaveFullscreen
618 if (!fullscreenEnabled) return;
620 fullscreenEnabled = NO;
621 [fullscreenWindow leaveFullscreen];
622 [fullscreenWindow release];
623 fullscreenWindow = nil;
625 // The vim view may be too large to fit the screen, so update it.
626 shouldResizeVimView = YES;
629 - (void)setFullscreenBackgroundColor:(NSColor *)back
631 if (fullscreenEnabled)
632 [fullscreenWindow setBackgroundColor:back];
635 - (void)setBuffersModified:(BOOL)mod
637 // NOTE: We only set the document edited flag on the decorated window since
638 // the full-screen window has no close button anyway. (It also saves us
639 // from keeping track of the flag in two different places.)
640 [decoratedWindow setDocumentEdited:mod];
644 - (IBAction)addNewTab:(id)sender
646 [vimView addNewTab:sender];
649 - (IBAction)toggleToolbar:(id)sender
651 [vimController sendMessage:ToggleToolbarMsgID data:nil];
654 - (IBAction)performClose:(id)sender
656 // NOTE: With the introduction of :macmenu it is possible to bind
657 // File.Close to ":conf q" but at the same time have it send off the
658 // performClose: action. For this reason we no longer need the CloseMsgID
659 // message. However, we still need File.Close to send performClose:
660 // otherwise Cmd-w will not work on dialogs.
661 [self vimMenuItemAction:sender];
664 - (IBAction)findNext:(id)sender
666 [self doFindNext:YES];
669 - (IBAction)findPrevious:(id)sender
671 [self doFindNext:NO];
674 - (IBAction)vimMenuItemAction:(id)sender
676 if (![sender isKindOfClass:[NSMenuItem class]]) return;
678 // TODO: Make into category on NSMenuItem which returns descriptor.
679 NSMenuItem *item = (NSMenuItem*)sender;
680 NSMutableArray *desc = [NSMutableArray arrayWithObject:[item title]];
682 NSMenu *menu = [item menu];
684 [desc insertObject:[menu title] atIndex:0];
685 menu = [menu supermenu];
688 // The "MainMenu" item is part of the Cocoa menu and should not be part of
690 if ([[desc objectAtIndex:0] isEqual:@"MainMenu"])
691 [desc removeObjectAtIndex:0];
693 NSDictionary *attrs = [NSDictionary dictionaryWithObject:desc
694 forKey:@"descriptor"];
695 [vimController sendMessage:ExecuteMenuMsgID data:[attrs dictionaryAsData]];
698 - (IBAction)vimToolbarItemAction:(id)sender
700 NSArray *desc = [NSArray arrayWithObjects:@"ToolBar", [sender label], nil];
701 NSDictionary *attrs = [NSDictionary dictionaryWithObject:desc
702 forKey:@"descriptor"];
703 [vimController sendMessage:ExecuteMenuMsgID data:[attrs dictionaryAsData]];
706 - (IBAction)fontSizeUp:(id)sender
708 [[NSFontManager sharedFontManager] modifyFont:
709 [NSNumber numberWithInt:NSSizeUpFontAction]];
712 - (IBAction)fontSizeDown:(id)sender
714 [[NSFontManager sharedFontManager] modifyFont:
715 [NSNumber numberWithInt:NSSizeDownFontAction]];
718 - (IBAction)findAndReplace:(id)sender
720 int tag = [sender tag];
721 MMFindReplaceController *fr = [MMFindReplaceController sharedInstance];
724 // NOTE: The 'flags' values must match the FRD_ defines in gui.h (except
725 // for 0x100 which we use to indicate a backward search).
727 case 1: flags = 0x100; break;
728 case 2: flags = 3; break;
729 case 3: flags = 4; break;
734 if (![fr ignoreCase])
737 NSDictionary *args = [NSDictionary dictionaryWithObjectsAndKeys:
738 [fr findString], @"find",
739 [fr replaceString], @"replace",
740 [NSNumber numberWithInt:flags], @"flags",
743 [vimController sendMessage:FindReplaceMsgID data:[args dictionaryAsData]];
746 - (BOOL)validateMenuItem:(NSMenuItem *)item
748 if ([item action] == @selector(vimMenuItemAction:)
749 || [item action] == @selector(performClose:))
755 // -- NSWindow delegate ------------------------------------------------------
757 - (void)windowDidBecomeMain:(NSNotification *)notification
759 [[MMAppController sharedInstance] setMainMenu:[vimController mainMenu]];
760 [vimController sendMessage:GotFocusMsgID data:nil];
762 if ([vimView textView]) {
763 NSFontManager *fm = [NSFontManager sharedFontManager];
764 [fm setSelectedFont:[[vimView textView] font] isMultiple:NO];
768 - (void)windowDidResignMain:(NSNotification *)notification
770 [vimController sendMessage:LostFocusMsgID data:nil];
773 - (BOOL)windowShouldClose:(id)sender
775 // Don't close the window now; Instead let Vim decide whether to close the
777 [vimController sendMessage:VimShouldCloseMsgID data:nil];
781 - (void)windowDidMove:(NSNotification *)notification
786 if (fullscreenEnabled) {
787 // HACK! The full-screen is not supposed to be able to be moved. If we
788 // do get here while in full-screen something unexpected happened (e.g.
789 // the full-screen window was on an external display that got
790 // unplugged) and we handle this situation by leaving full-screen.
791 [self leaveFullscreen];
795 if (windowAutosaveKey) {
796 NSRect frame = [decoratedWindow frame];
797 NSPoint topLeft = { frame.origin.x, NSMaxY(frame) };
798 NSString *topLeftString = NSStringFromPoint(topLeft);
800 [[NSUserDefaults standardUserDefaults]
801 setObject:topLeftString forKey:windowAutosaveKey];
805 - (void)windowDidResize:(id)sender
807 if (!setupDone || fullscreenEnabled) return;
809 // NOTE: Since we have no control over when the window may resize (Cocoa
810 // may resize automatically) we simply set the view to fill the entire
811 // window. The vim view takes care of notifying Vim if the number of
812 // (rows,columns) changed.
813 [vimView setFrameSize:[self contentSize]];
816 // This is not an NSWindow delegate method, our custom MMWindow class calls it
817 // instead of the usual windowWillUseStandardFrame:defaultFrame:.
818 - (IBAction)zoom:(id)sender
820 NSScreen *screen = [decoratedWindow screen];
822 ASLogNotice(@"Window not on screen, zoom to main screen");
823 screen = [NSScreen mainScreen];
825 ASLogNotice(@"No main screen, abort zoom");
830 // Decide whether too zoom horizontally or not (always zoom vertically).
831 NSEvent *event = [NSApp currentEvent];
832 BOOL cmdLeftClick = [event type] == NSLeftMouseUp &&
833 [event modifierFlags] & NSCommandKeyMask;
834 BOOL zoomBoth = [[NSUserDefaults standardUserDefaults]
835 boolForKey:MMZoomBothKey];
836 zoomBoth = (zoomBoth && !cmdLeftClick) || (!zoomBoth && cmdLeftClick);
838 // Figure out how many rows/columns can fit while zoomed.
839 int rowsZoomed, colsZoomed;
840 NSRect maxFrame = [screen visibleFrame];
841 NSRect contentRect = [decoratedWindow contentRectForFrameRect:maxFrame];
842 [vimView constrainRows:&rowsZoomed
844 toSize:contentRect.size];
846 int curRows, curCols;
847 [[vimView textView] getMaxRows:&curRows columns:&curCols];
850 BOOL isZoomed = zoomBoth ? curRows >= rowsZoomed && curCols >= colsZoomed
851 : curRows >= rowsZoomed;
853 rows = userRows > 0 ? userRows : curRows;
854 cols = userCols > 0 ? userCols : curCols;
857 cols = zoomBoth ? colsZoomed : curCols;
859 if (curRows+2 < rows || curCols+2 < cols) {
860 // The window is being zoomed so save the current "user state".
861 // Note that if the window does not enlarge by a 'significant'
862 // number of rows/columns then we don't save the current state.
863 // This is done to take into account toolbar/scrollbars
867 NSRect frame = [decoratedWindow frame];
868 userTopLeft = NSMakePoint(frame.origin.x, NSMaxY(frame));
872 // NOTE: Instead of resizing the window immediately we send a zoom message
873 // to the backend so that it gets a chance to resize before the window
874 // does. This avoids problems with the window flickering when zooming.
875 int info[3] = { rows, cols, !isZoomed };
876 NSData *data = [NSData dataWithBytes:info length:3*sizeof(int)];
877 [vimController sendMessage:ZoomMsgID data:data];
882 // -- Services menu delegate -------------------------------------------------
884 - (id)validRequestorForSendType:(NSString *)sendType
885 returnType:(NSString *)returnType
887 if ([sendType isEqual:NSStringPboardType]
888 && [self askBackendForStarRegister:nil])
891 return [super validRequestorForSendType:sendType returnType:returnType];
894 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
895 types:(NSArray *)types
897 if (![types containsObject:NSStringPboardType])
900 return [self askBackendForStarRegister:pboard];
903 - (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pboard
905 // Replace the current selection with the text on the pasteboard.
906 NSArray *types = [pboard types];
907 if ([types containsObject:NSStringPboardType]) {
908 NSString *input = [NSString stringWithFormat:@"s%@",
909 [pboard stringForType:NSStringPboardType]];
910 [vimController addVimInput:input];
917 @end // MMWindowController
921 @implementation MMWindowController (Private)
923 - (NSSize)contentSize
925 // NOTE: Never query the content view directly for its size since it may
926 // not return the same size as contentRectForFrameRect: (e.g. when in
927 // windowed mode and the tabline separator is visible)!
928 NSWindow *win = [self window];
929 return [win contentRectForFrameRect:[win frame]].size;
932 - (void)resizeWindowToFitContentSize:(NSSize)contentSize
933 keepOnScreen:(BOOL)onScreen
935 NSRect frame = [decoratedWindow frame];
936 NSRect contentRect = [decoratedWindow contentRectForFrameRect:frame];
938 // Keep top-left corner of the window fixed when resizing.
939 contentRect.origin.y -= contentSize.height - contentRect.size.height;
940 contentRect.size = contentSize;
942 NSRect newFrame = [decoratedWindow frameRectForContentRect:contentRect];
944 if (shouldRestoreUserTopLeft) {
945 // Restore user top left window position (which is saved when zooming).
946 CGFloat dy = userTopLeft.y - NSMaxY(newFrame);
947 newFrame.origin.x = userTopLeft.x;
948 newFrame.origin.y += dy;
949 shouldRestoreUserTopLeft = NO;
952 if (onScreen && [decoratedWindow screen]) {
953 // Ensure that the window fits inside the visible part of the screen.
954 // If there are more than one screen the window will be moved to fit
955 // entirely in the screen that most of it occupies.
956 // NOTE: Not called in full-screen mode so use "visibleFrame' instead
958 NSRect maxFrame = [[decoratedWindow screen] visibleFrame];
959 maxFrame = [self constrainFrame:maxFrame];
961 if (newFrame.size.width > maxFrame.size.width) {
962 newFrame.size.width = maxFrame.size.width;
963 newFrame.origin.x = maxFrame.origin.x;
965 if (newFrame.size.height > maxFrame.size.height) {
966 newFrame.size.height = maxFrame.size.height;
967 newFrame.origin.y = maxFrame.origin.y;
970 if (newFrame.origin.y < maxFrame.origin.y)
971 newFrame.origin.y = maxFrame.origin.y;
972 if (NSMaxY(newFrame) > NSMaxY(maxFrame))
973 newFrame.origin.y = NSMaxY(maxFrame) - newFrame.size.height;
974 if (newFrame.origin.x < maxFrame.origin.x)
975 newFrame.origin.x = maxFrame.origin.x;
976 if (NSMaxX(newFrame) > NSMaxX(maxFrame))
977 newFrame.origin.x = NSMaxX(maxFrame) - newFrame.size.width;
980 [decoratedWindow setFrame:newFrame display:YES];
983 - (NSSize)constrainContentSizeToScreenSize:(NSSize)contentSize
985 NSWindow *win = [self window];
989 // NOTE: This may be called in both windowed and full-screen mode. The
990 // "visibleFrame" method does not overlap menu and dock so should not be
991 // used in full-screen.
992 NSRect screenRect = fullscreenEnabled ? [[win screen] frame]
993 : [[win screen] visibleFrame];
994 NSRect rect = [win contentRectForFrameRect:screenRect];
996 if (contentSize.height > rect.size.height)
997 contentSize.height = rect.size.height;
998 if (contentSize.width > rect.size.width)
999 contentSize.width = rect.size.width;
1004 - (NSRect)constrainFrame:(NSRect)frame
1006 // Constrain the given (window) frame so that it fits an even number of
1007 // rows and columns.
1008 NSRect contentRect = [decoratedWindow contentRectForFrameRect:frame];
1009 NSSize constrainedSize = [vimView constrainRows:NULL
1011 toSize:contentRect.size];
1013 contentRect.origin.y += contentRect.size.height - constrainedSize.height;
1014 contentRect.size = constrainedSize;
1016 return [decoratedWindow frameRectForContentRect:contentRect];
1019 - (void)updateResizeConstraints
1021 if (!setupDone) return;
1023 // Set the resize increments to exactly match the font size; this way the
1024 // window will always hold an integer number of (rows,columns).
1025 NSSize cellSize = [[vimView textView] cellSize];
1026 [decoratedWindow setContentResizeIncrements:cellSize];
1028 NSSize minSize = [vimView minSize];
1029 [decoratedWindow setContentMinSize:minSize];
1032 - (NSTabViewItem *)addNewTabViewItem
1034 return [vimView addNewTabViewItem];
1037 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb
1039 // TODO: Can this be done with evaluateExpression: instead?
1041 id backendProxy = [vimController backendProxy];
1045 reply = [backendProxy starRegisterToPasteboard:pb];
1047 @catch (NSException *ex) {
1048 ASLogDebug(@"starRegisterToPasteboard: failed: pid=%d reason=%@",
1049 [vimController pid], ex);
1056 - (void)hideTablineSeparator:(BOOL)hide
1058 // The full-screen window has no tabline separator so we operate on
1059 // decoratedWindow instead of [self window].
1060 if ([decoratedWindow hideTablineSeparator:hide]) {
1061 // The tabline separator was toggled so the content view must change
1063 [self updateResizeConstraints];
1064 shouldResizeVimView = YES;
1068 - (void)doFindNext:(BOOL)next
1070 NSString *query = nil;
1073 // Use current query if the search field is selected.
1074 id searchField = [[self searchFieldItem] view];
1075 if (searchField && [[searchField stringValue] length] > 0 &&
1076 [decoratedWindow firstResponder] == [searchField currentEditor])
1077 query = [searchField stringValue];
1081 // Use find pasteboard for next query.
1082 NSPasteboard *pb = [NSPasteboard pasteboardWithName:NSFindPboard];
1083 NSArray *supportedTypes = [NSArray arrayWithObjects:VimFindPboardType,
1084 NSStringPboardType, nil];
1085 NSString *bestType = [pb availableTypeFromArray:supportedTypes];
1087 // See gui_macvim_add_to_find_pboard() for an explanation of these
1089 if ([bestType isEqual:VimFindPboardType])
1090 query = [pb stringForType:VimFindPboardType];
1092 query = [pb stringForType:NSStringPboardType];
1095 NSString *input = nil;
1097 // NOTE: The '/' register holds the last search string. By setting it
1098 // (using the '@/' syntax) we fool Vim into thinking that it has
1099 // already searched for that string and then we can simply use 'n' or
1100 // 'N' to find the next/previous match.
1101 input = [NSString stringWithFormat:@"<C-\\><C-N>:let @/='%@'<CR>%c",
1102 query, next ? 'n' : 'N'];
1104 input = next ? @"<C-\\><C-N>n" : @"<C-\\><C-N>N";
1107 [vimController addVimInput:input];
1110 - (void)updateToolbar
1112 NSToolbar *toolbar = [decoratedWindow toolbar];
1113 if (nil == toolbar || 0 == updateToolbarFlag) return;
1115 // Positive flag shows toolbar, negative hides it.
1116 BOOL on = updateToolbarFlag > 0 ? YES : NO;
1117 [toolbar setVisible:on];
1119 if (([decoratedWindow styleMask] & NSTexturedBackgroundWindowMask) == 0) {
1121 [self hideTablineSeparator:YES];
1123 [self hideTablineSeparator:![[vimView tabBarControl] isHidden]];
1126 // Textured windows don't have a line below there title bar, so we
1127 // need the separator in this case as well. In fact, the only case
1128 // where we don't need the separator is when the tab bar control
1129 // is visible (because it brings its own separator).
1130 [self hideTablineSeparator:![[vimView tabBarControl] isHidden]];
1133 updateToolbarFlag = 0;
1136 @end // MMWindowController (Private)