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 if (shouldResizeVimView) {
446 shouldResizeVimView = NO;
448 NSSize originalSize = [vimView frame].size;
449 NSSize contentSize = [vimView desiredSize];
450 contentSize = [self constrainContentSizeToScreenSize:contentSize];
451 contentSize = [vimView constrainRows:NULL columns:NULL
453 [vimView setFrameSize:contentSize];
455 if (fullscreenEnabled) {
456 // NOTE! Don't mark the fullscreen content view as needing an
457 // update unless absolutely necessary since when it is updated the
458 // entire screen is cleared. This may cause some parts of the Vim
459 // view to be cleared but not redrawn since Vim does not realize
460 // that we've erased part of the view.
461 if (!NSEqualSizes(originalSize, contentSize)) {
462 [[fullscreenWindow contentView] setNeedsDisplay:YES];
463 [fullscreenWindow centerView];
466 [self resizeWindowToFitContentSize:contentSize
467 keepOnScreen:keepOnScreen];
473 if (updateToolbarFlag != 0)
474 [self updateToolbar];
477 - (void)showTabBar:(BOOL)on
479 [[vimView tabBarControl] setHidden:!on];
481 // Showing the tabline may result in the tabline separator being hidden or
482 // shown; this does not apply to full-screen mode.
484 NSToolbar *toolbar = [decoratedWindow toolbar];
485 if (([decoratedWindow styleMask] & NSTexturedBackgroundWindowMask)
487 [self hideTablineSeparator:![toolbar isVisible]];
489 [self hideTablineSeparator:NO];
492 if (([decoratedWindow styleMask] & NSTexturedBackgroundWindowMask)
494 [self hideTablineSeparator:on];
496 [self hideTablineSeparator:YES];
501 - (void)showToolbar:(BOOL)on size:(int)size mode:(int)mode
503 NSToolbar *toolbar = [decoratedWindow toolbar];
504 if (!toolbar) return;
506 [toolbar setSizeMode:size];
507 [toolbar setDisplayMode:mode];
509 // Positive flag shows toolbar, negative hides it.
510 updateToolbarFlag = on ? 1 : -1;
512 // NOTE: If the window is not visible we must toggle the toolbar
513 // immediately, otherwise "set go-=T" in .gvimrc will lead to the toolbar
514 // showing its hide animation every time a new window is opened. (See
515 // processInputQueueDidFinish for the reason why we need to delay toggling
516 // the toolbar when the window is visible.)
517 if (![decoratedWindow isVisible])
518 [self updateToolbar];
521 - (void)setMouseShape:(int)shape
523 [[vimView textView] setMouseShape:shape];
526 - (void)adjustLinespace:(int)linespace
528 if (vimView && [vimView textView]) {
529 [[vimView textView] setLinespace:(float)linespace];
530 shouldResizeVimView = YES;
534 - (void)liveResizeWillStart
536 if (!setupDone) return;
538 // Save the original title, if we haven't already.
539 if (lastSetTitle == nil) {
540 lastSetTitle = [[decoratedWindow title] retain];
543 // NOTE: During live resize Cocoa goes into "event tracking mode". We have
544 // to add the backend connection to this mode in order for resize messages
545 // from Vim to reach MacVim. We do not wish to always listen to requests
546 // in event tracking mode since then MacVim could receive DO messages at
547 // unexpected times (e.g. when a key equivalent is pressed and the menu bar
548 // momentarily lights up).
549 id proxy = [vimController backendProxy];
550 NSConnection *connection = [(NSDistantObject*)proxy connectionForProxy];
551 [connection addRequestMode:NSEventTrackingRunLoopMode];
554 - (void)liveResizeDidEnd
556 if (!setupDone) return;
558 // See comment above regarding event tracking mode.
559 id proxy = [vimController backendProxy];
560 NSConnection *connection = [(NSDistantObject*)proxy connectionForProxy];
561 [connection removeRequestMode:NSEventTrackingRunLoopMode];
563 // NOTE: During live resize messages from MacVim to Vim are often dropped
564 // (because too many messages are sent at once). This may lead to
565 // inconsistent states between Vim and MacVim; to avoid this we send a
566 // synchronous resize message to Vim now (this is not fool-proof, but it
567 // does seem to work quite well).
570 NSSize textViewSize = [[vimView textView] frame].size;
571 [[vimView textView] constrainRows:&constrained[0] columns:&constrained[1]
572 toSize:textViewSize];
574 ASLogDebug(@"End of live resize, notify Vim that text dimensions are %dx%d",
575 constrained[1], constrained[0]);
577 NSData *data = [NSData dataWithBytes:constrained length:2*sizeof(int)];
578 BOOL sendOk = [vimController sendMessageNow:SetTextDimensionsMsgID
583 // Sending of synchronous message failed. Force the window size to
584 // match the last dimensions received from Vim, otherwise we end up
585 // with inconsistent states.
586 [self resizeWindowToFitContentSize:[vimView desiredSize]
590 // If we saved the original title while resizing, restore it.
591 if (lastSetTitle != nil) {
592 [decoratedWindow setTitle:lastSetTitle];
593 [lastSetTitle release];
598 - (void)enterFullscreen:(int)fuoptions backgroundColor:(NSColor *)back
600 if (fullscreenEnabled) return;
602 fullscreenWindow = [[MMFullscreenWindow alloc]
603 initWithWindow:decoratedWindow view:vimView backgroundColor:back];
604 [fullscreenWindow enterFullscreen:fuoptions];
605 [fullscreenWindow setDelegate:self];
606 fullscreenEnabled = YES;
608 // The resize handle disappears so the vim view needs to update the
610 shouldResizeVimView = YES;
613 - (void)leaveFullscreen
615 if (!fullscreenEnabled) return;
617 fullscreenEnabled = NO;
618 [fullscreenWindow leaveFullscreen];
619 [fullscreenWindow release];
620 fullscreenWindow = nil;
622 // The vim view may be too large to fit the screen, so update it.
623 shouldResizeVimView = YES;
626 - (void)setFullscreenBackgroundColor:(NSColor *)back
628 if (fullscreenEnabled)
629 [fullscreenWindow setBackgroundColor:back];
632 - (void)setBuffersModified:(BOOL)mod
634 // NOTE: We only set the document edited flag on the decorated window since
635 // the full-screen window has no close button anyway. (It also saves us
636 // from keeping track of the flag in two different places.)
637 [decoratedWindow setDocumentEdited:mod];
641 - (IBAction)addNewTab:(id)sender
643 [vimView addNewTab:sender];
646 - (IBAction)toggleToolbar:(id)sender
648 [vimController sendMessage:ToggleToolbarMsgID data:nil];
651 - (IBAction)performClose:(id)sender
653 // NOTE: With the introduction of :macmenu it is possible to bind
654 // File.Close to ":conf q" but at the same time have it send off the
655 // performClose: action. For this reason we no longer need the CloseMsgID
656 // message. However, we still need File.Close to send performClose:
657 // otherwise Cmd-w will not work on dialogs.
658 [self vimMenuItemAction:sender];
661 - (IBAction)findNext:(id)sender
663 [self doFindNext:YES];
666 - (IBAction)findPrevious:(id)sender
668 [self doFindNext:NO];
671 - (IBAction)vimMenuItemAction:(id)sender
673 if (![sender isKindOfClass:[NSMenuItem class]]) return;
675 // TODO: Make into category on NSMenuItem which returns descriptor.
676 NSMenuItem *item = (NSMenuItem*)sender;
677 NSMutableArray *desc = [NSMutableArray arrayWithObject:[item title]];
679 NSMenu *menu = [item menu];
681 [desc insertObject:[menu title] atIndex:0];
682 menu = [menu supermenu];
685 // The "MainMenu" item is part of the Cocoa menu and should not be part of
687 if ([[desc objectAtIndex:0] isEqual:@"MainMenu"])
688 [desc removeObjectAtIndex:0];
690 NSDictionary *attrs = [NSDictionary dictionaryWithObject:desc
691 forKey:@"descriptor"];
692 [vimController sendMessage:ExecuteMenuMsgID data:[attrs dictionaryAsData]];
695 - (IBAction)vimToolbarItemAction:(id)sender
697 NSArray *desc = [NSArray arrayWithObjects:@"ToolBar", [sender label], nil];
698 NSDictionary *attrs = [NSDictionary dictionaryWithObject:desc
699 forKey:@"descriptor"];
700 [vimController sendMessage:ExecuteMenuMsgID data:[attrs dictionaryAsData]];
703 - (IBAction)fontSizeUp:(id)sender
705 [[NSFontManager sharedFontManager] modifyFont:
706 [NSNumber numberWithInt:NSSizeUpFontAction]];
709 - (IBAction)fontSizeDown:(id)sender
711 [[NSFontManager sharedFontManager] modifyFont:
712 [NSNumber numberWithInt:NSSizeDownFontAction]];
715 - (IBAction)findAndReplace:(id)sender
717 int tag = [sender tag];
718 MMFindReplaceController *fr = [MMFindReplaceController sharedInstance];
721 // NOTE: The 'flags' values must match the FRD_ defines in gui.h (except
722 // for 0x100 which we use to indicate a backward search).
724 case 1: flags = 0x100; break;
725 case 2: flags = 3; break;
726 case 3: flags = 4; break;
731 if (![fr ignoreCase])
734 NSDictionary *args = [NSDictionary dictionaryWithObjectsAndKeys:
735 [fr findString], @"find",
736 [fr replaceString], @"replace",
737 [NSNumber numberWithInt:flags], @"flags",
740 [vimController sendMessage:FindReplaceMsgID data:[args dictionaryAsData]];
743 - (BOOL)validateMenuItem:(NSMenuItem *)item
745 if ([item action] == @selector(vimMenuItemAction:)
746 || [item action] == @selector(performClose:))
752 // -- NSWindow delegate ------------------------------------------------------
754 - (void)windowDidBecomeMain:(NSNotification *)notification
756 [[MMAppController sharedInstance] setMainMenu:[vimController mainMenu]];
757 [vimController sendMessage:GotFocusMsgID data:nil];
759 if ([vimView textView]) {
760 NSFontManager *fm = [NSFontManager sharedFontManager];
761 [fm setSelectedFont:[[vimView textView] font] isMultiple:NO];
765 - (void)windowDidResignMain:(NSNotification *)notification
767 [vimController sendMessage:LostFocusMsgID data:nil];
770 - (BOOL)windowShouldClose:(id)sender
772 // Don't close the window now; Instead let Vim decide whether to close the
774 [vimController sendMessage:VimShouldCloseMsgID data:nil];
778 - (void)windowDidMove:(NSNotification *)notification
783 if (fullscreenEnabled) {
784 // HACK! The full-screen is not supposed to be able to be moved. If we
785 // do get here while in full-screen something unexpected happened (e.g.
786 // the full-screen window was on an external display that got
787 // unplugged) and we handle this situation by leaving full-screen.
788 [self leaveFullscreen];
792 if (windowAutosaveKey) {
793 NSRect frame = [decoratedWindow frame];
794 NSPoint topLeft = { frame.origin.x, NSMaxY(frame) };
795 NSString *topLeftString = NSStringFromPoint(topLeft);
797 [[NSUserDefaults standardUserDefaults]
798 setObject:topLeftString forKey:windowAutosaveKey];
802 - (NSSize)windowWillResize:(NSWindow *)win toSize:(NSSize)proposedFrameSize
804 // Make sure the window isn't resized to be larger than the "visible frame"
805 // for the current screen. Do this here instead of setting the window max
806 // size in updateResizeConstraints since the screen's visible frame may
807 // change at any time (dock could move, resolution could change, window
808 // could be moved to another screen, ...).
810 return proposedFrameSize;
812 // NOTE: Not called in full-screen mode so use "visibleFrame" instead of
814 NSRect maxFrame = [self constrainFrame:[[win screen] visibleFrame]];
816 if (proposedFrameSize.width > maxFrame.size.width)
817 proposedFrameSize.width = maxFrame.size.width;
818 if (proposedFrameSize.height > maxFrame.size.height)
819 proposedFrameSize.height = maxFrame.size.height;
821 return proposedFrameSize;
824 - (void)windowDidResize:(id)sender
826 if (!setupDone || fullscreenEnabled) return;
828 // NOTE: Since we have no control over when the window may resize (Cocoa
829 // may resize automatically) we simply set the view to fill the entire
830 // window. The vim view takes care of notifying Vim if the number of
831 // (rows,columns) changed.
832 [vimView setFrameSize:[self contentSize]];
835 // This is not an NSWindow delegate method, our custom MMWindow class calls it
836 // instead of the usual windowWillUseStandardFrame:defaultFrame:.
837 - (IBAction)zoom:(id)sender
839 NSScreen *screen = [decoratedWindow screen];
841 ASLogNotice(@"Window not on screen, zoom to main screen");
842 screen = [NSScreen mainScreen];
844 ASLogNotice(@"No main screen, abort zoom");
849 // Decide whether too zoom horizontally or not (always zoom vertically).
850 NSEvent *event = [NSApp currentEvent];
851 BOOL cmdLeftClick = [event type] == NSLeftMouseUp &&
852 [event modifierFlags] & NSCommandKeyMask;
853 BOOL zoomBoth = [[NSUserDefaults standardUserDefaults]
854 boolForKey:MMZoomBothKey];
855 zoomBoth = (zoomBoth && !cmdLeftClick) || (!zoomBoth && cmdLeftClick);
857 // Figure out how many rows/columns can fit while zoomed.
858 int rowsZoomed, colsZoomed;
859 NSRect maxFrame = [screen visibleFrame];
860 NSRect contentRect = [decoratedWindow contentRectForFrameRect:maxFrame];
861 [vimView constrainRows:&rowsZoomed
863 toSize:contentRect.size];
865 int curRows, curCols;
866 [[vimView textView] getMaxRows:&curRows columns:&curCols];
869 BOOL isZoomed = zoomBoth ? curRows >= rowsZoomed && curCols >= colsZoomed
870 : curRows >= rowsZoomed;
872 rows = userRows > 0 ? userRows : curRows;
873 cols = userCols > 0 ? userCols : curCols;
876 cols = zoomBoth ? colsZoomed : curCols;
878 if (curRows+2 < rows || curCols+2 < cols) {
879 // The window is being zoomed so save the current "user state".
880 // Note that if the window does not enlarge by a 'significant'
881 // number of rows/columns then we don't save the current state.
882 // This is done to take into account toolbar/scrollbars
886 NSRect frame = [decoratedWindow frame];
887 userTopLeft = NSMakePoint(frame.origin.x, NSMaxY(frame));
891 // NOTE: Instead of resizing the window immediately we send a zoom message
892 // to the backend so that it gets a chance to resize before the window
893 // does. This avoids problems with the window flickering when zooming.
894 int info[3] = { rows, cols, !isZoomed };
895 NSData *data = [NSData dataWithBytes:info length:3*sizeof(int)];
896 [vimController sendMessage:ZoomMsgID data:data];
901 // -- Services menu delegate -------------------------------------------------
903 - (id)validRequestorForSendType:(NSString *)sendType
904 returnType:(NSString *)returnType
906 if ([sendType isEqual:NSStringPboardType]
907 && [self askBackendForStarRegister:nil])
910 return [super validRequestorForSendType:sendType returnType:returnType];
913 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
914 types:(NSArray *)types
916 if (![types containsObject:NSStringPboardType])
919 return [self askBackendForStarRegister:pboard];
922 - (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pboard
924 // Replace the current selection with the text on the pasteboard.
925 NSArray *types = [pboard types];
926 if ([types containsObject:NSStringPboardType]) {
927 NSString *input = [NSString stringWithFormat:@"s%@",
928 [pboard stringForType:NSStringPboardType]];
929 [vimController addVimInput:input];
936 @end // MMWindowController
940 @implementation MMWindowController (Private)
942 - (NSSize)contentSize
944 // NOTE: Never query the content view directly for its size since it may
945 // not return the same size as contentRectForFrameRect: (e.g. when in
946 // windowed mode and the tabline separator is visible)!
947 NSWindow *win = [self window];
948 return [win contentRectForFrameRect:[win frame]].size;
951 - (void)resizeWindowToFitContentSize:(NSSize)contentSize
952 keepOnScreen:(BOOL)onScreen
954 NSRect frame = [decoratedWindow frame];
955 NSRect contentRect = [decoratedWindow contentRectForFrameRect:frame];
957 // Keep top-left corner of the window fixed when resizing.
958 contentRect.origin.y -= contentSize.height - contentRect.size.height;
959 contentRect.size = contentSize;
961 NSRect newFrame = [decoratedWindow frameRectForContentRect:contentRect];
963 if (shouldRestoreUserTopLeft) {
964 // Restore user top left window position (which is saved when zooming).
965 CGFloat dy = userTopLeft.y - NSMaxY(newFrame);
966 newFrame.origin.x = userTopLeft.x;
967 newFrame.origin.y += dy;
968 shouldRestoreUserTopLeft = NO;
971 if ([decoratedWindow screen]) {
972 // Ensure that the window fits inside the visible part of the screen.
973 // NOTE: Not called in full-screen mode so use "visibleFrame' instead
975 NSRect maxFrame = [[decoratedWindow screen] visibleFrame];
976 maxFrame = [self constrainFrame:maxFrame];
978 if (newFrame.size.width > maxFrame.size.width) {
979 newFrame.size.width = maxFrame.size.width;
980 newFrame.origin.x = maxFrame.origin.x;
982 if (newFrame.size.height > maxFrame.size.height) {
983 newFrame.size.height = maxFrame.size.height;
984 newFrame.origin.y = maxFrame.origin.y;
988 if (newFrame.origin.y < maxFrame.origin.y)
989 newFrame.origin.y = maxFrame.origin.y;
990 if (NSMaxX(newFrame) > NSMaxX(maxFrame))
991 newFrame.origin.x = NSMaxX(maxFrame) - newFrame.size.width;
995 [decoratedWindow setFrame:newFrame display:YES];
998 - (NSSize)constrainContentSizeToScreenSize:(NSSize)contentSize
1000 NSWindow *win = [self window];
1004 // NOTE: This may be called in both windowed and full-screen mode. The
1005 // "visibleFrame" method does not overlap menu and dock so should not be
1006 // used in full-screen.
1007 NSRect screenRect = fullscreenEnabled ? [[win screen] frame]
1008 : [[win screen] visibleFrame];
1009 NSRect rect = [win contentRectForFrameRect:screenRect];
1011 if (contentSize.height > rect.size.height)
1012 contentSize.height = rect.size.height;
1013 if (contentSize.width > rect.size.width)
1014 contentSize.width = rect.size.width;
1019 - (NSRect)constrainFrame:(NSRect)frame
1021 // Constrain the given (window) frame so that it fits an even number of
1022 // rows and columns.
1023 NSRect contentRect = [decoratedWindow contentRectForFrameRect:frame];
1024 NSSize constrainedSize = [vimView constrainRows:NULL
1026 toSize:contentRect.size];
1028 contentRect.origin.y += contentRect.size.height - constrainedSize.height;
1029 contentRect.size = constrainedSize;
1031 return [decoratedWindow frameRectForContentRect:contentRect];
1034 - (void)updateResizeConstraints
1036 if (!setupDone) return;
1038 // Set the resize increments to exactly match the font size; this way the
1039 // window will always hold an integer number of (rows,columns).
1040 NSSize cellSize = [[vimView textView] cellSize];
1041 [decoratedWindow setContentResizeIncrements:cellSize];
1043 NSSize minSize = [vimView minSize];
1044 [decoratedWindow setContentMinSize:minSize];
1047 - (NSTabViewItem *)addNewTabViewItem
1049 return [vimView addNewTabViewItem];
1052 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb
1054 // TODO: Can this be done with evaluateExpression: instead?
1056 id backendProxy = [vimController backendProxy];
1060 reply = [backendProxy starRegisterToPasteboard:pb];
1062 @catch (NSException *ex) {
1063 ASLogDebug(@"starRegisterToPasteboard: failed: pid=%d reason=%@",
1064 [vimController pid], ex);
1071 - (void)hideTablineSeparator:(BOOL)hide
1073 // The full-screen window has no tabline separator so we operate on
1074 // decoratedWindow instead of [self window].
1075 if ([decoratedWindow hideTablineSeparator:hide]) {
1076 // The tabline separator was toggled so the content view must change
1078 [self updateResizeConstraints];
1079 shouldResizeVimView = YES;
1083 - (void)doFindNext:(BOOL)next
1085 NSString *query = nil;
1088 // Use current query if the search field is selected.
1089 id searchField = [[self searchFieldItem] view];
1090 if (searchField && [[searchField stringValue] length] > 0 &&
1091 [decoratedWindow firstResponder] == [searchField currentEditor])
1092 query = [searchField stringValue];
1096 // Use find pasteboard for next query.
1097 NSPasteboard *pb = [NSPasteboard pasteboardWithName:NSFindPboard];
1098 NSArray *supportedTypes = [NSArray arrayWithObjects:VimFindPboardType,
1099 NSStringPboardType, nil];
1100 NSString *bestType = [pb availableTypeFromArray:supportedTypes];
1102 // See gui_macvim_add_to_find_pboard() for an explanation of these
1104 if ([bestType isEqual:VimFindPboardType])
1105 query = [pb stringForType:VimFindPboardType];
1107 query = [pb stringForType:NSStringPboardType];
1110 NSString *input = nil;
1112 // NOTE: The '/' register holds the last search string. By setting it
1113 // (using the '@/' syntax) we fool Vim into thinking that it has
1114 // already searched for that string and then we can simply use 'n' or
1115 // 'N' to find the next/previous match.
1116 input = [NSString stringWithFormat:@"<C-\\><C-N>:let @/='%@'<CR>%c",
1117 query, next ? 'n' : 'N'];
1119 input = next ? @"<C-\\><C-N>n" : @"<C-\\><C-N>N";
1122 [vimController addVimInput:input];
1125 - (void)updateToolbar
1127 NSToolbar *toolbar = [decoratedWindow toolbar];
1128 if (nil == toolbar || 0 == updateToolbarFlag) return;
1130 // Positive flag shows toolbar, negative hides it.
1131 BOOL on = updateToolbarFlag > 0 ? YES : NO;
1132 [toolbar setVisible:on];
1134 if (([decoratedWindow styleMask] & NSTexturedBackgroundWindowMask) == 0) {
1136 [self hideTablineSeparator:YES];
1138 [self hideTablineSeparator:![[vimView tabBarControl] isHidden]];
1141 // Textured windows don't have a line below there title bar, so we
1142 // need the separator in this case as well. In fact, the only case
1143 // where we don't need the separator is when the tab bar control
1144 // is visible (because it brings its own separator).
1145 [self hideTablineSeparator:![[vimView tabBarControl] isHidden]];
1148 updateToolbarFlag = 0;
1151 @end // MMWindowController (Private)