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
17 #import "MMWindowController.h"
18 #import <PSMTabBarControl.h>
19 #import "MMTextView.h"
20 #import "MMTextStorage.h"
21 #import "MMVimController.h"
23 #import "MMAppController.h"
24 #import "MMTypesetter.h"
25 #import "MMFullscreenWindow.h"
30 @interface MMWindowController (Private)
31 - (NSSize)contentSize;
32 - (NSRect)contentRectForFrameRect:(NSRect)frame;
33 - (NSRect)frameRectForContentRect:(NSRect)contentRect;
34 - (void)resizeWindowToFit:(id)sender;
35 - (NSRect)fitWindowToFrame:(NSRect)frame;
36 - (void)updateResizeIncrements;
37 - (NSTabViewItem *)addNewTabViewItem;
38 - (IBAction)vimMenuItemAction:(id)sender;
39 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb;
40 - (void)checkWindowNeedsResizing;
41 - (NSSize)resizeVimViewToFitSize:(NSSize)size;
47 NSString *buildMenuItemDescriptor(NSMenu *menu, NSString *tail)
49 return menu ? buildMenuItemDescriptor([menu supermenu], [[menu title]
50 stringByAppendingString:tail])
54 NSMutableArray *buildMenuAddress(NSMenu *menu)
58 addr = buildMenuAddress([menu supermenu]);
59 [addr addObject:[menu title]];
61 addr = [NSMutableArray array];
68 // Note: This hack allows us to set content shadowing separately from
69 // the window shadow. This is apparently what webkit and terminal do.
70 @interface NSWindow (NSWindowPrivate) // new Tiger private method
71 - (void)_setContentHasShadow:(BOOL)shadow;
75 @implementation MMWindowController
77 - (id)initWithVimController:(MMVimController *)controller
79 if ((self = [super initWithWindowNibName:@"EmptyWindow"])) {
80 vimController = controller;
82 // Window cascading is handled by MMAppController.
83 [self setShouldCascadeWindows:NO];
85 NSWindow *win = [self window];
86 NSView *contentView = [win contentView];
87 vimView = [[MMVimView alloc] initWithFrame:[contentView frame]
88 vimController:vimController];
89 [contentView addSubview:vimView];
90 // [vimView translateOriginToPoint:
91 // NSMakePoint([contentView frame].size.width, 0)];
92 // [vimView rotateByAngle:45.f];
94 // Create the tabline separator (which may be visible when the tabline
96 NSRect tabSepRect = [contentView frame];
97 tabSepRect.origin.y = NSMaxY(tabSepRect)-1;
98 tabSepRect.size.height = 1;
99 tablineSeparator = [[NSBox alloc] initWithFrame:tabSepRect];
101 [tablineSeparator setBoxType:NSBoxSeparator];
102 [tablineSeparator setHidden:NO];
103 [tablineSeparator setAutoresizingMask:NSViewWidthSizable
106 [contentView setAutoresizesSubviews:YES];
107 [contentView addSubview:tablineSeparator];
109 [win setDelegate:self];
110 [win setInitialFirstResponder:[vimView textView]];
112 // Make us safe on pre-tiger OSX
113 if ([win respondsToSelector:@selector(_setContentHasShadow:)])
114 [win _setContentHasShadow:NO];
116 NSLog(@"initWithVimController done.");
124 //NSLog(@"%@ %s", [self className], _cmd);
126 [tablineSeparator release]; tablineSeparator = nil;
127 [windowAutosaveKey release]; windowAutosaveKey = nil;
128 [vimView release]; vimView = nil;
133 - (NSString *)description
136 @"%@ : setupDone=%d windowAutosaveKey=%@ vimController=%@";
137 return [NSString stringWithFormat:format,
138 [self className], setupDone, windowAutosaveKey, vimController];
141 - (MMVimController *)vimController
143 return vimController;
146 - (MMTextView *)textView
148 return [vimView textView];
151 - (MMTextStorage *)textStorage
153 return [vimView textStorage];
156 - (MMVimView *)vimView
161 - (NSString *)windowAutosaveKey
163 return windowAutosaveKey;
166 - (void)setWindowAutosaveKey:(NSString *)key
168 [windowAutosaveKey autorelease];
169 windowAutosaveKey = [key copy];
174 //NSLog(@"%@ %s", [self className], _cmd);
176 if (fullscreenWindow != nil) {
177 // if we are closed while still in fullscreen, end fullscreen mode,
178 // release ourselves (because this won't happen in MMWindowController)
179 // and perform close operation on the original window
180 [self leaveFullscreen];
187 [tablineSeparator removeFromSuperviewWithoutNeedingDisplay];
188 [vimView removeFromSuperviewWithoutNeedingDisplay];
189 [vimView cleanup]; // TODO: is this necessary?
191 [[self window] orderOut:self];
196 [[NSApp delegate] windowControllerWillOpen:self];
198 [self addNewTabViewItem];
202 [self updateResizeIncrements];
203 [self resizeWindowToFit:self];
204 [[self window] makeKeyAndOrderFront:self];
207 - (void)updateTabsWithData:(NSData *)data
209 [vimView updateTabsWithData:data];
212 - (void)selectTabWithIndex:(int)idx
214 [vimView selectTabWithIndex:idx];
217 - (void)setTextDimensionsWithRows:(int)rows columns:(int)cols
219 //NSLog(@"setTextDimensionsWithRows:%d columns:%d", rows, cols);
221 [vimView setActualRows:rows columns:cols];
223 if (setupDone && ![vimView inLiveResize])
224 shouldUpdateWindowSize = YES;
227 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
229 [vimView createScrollbarWithIdentifier:ident type:type];
232 - (void)destroyScrollbarWithIdentifier:(long)ident
234 [vimView destroyScrollbarWithIdentifier:ident];
235 [self checkWindowNeedsResizing];
238 - (void)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
240 [vimView showScrollbarWithIdentifier:ident state:visible];
241 [self checkWindowNeedsResizing];
244 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
246 [vimView setScrollbarPosition:pos length:len identifier:ident];
249 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
250 identifier:(long)ident
252 [vimView setScrollbarThumbValue:val proportion:prop identifier:ident];
255 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
257 // NOTE: This is called when the transparency changes so set the opacity
258 // flag on the window here (should be faster if the window is opaque).
259 BOOL isOpaque = [back alphaComponent] == 1.0f;
260 [[self window] setOpaque:isOpaque];
262 [vimView setDefaultColorsBackground:back foreground:fore];
265 - (void)setFont:(NSFont *)font
267 [[NSFontManager sharedFontManager] setSelectedFont:font isMultiple:NO];
268 [[vimView textStorage] setFont:font];
269 [self updateResizeIncrements];
272 - (void)setWideFont:(NSFont *)font
274 [[vimView textStorage] setWideFont:font];
277 - (void)processCommandQueueDidFinish
279 // XXX: If not in live resize and vimview's desired size differs from actual
280 // size, resize ourselves
281 if (shouldUpdateWindowSize) {
282 shouldUpdateWindowSize = NO;
283 [vimView setShouldUpdateWindowSize:NO];
284 [self resizeWindowToFit:self];
288 - (void)popupMenu:(NSMenu *)menu atRow:(int)row column:(int)col
290 if (!setupDone) return;
293 if (row >= 0 && col >= 0) {
294 NSSize cellSize = [[vimView textStorage] cellSize];
295 NSPoint pt = { (col+1)*cellSize.width, (row+1)*cellSize.height };
296 pt = [[vimView textView] convertPoint:pt toView:nil];
298 event = [NSEvent mouseEventWithType:NSRightMouseDown
302 windowNumber:[[self window] windowNumber]
308 event = [[vimView textView] lastMouseDownEvent];
311 [NSMenu popUpContextMenu:menu withEvent:event forView:[vimView textView]];
314 - (void)showTabBar:(BOOL)on
316 [[vimView tabBarControl] setHidden:!on];
319 NSToolbar *toolbar = [[self window] toolbar];
320 [tablineSeparator setHidden:![toolbar isVisible]];
322 [tablineSeparator setHidden:on];
326 // shouldUpdateWindowSize = YES;
329 - (void)showToolbar:(BOOL)on size:(int)size mode:(int)mode
331 NSToolbar *toolbar = [[self window] toolbar];
332 if (!toolbar) return;
334 [toolbar setSizeMode:size];
335 [toolbar setDisplayMode:mode];
336 [toolbar setVisible:on];
339 [tablineSeparator setHidden:YES];
341 [tablineSeparator setHidden:![[vimView tabBarControl] isHidden]];
345 - (void)setMouseShape:(int)shape
347 // This switch should match mshape_names[] in misc2.c.
349 // TODO: Add missing cursor shapes.
351 case 2: [[NSCursor IBeamCursor] set]; break;
352 case 3: case 4: [[NSCursor resizeUpDownCursor] set]; break;
353 case 5: case 6: [[NSCursor resizeLeftRightCursor] set]; break;
354 case 9: [[NSCursor crosshairCursor] set]; break;
355 case 10: [[NSCursor pointingHandCursor] set]; break;
356 case 11: [[NSCursor openHandCursor] set]; break;
358 [[NSCursor arrowCursor] set]; break;
361 // Shape 1 indicates that the mouse cursor should be hidden.
363 [NSCursor setHiddenUntilMouseMoves:YES];
366 - (void)adjustLinespace:(int)linespace
368 if (vimView && [vimView textStorage]) {
369 [[vimView textStorage] setLinespace:(float)linespace];
370 shouldUpdateWindowSize = YES;
374 - (void)liveResizeWillStart
376 // Save the original title, if we haven't already.
377 if (lastSetTitle == nil) {
378 lastSetTitle = [[[self window] title] retain];
382 - (void)liveResizeDidEnd
384 if (!setupDone) return;
386 // At the end of an resize, check if vim view size and number of
387 // columns / rows agree (the first is set while resizing, the second by
388 // messages sent from vim). If not, send a synchronous (!) message to vim
389 // to set columns / rows to the value belonging to the view size. If the
390 // message couldn't be sent, change the view size to fit columns / rows.
392 // NOTE! It is assumed that the window has been resized so that it will
393 // exactly fit the text storage (possibly after resizing it). If this is
394 // not the case the display might be messed up.
395 BOOL resizeFailed = NO;
396 NSSize contentSize = [self contentSize];
399 [vimView getDesiredRows:&desiredSize[0] columns:&desiredSize[1]
400 forSize:contentSize];
403 [vimView getActualRows:&rows columns:&columns];
405 if (desiredSize[0] != rows || desiredSize[1] != columns) {
407 NSData *data = [NSData dataWithBytes:desiredSize length:2*sizeof(int)];
409 // NOTE: Since we're at the end of a live resize we want to make sure
410 // that the SetTextDimensionsMsgID message reaches Vim, else Vim and
411 // MacVim will have inconsistent states (i.e. the text view will be too
412 // large or too small for the window size). Thus, add a timeout (this
413 // may have to be tweaked) and take note if the message was sent or
415 resizeFailed = ![vimController sendMessageNow:SetTextDimensionsMsgID
421 // Force the window size to match the text view size otherwise Vim and
422 // MacVim will have inconsistent states.
423 [self resizeWindowToFit:self];
426 // If we saved the original title while resizing, restore it.
427 if (lastSetTitle != nil) {
428 [[self window] setTitle:lastSetTitle];
429 [lastSetTitle release];
436 if (!setupDone) return;
439 vimViewRect.origin = NSMakePoint(0, 0);
440 vimViewRect.size = [vimView getDesiredRows:NULL columns:NULL
441 forSize:[self contentSize]];
443 // HACK! If the window does resize, then windowDidResize is called which in
444 // turn calls placeViews. In case the computed new size of the window is
445 // no different from the current size, then we need to call placeViews
447 if (NSEqualRects(vimViewRect, [vimView frame])) {
448 [vimView placeViews];
450 [vimView setFrame:vimViewRect];
454 - (void)enterFullscreen
456 fullscreenWindow = [[MMFullscreenWindow alloc] initWithWindow:[self window]
458 [fullscreenWindow enterFullscreen];
460 [fullscreenWindow setDelegate:self];
463 - (void)leaveFullscreen
465 [fullscreenWindow leaveFullscreen];
466 [fullscreenWindow release];
467 fullscreenWindow = nil;
471 - (IBAction)addNewTab:(id)sender
473 [vimView addNewTab:sender];
476 - (IBAction)toggleToolbar:(id)sender
478 [vimController sendMessage:ToggleToolbarMsgID data:nil];
483 // -- NSWindow delegate ------------------------------------------------------
485 - (void)windowDidBecomeMain:(NSNotification *)notification
487 [vimController sendMessage:GotFocusMsgID data:nil];
489 if ([vimView textStorage]) {
490 NSFontManager *fontManager = [NSFontManager sharedFontManager];
491 [fontManager setSelectedFont:[[vimView textStorage] font]
496 - (void)windowDidResignMain:(NSNotification *)notification
498 [vimController sendMessage:LostFocusMsgID data:nil];
500 if ([vimView textView])
501 [[vimView textView] hideMarkedTextField];
504 - (BOOL)windowShouldClose:(id)sender
506 [vimController sendMessage:VimShouldCloseMsgID data:nil];
510 - (void)windowDidMove:(NSNotification *)notification
512 if (setupDone && windowAutosaveKey) {
513 NSRect frame = [[self window] frame];
514 NSPoint topLeft = { frame.origin.x, NSMaxY(frame) };
515 NSString *topLeftString = NSStringFromPoint(topLeft);
517 [[NSUserDefaults standardUserDefaults]
518 setObject:topLeftString forKey:windowAutosaveKey];
522 - (void)windowDidResize:(id)sender
524 if (!setupDone) return;
526 // Live resizing works as follows:
527 // VimView's size is changed immediatly, and a resize message to the
528 // remote vim instance is sent. The remote vim instance sends a
529 // "vim content size changed" right back, but in live resize mode this
530 // doesn't change the VimView (because we assume that it already has the
531 // correct size because we set the resize increments correctly). Afterward,
532 // the remote vim view sends a batch draw for the text visible in the
533 // resized text area.
535 NSSize contentSize = [self contentSize];
536 [self resizeVimViewToFitSize:contentSize];
539 frame.origin = NSMakePoint(0, 0);
540 frame.size = contentSize;
541 [vimView setFrame:frame];
544 - (NSRect)windowWillUseStandardFrame:(NSWindow *)win
545 defaultFrame:(NSRect)frame
547 // HACK! For some reason 'frame' is not always constrained to fit on the
548 // screen (e.g. it may overlap the menu bar), so first constrain it to the
549 // screen; otherwise the new frame we compute may be too large and this
550 // will mess up the display after the window resizes.
551 frame = [win constrainFrameRect:frame toScreen:[win screen]];
553 // HACK! If the top of 'frame' is lower than the current window frame,
554 // increase 'frame' so that their tops align. Really, 'frame' should
555 // already have its top at least as high as the current window frame, but
556 // for some reason this is not always the case.
557 // (See resizeWindowToFit: for a similar hack.)
558 NSRect cur = [win frame];
559 if (NSMaxY(cur) > NSMaxY(frame)) {
560 frame.size.height = cur.origin.y - frame.origin.y + cur.size.height;
563 frame = [self fitWindowToFrame:frame];
565 // Keep old width and horizontal position unless user clicked while the
566 // Command key is held down.
567 NSEvent *event = [NSApp currentEvent];
568 if (!([event type] == NSLeftMouseUp
569 && [event modifierFlags] & NSCommandKeyMask)) {
570 NSRect currentFrame = [win frame];
571 frame.size.width = currentFrame.size.width;
572 frame.origin.x = currentFrame.origin.x;
581 // -- Services menu delegate -------------------------------------------------
583 - (id)validRequestorForSendType:(NSString *)sendType
584 returnType:(NSString *)returnType
586 if ([sendType isEqual:NSStringPboardType]
587 && [self askBackendForStarRegister:nil])
590 return [super validRequestorForSendType:sendType returnType:returnType];
593 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
594 types:(NSArray *)types
596 if (![types containsObject:NSStringPboardType])
599 return [self askBackendForStarRegister:pboard];
602 @end // MMWindowController
606 @implementation MMWindowController (Private)
608 - (NSRect)contentRectForFrameRect:(NSRect)frame
610 NSRect result = [[self window] contentRectForFrameRect:frame];
611 if (![tablineSeparator isHidden])
612 --result.size.height;
616 - (NSRect)frameRectForContentRect:(NSRect)contentRect
618 if (![tablineSeparator isHidden])
619 ++contentRect.size.height;
620 return [[self window] frameRectForContentRect:contentRect];
623 - (NSSize)contentSize
625 return [self contentRectForFrameRect:[[self window] frame]].size;
628 - (void)resizeWindowToFit:(id)sender
630 // Makes the window large enough to contain the vim view, called after the
631 // vim view's size was changed. If the window had to become to big, the
632 // vim view is made smaller.
634 // NOTE: Be very careful when you call this method! Do not call while
635 // processing command queue, instead set 'shouldUpdateWindowSize' to YES.
636 // The only other place it is currently called is when live resize ends.
637 // This is done to ensure that the text view and window sizes match up
638 // (they may become out of sync if a SetTextDimensionsMsgID message to the
639 // backend is dropped).
641 if (!setupDone) return;
643 // Get size of text view, adapt window size to it
644 NSWindow *win = [self window];
645 NSRect frame = [win frame];
646 NSRect contentRect = [self contentRectForFrameRect:frame];
647 NSSize newSize = [vimView desiredSizeForActualRowsAndColumns];
649 // Keep top-left corner of the window fixed when resizing.
650 contentRect.origin.y -= newSize.height - contentRect.size.height;
651 contentRect.size = newSize;
653 frame = [self frameRectForContentRect:contentRect];
654 NSRect maxFrame = [win constrainFrameRect:frame toScreen:[win screen]];
656 // HACK! Assuming the window frame cannot already be placed too high,
657 // adjust 'maxFrame' so that it at least as high up as the current frame.
658 // The reason for doing this is that constrainFrameRect:toScreen: does not
659 // always seem to utilize as much area as possible.
660 if (NSMaxY(frame) > NSMaxY(maxFrame)) {
661 maxFrame.size.height = frame.origin.y - maxFrame.origin.y
665 if (!NSEqualRects(maxFrame, frame)) {
666 // The new window frame is too big to fit on the screen, so fit the
667 // text storage to the biggest frame which will fit on the screen.
668 //NSLog(@"Proposed window frame does not fit on the screen!");
669 frame = [self fitWindowToFrame:maxFrame];
670 [self resizeVimViewToFitSize:[self contentRectForFrameRect:frame].size];
673 // NSLog(@"%s %@", _cmd, NSStringFromRect(frame));
675 // HACK! If the window does resize, then windowDidResize is called which in
676 // turn calls placeViews. In case the computed new size of the window is
677 // no different from the current size, then we need to call placeViews
679 if (NSEqualRects(frame, [win frame])) {
682 [win setFrame:frame display:YES];
686 - (NSRect)fitWindowToFrame:(NSRect)frame
688 if (!setupDone) return frame;
690 NSRect contentRect = [self contentRectForFrameRect:frame];
691 NSSize size = [vimView getDesiredRows:NULL columns:NULL
692 forSize:contentRect.size];
694 // Keep top-left corner of 'frame' fixed.
695 contentRect.origin.y -= size.height - contentRect.size.height;
696 contentRect.size = size;
698 return [self frameRectForContentRect:contentRect];
701 - (void)updateResizeIncrements
703 if (!setupDone) return;
705 NSSize size = [[vimView textStorage] cellSize];
706 [[self window] setContentResizeIncrements:size];
709 - (NSTabViewItem *)addNewTabViewItem
711 return [vimView addNewTabViewItem];
714 - (IBAction)vimMenuItemAction:(id)sender
716 int tag = [sender tag];
718 NSMutableData *data = [NSMutableData data];
719 [data appendBytes:&tag length:sizeof(int)];
721 [vimController sendMessage:ExecuteMenuMsgID data:data];
724 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb
727 id backendProxy = [vimController backendProxy];
731 reply = [backendProxy starRegisterToPasteboard:pb];
733 @catch (NSException *e) {
734 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
741 - (void)checkWindowNeedsResizing
743 shouldUpdateWindowSize =
744 shouldUpdateWindowSize || [vimView shouldUpdateWindowSize];
747 - (NSSize)resizeVimViewToFitSize:(NSSize)size
749 // If our optimal (rows,cols) do not match our current (rows,cols), resize
750 // ourselves and tell the Vim process to sync up.
752 NSSize newSize = [vimView getDesiredRows:&desired[0] columns:&desired[1]
756 [vimView getActualRows:&rows columns:&columns];
758 if (desired[0] != rows || desired[1] != columns) {
759 // NSLog(@"Notify Vim that text storage dimensions changed from %dx%d "
760 // @"to %dx%d", columns, rows, desired[0], desired[1]);
761 NSData *data = [NSData dataWithBytes:desired length:2*sizeof(int)];
763 [vimController sendMessage:SetTextDimensionsMsgID data:data];
765 // We only want to set the window title if this resize came from
766 // a live-resize, not (for example) setting 'columns' or 'lines'.
767 if ([[self textView] inLiveResize]) {
768 [[self window] setTitle:[NSString stringWithFormat:@"%dx%d",
769 desired[1], desired[0]]];
777 @end // MMWindowController (Private)