Find Next/Previous and Use Selection for Find menus
[MacVim.git] / src / MacVim / MMWindowController.m
blob3a284936b88d3cfe79748d5e629e4bbdd29ff22f
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
6  * Do ":help uganda"  in Vim to read copying and usage conditions.
7  * Do ":help credits" in Vim to see a list of people who contributed.
8  * See README.txt for an overview of the Vim source code.
9  */
11  * MMWindowController
12  *
13  * Handles resizing of windows, acts as an mediator between MMVimView and
14  * MMVimController.
15  *
16  * Resizing in windowed mode:
17  *
18  * In windowed mode resizing can occur either due to the window frame changing
19  * size (e.g. when the user drags to resize), or due to Vim changing the number
20  * of (rows,columns).  The former case is dealt with by letting the vim view
21  * fill the entire content view when the window has resized.  In the latter
22  * case we ensure that vim view fits on the screen.
23  *
24  * The vim view notifies Vim if the number of (rows,columns) does not match the
25  * current number whenver the view size is about to change.  Upon receiving a
26  * dimension change message, Vim notifies the window controller and the window
27  * resizes.  However, the window is never resized programmatically during a
28  * live resize (in order to avoid jittering).
29  *
30  * The window size is constrained to not become too small during live resize,
31  * and it is also constrained to always fit an integer number of
32  * (rows,columns).
33  *
34  * In windowed mode we have to manually draw a tabline separator (due to bugs
35  * in the way Cocoa deals with the toolbar separator) when certain conditions
36  * are met.  The rules for this are as follows:
37  *
38  *   Tabline visible & Toolbar visible  =>  Separator visible
39  *   =====================================================================
40  *         NO        &        NO        =>  YES, if the window is textured
41  *                                           NO, otherwise
42  *         NO        &       YES        =>  YES
43  *        YES        &        NO        =>   NO
44  *        YES        &       YES        =>   NO
45  *
46  *
47  * Resizing in full-screen mode:
48  *
49  * The window never resizes since it fills the screen, however the vim view may
50  * change size, e.g. when the user types ":set lines=60", or when a scrollbar
51  * is toggled.
52  *
53  * It is ensured that the vim view never becomes larger than the screen size
54  * and that it always stays in the center of the screen.
55  *  
56  */
58 #import "MMWindowController.h"
59 #import "MMAppController.h"
60 #import "MMAtsuiTextView.h"
61 #import "MMFullscreenWindow.h"
62 #import "MMTextView.h"
63 #import "MMTypesetter.h"
64 #import "MMVimController.h"
65 #import "MMVimView.h"
66 #import "MMWindow.h"
67 #import "MacVim.h"
69 #import <PSMTabBarControl.h>
73 @interface MMWindowController (Private)
74 - (NSSize)contentSize;
75 - (void)resizeWindowToFitContentSize:(NSSize)contentSize;
76 - (NSSize)constrainContentSizeToScreenSize:(NSSize)contentSize;
77 - (void)updateResizeConstraints;
78 - (NSTabViewItem *)addNewTabViewItem;
79 - (IBAction)vimMenuItemAction:(id)sender;
80 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb;
81 - (void)hideTablineSeparator:(BOOL)hide;
82 - (void)doFindNext:(BOOL)next;
83 @end
86 @interface NSWindow (NSWindowPrivate)
87 // Note: This hack allows us to set content shadowing separately from
88 // the window shadow.  This is apparently what webkit and terminal do.
89 - (void)_setContentHasShadow:(BOOL)shadow; // new Tiger private method
91 // This is a private api that makes textured windows not have rounded corners.
92 // We want this on Leopard.
93 - (void)setBottomCornerRounded:(BOOL)rounded;
94 @end
97 @interface NSWindow (NSLeopardOnly)
98 // Note: These functions are Leopard-only, use -[NSObject respondsToSelector:]
99 // before calling them to make sure everything works on Tiger too.
101 #ifndef CGFLOAT_DEFINED
102     // On Leopard, CGFloat is float on 32bit and double on 64bit. On Tiger,
103     // we can't use this anyways, so it's just here to keep the compiler happy.
104     // However, when we're compiling for Tiger and running on Leopard, we
105     // might need the correct typedef, so this piece is copied from ATSTypes.h
106 # ifdef __LP64__
107     typedef double CGFloat;
108 # else
109     typedef float CGFloat;
110 # endif
111 #endif
112 - (void)setAutorecalculatesContentBorderThickness:(BOOL)b forEdge:(NSRectEdge)e;
113 - (void)setContentBorderThickness:(CGFloat)b forEdge:(NSRectEdge)e;
114 @end
119 @implementation MMWindowController
121 - (id)initWithVimController:(MMVimController *)controller
123 #ifndef NSAppKitVersionNumber10_4  // needed for non-10.5 sdk
124 # define NSAppKitVersionNumber10_4 824
125 #endif
126     unsigned styleMask = NSTitledWindowMask | NSClosableWindowMask
127             | NSMiniaturizableWindowMask | NSResizableWindowMask
128             | NSUnifiedTitleAndToolbarWindowMask;
130     // Use textured background on Leopard or later (skip the 'if' on Tiger for
131     // polished metal window).
132     if ([[NSUserDefaults standardUserDefaults] boolForKey:MMTexturedWindowKey]
133             || (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_4))
134         styleMask |= NSTexturedBackgroundWindowMask;
136     // NOTE: The content rect is only used the very first time MacVim is
137     // started (or rather, when ~/Library/Preferences/org.vim.MacVim.plist does
138     // not exist).  The chosen values will put the window somewhere near the
139     // top and in the middle of a 1024x768 screen.
140     MMWindow *win = [[MMWindow alloc]
141             initWithContentRect:NSMakeRect(242,364,480,360)
142                       styleMask:styleMask
143                         backing:NSBackingStoreBuffered
144                           defer:YES];
145     [win autorelease];
147     self = [super initWithWindow:win];
148     if (!self) return nil;
150     vimController = controller;
151     decoratedWindow = [win retain];
153     // Window cascading is handled by MMAppController.
154     [self setShouldCascadeWindows:NO];
156     // NOTE: Autoresizing is enabled for the content view, but only used
157     // for the tabline separator.  The vim view must be resized manually
158     // because of full-screen considerations, and because its size depends
159     // on whether the tabline separator is visible or not.
160     NSView *contentView = [win contentView];
161     [contentView setAutoresizesSubviews:YES];
163     vimView = [[MMVimView alloc] initWithFrame:[contentView frame]
164                                  vimController:vimController];
165     [vimView setAutoresizingMask:NSViewNotSizable];
166     [contentView addSubview:vimView];
168     [win setDelegate:self];
169     [win setInitialFirstResponder:[vimView textView]];
170     
171     if ([win styleMask] & NSTexturedBackgroundWindowMask) {
172         // On Leopard, we want to have a textured window to have nice
173         // looking tabs. But the textured window look implies rounded
174         // corners, which looks really weird -- disable them. This is a
175         // private api, though.
176         if ([win respondsToSelector:@selector(setBottomCornerRounded:)])
177             [win setBottomCornerRounded:NO];
179         // When the tab bar is toggled, it changes color for the fraction
180         // of a second, probably because vim sends us events in a strange
181         // order, confusing appkit's content border heuristic for a short
182         // while.  This can be worked around with these two methods.  There
183         // might be a better way, but it's good enough.
184         if ([win respondsToSelector:@selector(
185                 setAutorecalculatesContentBorderThickness:forEdge:)])
186             [win setAutorecalculatesContentBorderThickness:NO
187                                                    forEdge:NSMaxYEdge];
188         if ([win respondsToSelector:
189                 @selector(setContentBorderThickness:forEdge:)])
190             [win setContentBorderThickness:0 forEdge:NSMaxYEdge];
191     }
193     // Make us safe on pre-tiger OSX
194     if ([win respondsToSelector:@selector(_setContentHasShadow:)])
195         [win _setContentHasShadow:NO];
197     return self;
200 - (void)dealloc
202     //NSLog(@"%@ %s", [self className], _cmd);
204     [decoratedWindow release];  decoratedWindow = nil;
205     [windowAutosaveKey release];  windowAutosaveKey = nil;
206     [vimView release];  vimView = nil;
208     [super dealloc];
211 - (NSString *)description
213     NSString *format =
214         @"%@ : setupDone=%d windowAutosaveKey=%@ vimController=%@";
215     return [NSString stringWithFormat:format,
216         [self className], setupDone, windowAutosaveKey, vimController];
219 - (MMVimController *)vimController
221     return vimController;
224 - (MMVimView *)vimView
226     return vimView;
229 - (NSString *)windowAutosaveKey
231     return windowAutosaveKey;
234 - (void)setWindowAutosaveKey:(NSString *)key
236     [windowAutosaveKey autorelease];
237     windowAutosaveKey = [key copy];
240 - (void)cleanup
242     //NSLog(@"%@ %s", [self className], _cmd);
244     if (fullscreenEnabled) {
245         // If we are closed while still in fullscreen, end fullscreen mode,
246         // release ourselves (because this won't happen in MMWindowController)
247         // and perform close operation on the original window.
248         [self leaveFullscreen];
249     }
251     setupDone = NO;
252     vimController = nil;
254     [vimView removeFromSuperviewWithoutNeedingDisplay];
255     [vimView cleanup];
257     // It is feasible (though unlikely) that the user quits before the window
258     // controller is released, make sure the edit flag is cleared so no warning
259     // dialog is displayed.
260     [decoratedWindow setDocumentEdited:NO];
262     [[self window] orderOut:self];
265 - (void)openWindow
267     [[NSApp delegate] windowControllerWillOpen:self];
269     [self addNewTabViewItem];
271     setupDone = YES;
273     [self updateResizeConstraints];
274     [self resizeWindowToFitContentSize:[vimView desiredSize]];
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 live:(BOOL)live
290     //NSLog(@"setTextDimensionsWithRows:%d columns:%d live:%s", rows, cols,
291     //        live ? "YES" : "NO");
293     // NOTE: This is the only place where the (rows,columns) of the vim view
294     // are modified.  Setting these values have no immediate effect, the actual
295     // resizing of the view is done in processCommandQueueDidFinish.
296     //
297     // The 'live' flag indicates that this resize originated from a live
298     // resize; it may very well happen that the view is no longer in live
299     // resize when this message is received.  We refrain from changing the view
300     // size when this flag is set, otherwise the window might jitter when the
301     // user drags to resize the window.
303     [vimView setDesiredRows:rows columns:cols];
305     if (setupDone && !live)
306         shouldResizeVimView = YES;
309 - (void)setTitle:(NSString *)title
311     if (title) {
312         [decoratedWindow setTitle:title];
313         [fullscreenWindow setTitle:title];
314     }
317 - (void)setToolbar:(NSToolbar *)toolbar
319     // The full-screen window has no toolbar.
320     [decoratedWindow setToolbar:toolbar];
322     // HACK! Redirect the pill button so that we can ask Vim to hide the
323     // toolbar.
324     NSButton *pillButton = [decoratedWindow
325             standardWindowButton:NSWindowToolbarButton];
326     if (pillButton) {
327         [pillButton setAction:@selector(toggleToolbar:)];
328         [pillButton setTarget:self];
329     }
332 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
334     [vimView createScrollbarWithIdentifier:ident type:type];
337 - (BOOL)destroyScrollbarWithIdentifier:(long)ident
339     BOOL scrollbarHidden = [vimView destroyScrollbarWithIdentifier:ident];   
340     shouldResizeVimView = shouldResizeVimView || scrollbarHidden;
342     return scrollbarHidden;
345 - (BOOL)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
347     BOOL scrollbarToggled = [vimView showScrollbarWithIdentifier:ident
348                                                            state:visible];
349     shouldResizeVimView = shouldResizeVimView || scrollbarToggled;
351     return scrollbarToggled;
354 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
356     [vimView setScrollbarPosition:pos length:len identifier:ident];
359 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
360                     identifier:(long)ident
362     [vimView setScrollbarThumbValue:val proportion:prop identifier:ident];
365 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
367     // NOTE: This is called when the transparency changes so set the opacity
368     // flag on the window here (should be faster if the window is opaque).
369     BOOL isOpaque = [back alphaComponent] == 1.0f;
370     [[self window] setOpaque:isOpaque];
372     [vimView setDefaultColorsBackground:back foreground:fore];
375 - (void)setFont:(NSFont *)font
377     [[NSFontManager sharedFontManager] setSelectedFont:font isMultiple:NO];
378     [[vimView textView] setFont:font];
379     [self updateResizeConstraints];
382 - (void)setWideFont:(NSFont *)font
384     [[vimView textView] setWideFont:font];
387 - (void)processCommandQueueDidFinish
389     // NOTE: Resizing is delayed until after all commands have been processed
390     // since it often happens that more than one command will cause a resize.
391     // If we were to immediately resize then the vim view size would jitter
392     // (e.g.  hiding/showing scrollbars often happens several time in one
393     // update).
395     if (shouldResizeVimView) {
396         shouldResizeVimView = NO;
398         NSSize contentSize = [vimView desiredSize];
399         contentSize = [self constrainContentSizeToScreenSize:contentSize];
400         contentSize = [vimView constrainRows:NULL columns:NULL
401                                       toSize:contentSize];
402         [vimView setFrameSize:contentSize];
404         if (fullscreenEnabled) {
405             [[fullscreenWindow contentView] setNeedsDisplay:YES];
406             [fullscreenWindow centerView];
407         } else {
408             [self resizeWindowToFitContentSize:contentSize];
409         }
410     }
413 - (void)popupMenu:(NSMenu *)menu atRow:(int)row column:(int)col
415     if (!setupDone) return;
417     NSEvent *event;
418     if (row >= 0 && col >= 0) {
419         // TODO: Let textView convert (row,col) to NSPoint.
420         NSSize cellSize = [[vimView textView] cellSize];
421         NSPoint pt = { (col+1)*cellSize.width, (row+1)*cellSize.height };
422         pt = [[vimView textView] convertPoint:pt toView:nil];
424         event = [NSEvent mouseEventWithType:NSRightMouseDown
425                                    location:pt
426                               modifierFlags:0
427                                   timestamp:0
428                                windowNumber:[[self window] windowNumber]
429                                     context:nil
430                                 eventNumber:0
431                                  clickCount:0
432                                    pressure:1.0];
433     } else {
434         event = [[vimView textView] lastMouseDownEvent];
435     }
437     [NSMenu popUpContextMenu:menu withEvent:event forView:[vimView textView]];
440 - (void)showTabBar:(BOOL)on
442     [[vimView tabBarControl] setHidden:!on];
444     // Showing the tabline may result in the tabline separator being hidden or
445     // shown; this does not apply to full-screen mode.
446     if (!on) {
447         NSToolbar *toolbar = [decoratedWindow toolbar]; 
448         if (([decoratedWindow styleMask] & NSTexturedBackgroundWindowMask)
449                 == 0) {
450             [self hideTablineSeparator:![toolbar isVisible]];
451         } else {
452             [self hideTablineSeparator:NO];
453         }
454     } else {
455         if (([decoratedWindow styleMask] & NSTexturedBackgroundWindowMask)
456                 == 0) {
457             [self hideTablineSeparator:on];
458         } else {
459             [self hideTablineSeparator:YES];
460         }
461     }
464 - (void)showToolbar:(BOOL)on size:(int)size mode:(int)mode
466     NSToolbar *toolbar = [decoratedWindow toolbar];
467     if (!toolbar) return;
469     [toolbar setSizeMode:size];
470     [toolbar setDisplayMode:mode];
471     [toolbar setVisible:on];
473     if (([decoratedWindow styleMask] & NSTexturedBackgroundWindowMask) == 0) {
474         if (!on) {
475             [self hideTablineSeparator:YES];
476         } else {
477             [self hideTablineSeparator:![[vimView tabBarControl] isHidden]];
478         }
479     } else {
480         // Textured windows don't have a line below there title bar, so we
481         // need the separator in this case as well. In fact, the only case
482         // where we don't need the separator is when the tab bar control
483         // is visible (because it brings its own separator).
484         [self hideTablineSeparator:![[vimView tabBarControl] isHidden]];
485     }
488 - (void)setMouseShape:(int)shape
490     // This switch should match mshape_names[] in misc2.c.
491     //
492     // TODO: Add missing cursor shapes.
493     switch (shape) {
494         case 2: [[NSCursor IBeamCursor] set]; break;
495         case 3: case 4: [[NSCursor resizeUpDownCursor] set]; break;
496         case 5: case 6: [[NSCursor resizeLeftRightCursor] set]; break;
497         case 9: [[NSCursor crosshairCursor] set]; break;
498         case 10: [[NSCursor pointingHandCursor] set]; break;
499         case 11: [[NSCursor openHandCursor] set]; break;
500         default:
501             [[NSCursor arrowCursor] set]; break;
502     }
504     // Shape 1 indicates that the mouse cursor should be hidden.
505     if (1 == shape)
506         [NSCursor setHiddenUntilMouseMoves:YES];
509 - (void)adjustLinespace:(int)linespace
511     if (vimView && [vimView textView]) {
512         [[vimView textView] setLinespace:(float)linespace];
513         shouldResizeVimView = YES;
514     }
517 - (void)liveResizeWillStart
519     // Save the original title, if we haven't already.
520     if (lastSetTitle == nil) {
521         lastSetTitle = [[decoratedWindow title] retain];
522     }
525 - (void)liveResizeDidEnd
527     if (!setupDone) return;
529     // NOTE: During live resize messages from MacVim to Vim are often dropped
530     // (because too many messages are sent at once).  This may lead to
531     // inconsistent states between Vim and MacVim; to avoid this we send a
532     // synchronous resize message to Vim now (this is not fool-proof, but it
533     // does seem to work quite well).
535     int constrained[2];
536     NSSize textViewSize = [[vimView textView] frame].size;
537     [[vimView textView] constrainRows:&constrained[0] columns:&constrained[1]
538                                toSize:textViewSize];
540     //NSLog(@"End of live resize, notify Vim that text dimensions are %dx%d",
541     //       constrained[1], constrained[0]);
543     NSData *data = [NSData dataWithBytes:constrained length:2*sizeof(int)];
544     BOOL sendOk = [vimController sendMessageNow:SetTextDimensionsMsgID
545                                            data:data
546                                         timeout:.5];
548     if (!sendOk) {
549         // Sending of synchronous message failed.  Force the window size to
550         // match the last dimensions received from Vim, otherwise we end up
551         // with inconsistent states.
552         [self resizeWindowToFitContentSize:[vimView desiredSize]];
553     }
555     // If we saved the original title while resizing, restore it.
556     if (lastSetTitle != nil) {
557         [decoratedWindow setTitle:lastSetTitle];
558         [lastSetTitle release];
559         lastSetTitle = nil;
560     }
563 - (void)enterFullscreen
565     if (fullscreenEnabled) return;
567     fullscreenWindow = [[MMFullscreenWindow alloc]
568             initWithWindow:decoratedWindow view:vimView];
569     [fullscreenWindow enterFullscreen];    
570     [fullscreenWindow setDelegate:self];
571     fullscreenEnabled = YES;
573     // The resize handle disappears so the vim view needs to update the
574     // scrollbars.
575     shouldResizeVimView = YES;
578 - (void)leaveFullscreen
580     if (!fullscreenEnabled) return;
582     fullscreenEnabled = NO;
583     [fullscreenWindow leaveFullscreen];    
584     [fullscreenWindow release];
585     fullscreenWindow = nil;
587     // The vim view may be too large to fit the screen, so update it.
588     shouldResizeVimView = YES;
591 - (void)setBuffersModified:(BOOL)mod
593     // NOTE: We only set the document edited flag on the decorated window since
594     // the full-screen window has no close button anyway.  (It also saves us
595     // from keeping track of the flag in two different places.)
596     [decoratedWindow setDocumentEdited:mod];
600 - (IBAction)addNewTab:(id)sender
602     [vimView addNewTab:sender];
605 - (IBAction)toggleToolbar:(id)sender
607     [vimController sendMessage:ToggleToolbarMsgID data:nil];
610 - (IBAction)performClose:(id)sender
612     // NOTE: File->Close is bound to this action message instead binding it
613     // directly to the below vim input so that File->Close also works for
614     // auxiliary windows such as the About dialog.  (If we were to bind the
615     // below, then <D-w> will not close e.g. the About dialog.)
616     [vimController addVimInput:@"<C-\\><C-N>:conf q<CR>"];
619 - (IBAction)findNext:(id)sender
621     [self doFindNext:YES];
624 - (IBAction)findPrevious:(id)sender
626     [self doFindNext:NO];
630 // -- NSWindow delegate ------------------------------------------------------
632 - (void)windowDidBecomeMain:(NSNotification *)notification
634     [vimController updateMainMenu];
635     [vimController sendMessage:GotFocusMsgID data:nil];
637     if ([vimView textView]) {
638         NSFontManager *fm = [NSFontManager sharedFontManager];
639         [fm setSelectedFont:[[vimView textView] font] isMultiple:NO];
640     }
643 - (void)windowDidResignMain:(NSNotification *)notification
645     [vimController sendMessage:LostFocusMsgID data:nil];
647     if ([vimView textView])
648         [[vimView textView] hideMarkedTextField];
651 - (BOOL)windowShouldClose:(id)sender
653     // Don't close the window now; Instead let Vim decide whether to close the
654     // window or not.
655     [vimController sendMessage:VimShouldCloseMsgID data:nil];
656     return NO;
659 - (void)windowDidMove:(NSNotification *)notification
661     if (setupDone && windowAutosaveKey) {
662         NSRect frame = [decoratedWindow frame];
663         NSPoint topLeft = { frame.origin.x, NSMaxY(frame) };
664         NSString *topLeftString = NSStringFromPoint(topLeft);
666         [[NSUserDefaults standardUserDefaults]
667             setObject:topLeftString forKey:windowAutosaveKey];
668     }
671 - (void)windowDidResize:(id)sender
673     if (!setupDone || fullscreenEnabled) return;
675     // NOTE: Since we have no control over when the window may resize (Cocoa
676     // may resize automatically) we simply set the view to fill the entire
677     // window.  The vim view takes care of notifying Vim if the number of
678     // (rows,columns) changed.
679     [vimView setFrameSize:[self contentSize]];
682 - (NSRect)windowWillUseStandardFrame:(NSWindow *)win
683                         defaultFrame:(NSRect)frame
685     // Keep old width and horizontal position unless user clicked while the
686     // Command key is held down.
687     NSEvent *event = [NSApp currentEvent];
688     if (!([event type] == NSLeftMouseUp
689             && [event modifierFlags] & NSCommandKeyMask)) {
690         NSRect currentFrame = [win frame];
691         frame.size.width = currentFrame.size.width;
692         frame.origin.x = currentFrame.origin.x;
693     }
695     return frame;
701 // -- Services menu delegate -------------------------------------------------
703 - (id)validRequestorForSendType:(NSString *)sendType
704                      returnType:(NSString *)returnType
706     if ([sendType isEqual:NSStringPboardType]
707             && [self askBackendForStarRegister:nil])
708         return self;
710     return [super validRequestorForSendType:sendType returnType:returnType];
713 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
714                              types:(NSArray *)types
716     if (![types containsObject:NSStringPboardType])
717         return NO;
719     return [self askBackendForStarRegister:pboard];
722 @end // MMWindowController
726 @implementation MMWindowController (Private)
728 - (NSSize)contentSize
730     // NOTE: Never query the content view directly for its size since it may
731     // not return the same size as contentRectForFrameRect: (e.g. when in
732     // windowed mode and the tabline separator is visible)!
733     NSWindow *win = [self window];
734     return [win contentRectForFrameRect:[win frame]].size;
737 - (void)resizeWindowToFitContentSize:(NSSize)contentSize
739     NSRect frame = [decoratedWindow frame];
740     NSRect contentRect = [decoratedWindow contentRectForFrameRect:frame];
742     // Keep top-left corner of the window fixed when resizing.
743     contentRect.origin.y -= contentSize.height - contentRect.size.height;
744     contentRect.size = contentSize;
746     frame = [decoratedWindow frameRectForContentRect:contentRect];
747     [decoratedWindow setFrame:frame display:YES];
750 - (NSSize)constrainContentSizeToScreenSize:(NSSize)contentSize
752     NSWindow *win = [self window];
753     NSRect rect = [win contentRectForFrameRect:[[win screen] visibleFrame]];
755     if (contentSize.height > rect.size.height)
756         contentSize.height = rect.size.height;
757     if (contentSize.width > rect.size.width)
758         contentSize.width = rect.size.width;
760     return contentSize;
763 - (void)updateResizeConstraints
765     if (!setupDone) return;
767     // Set the resize increments to exactly match the font size; this way the
768     // window will always hold an integer number of (rows,columns).
769     NSSize cellSize = [[vimView textView] cellSize];
770     [decoratedWindow setContentResizeIncrements:cellSize];
772     NSSize minSize = [vimView minSize];
773     [decoratedWindow setContentMinSize:minSize];
776 - (NSTabViewItem *)addNewTabViewItem
778     return [vimView addNewTabViewItem];
781 - (IBAction)vimMenuItemAction:(id)sender
783     int tag = [sender tag];
785     NSMutableData *data = [NSMutableData data];
786     [data appendBytes:&tag length:sizeof(int)];
788     [vimController sendMessage:ExecuteMenuMsgID data:data];
791 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb
793     // TODO: Can this be done with evaluateExpression: instead?
794     BOOL reply = NO;
795     id backendProxy = [vimController backendProxy];
797     if (backendProxy) {
798         @try {
799             reply = [backendProxy starRegisterToPasteboard:pb];
800         }
801         @catch (NSException *e) {
802             NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
803         }
804     }
806     return reply;
809 - (void)hideTablineSeparator:(BOOL)hide
811     // The full-screen window has no tabline separator so we operate on
812     // decoratedWindow instead of [self window].
813     if ([decoratedWindow hideTablineSeparator:hide]) {
814         // The tabline separator was toggled so the content view must change
815         // size.
816         [self updateResizeConstraints];
817         shouldResizeVimView = YES;
818     }
821 - (void)doFindNext:(BOOL)next
823     NSString *query = nil;
825 #if 0
826     // Use current query if the search field is selected.
827     id searchField = [[self searchFieldItem] view];
828     if (searchField && [[searchField stringValue] length] > 0 &&
829             [decoratedWindow firstResponder] == [searchField currentEditor])
830         query = [searchField stringValue];
831 #endif
833     if (!query) {
834         // Use find pasteboard for next query.
835         NSPasteboard *pb = [NSPasteboard pasteboardWithName:NSFindPboard];
836         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
837         if ([pb availableTypeFromArray:types])
838             query = [pb stringForType:NSStringPboardType];
839     }
841     NSString *input = nil;
842     if (query) {
843         // NOTE: The '/' register holds the last search string.  By setting it
844         // (using the '@/' syntax) we fool Vim into thinking that it has
845         // already searched for that string and then we can simply use 'n' or
846         // 'N' to find the next/previous match.
847         input = [NSString stringWithFormat:@"<C-\\><C-N>:let @/='%@'<CR>%c",
848                 query, next ? 'n' : 'N'];
849     } else {
850         input = next ? @"<C-\\><C-N>n" : @"<C-\\><C-N>N"; 
851     }
853     [vimController addVimInput:input];
856 @end // MMWindowController (Private)