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