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.
11 #import "MMWindowController.h"
12 #import <PSMTabBarControl.h>
13 #import "MMTextView.h"
14 #import "MMTextStorage.h"
15 #import "MMVimController.h"
17 #import "MMAppController.h"
18 #import "MMTypesetter.h"
19 #import "MMFullscreenWindow.h"
24 @interface MMWindowController (Private)
25 - (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize;
26 - (NSRect)textViewRectForContentSize:(NSSize)contentSize;
27 - (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize;
28 - (void)resizeWindowToFit:(id)sender;
29 - (NSRect)fitWindowToFrame:(NSRect)frame;
30 - (void)updateResizeIncrements;
31 - (NSTabViewItem *)addNewTabViewItem;
32 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi;
33 - (IBAction)vimMenuItemAction:(id)sender;
34 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx;
35 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb;
36 - (void)checkWindowNeedsResizing;
42 NSString *buildMenuItemDescriptor(NSMenu *menu, NSString *tail)
44 return menu ? buildMenuItemDescriptor([menu supermenu], [[menu title]
45 stringByAppendingString:tail])
49 NSMutableArray *buildMenuAddress(NSMenu *menu)
53 addr = buildMenuAddress([menu supermenu]);
54 [addr addObject:[menu title]];
56 addr = [NSMutableArray array];
63 // Note: This hack allows us to set content shadowing separately from
64 // the window shadow. This is apparently what webkit and terminal do.
65 @interface NSWindow (NSWindowPrivate) // new Tiger private method
66 - (void)_setContentHasShadow:(BOOL)shadow;
70 @implementation MMWindowController
72 - (id)initWithVimController:(MMVimController *)controller
74 if ((self = [super initWithWindowNibName:@"EmptyWindow"])) {
75 fullscreenWindow = nil;
76 vimController = controller;
78 // Window cascading is handled by MMAppController.
79 [self setShouldCascadeWindows:NO];
81 NSWindow *win = [self window];
82 NSView *contentView = [win contentView];
83 vimView = [[MMVimView alloc] initWithFrame:[contentView frame]
84 vimController:vimController];
85 [contentView addSubview:vimView];
86 //[vimView translateOriginToPoint:
87 // NSMakePoint([contentView frame].size.width, 0)];
88 //[vimView rotateByAngle:45.f];
90 // Create the tabline separator (which may be visible when the tabline
92 NSRect tabSepRect = [contentView frame];
93 tabSepRect.origin.y = NSMaxY(tabSepRect)-1;
94 tabSepRect.size.height = 1;
95 tablineSeparator = [[NSBox alloc] initWithFrame:tabSepRect];
97 [tablineSeparator setBoxType:NSBoxSeparator];
98 [tablineSeparator setHidden:NO];
99 [tablineSeparator setAutoresizingMask:NSViewWidthSizable
102 [contentView setAutoresizesSubviews:YES];
103 [contentView addSubview:tablineSeparator];
105 [win setDelegate:self];
106 [win setInitialFirstResponder:[vimView textView]];
108 // Make us safe on pre-tiger OSX
109 if ([win respondsToSelector:@selector(_setContentHasShadow:)])
110 [win _setContentHasShadow:NO];
118 //NSLog(@"%@ %s", [self className], _cmd);
120 [tablineSeparator release]; tablineSeparator = nil;
121 [windowAutosaveKey release]; windowAutosaveKey = nil;
122 [vimView release]; vimView = nil;
127 - (NSString *)description
129 return [NSString stringWithFormat:@"%@ : setupDone=%d windowAutosaveKey=%@ vimController=%@", [self className], setupDone, windowAutosaveKey, vimController];
132 - (MMVimController *)vimController
134 return vimController;
137 - (MMTextView *)textView
139 return [vimView textView];
142 - (MMTextStorage *)textStorage
144 return [vimView textStorage];
147 - (MMVimView *)vimView
152 - (NSString *)windowAutosaveKey
154 return windowAutosaveKey;
157 - (void)setWindowAutosaveKey:(NSString *)key
159 [windowAutosaveKey autorelease];
160 windowAutosaveKey = [key copy];
165 //NSLog(@"%@ %s", [self className], _cmd);
167 if (fullscreenWindow != nil) {
168 // if we are closed while still in fullscreen, end fullscreen mode,
169 // release ourselves (because this won't happen in MMWindowController)
170 // and perform close operation on the original window
171 [self leaveFullscreen];
178 [tablineSeparator removeFromSuperviewWithoutNeedingDisplay];
179 [vimView removeFromSuperviewWithoutNeedingDisplay];
180 [vimView cleanup]; // TODO: is this necessary?
182 [[self window] orderOut:self];
187 [[NSApp delegate] windowControllerWillOpen:self];
189 [self addNewTabViewItem];
193 [self updateResizeIncrements];
194 [self resizeWindowToFit:self];
195 [[self window] makeKeyAndOrderFront:self];
198 - (void)updateTabsWithData:(NSData *)data
200 [vimView updateTabsWithData:data];
203 - (void)selectTabWithIndex:(int)idx
205 [vimView selectTabWithIndex:idx];
208 - (void)setTextDimensionsWithRows:(int)rows columns:(int)cols
210 //NSLog(@"setTextDimensionsWithRows:%d columns:%d", rows, cols);
212 [[vimView textStorage] setMaxRows:rows columns:cols];
214 if (setupDone && ![vimView inLiveResize])
215 shouldUpdateWindowSize = YES;
218 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
220 [vimView createScrollbarWithIdentifier:ident type:type];
223 - (void)destroyScrollbarWithIdentifier:(long)ident
225 [vimView destroyScrollbarWithIdentifier:ident];
226 [self checkWindowNeedsResizing];
229 - (void)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
231 [vimView showScrollbarWithIdentifier:ident state:visible];
232 [self checkWindowNeedsResizing];
235 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
237 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
238 NSRange range = NSMakeRange(pos, len);
239 if (!NSEqualRanges(range, [scroller range])) {
240 //NSLog(@"Set range %@ for scroller %d",
241 // NSStringFromRange(range), ident);
242 [scroller setRange:range];
243 // TODO! Should only do this once per update.
245 if (setupDone) // TODO: probably not necessary
246 [vimView placeScrollbars];
250 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
251 identifier:(long)ident
253 [vimView setScrollbarThumbValue:val proportion:prop identifier:ident];
256 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
258 // NOTE: This is called when the transparency changes so set the opacity
259 // flag on the window here (should be faster if the window is opaque).
260 BOOL isOpaque = [back alphaComponent] == 1.0f;
261 [[self window] setOpaque:isOpaque];
263 [vimView setDefaultColorsBackground:back foreground:fore];
266 - (void)setFont:(NSFont *)font
268 [[NSFontManager sharedFontManager] setSelectedFont:font isMultiple:NO];
269 [[vimView textStorage] setFont:font];
270 [self updateResizeIncrements];
273 - (void)processCommandQueueDidFinish
275 if (shouldUpdateWindowSize) {
276 shouldUpdateWindowSize = NO;
277 [vimView setShouldUpdateWindowSize:NO];
278 [self resizeWindowToFit:self];
282 - (void)popupMenu:(NSMenu *)menu atRow:(int)row column:(int)col
284 if (!setupDone) return;
287 if (row >= 0 && col >= 0) {
288 NSSize cellSize = [[vimView textStorage] cellSize];
289 NSPoint pt = { (col+1)*cellSize.width, (row+1)*cellSize.height };
290 pt = [[vimView textView] convertPoint:pt toView:nil];
292 event = [NSEvent mouseEventWithType:NSRightMouseDown
296 windowNumber:[[self window] windowNumber]
302 event = [[vimView textView] lastMouseDownEvent];
305 [NSMenu popUpContextMenu:menu withEvent:event forView:[vimView textView]];
308 - (void)showTabBar:(BOOL)on
310 [[vimView tabBarControl] setHidden:!on];
313 NSToolbar *toolbar = [[self window] toolbar];
314 [tablineSeparator setHidden:![toolbar isVisible]];
316 [tablineSeparator setHidden:on];
320 // shouldUpdateWindowSize = YES;
323 - (void)showToolbar:(BOOL)on size:(int)size mode:(int)mode
325 NSToolbar *toolbar = [[self window] toolbar];
326 if (!toolbar) return;
328 [toolbar setSizeMode:size];
329 [toolbar setDisplayMode:mode];
330 [toolbar setVisible:on];
333 [tablineSeparator setHidden:YES];
335 [tablineSeparator setHidden:![[vimView tabBarControl] isHidden]];
339 - (void)setMouseShape:(int)shape
341 // This switch should match mshape_names[] in misc2.c.
343 // TODO: Add missing cursor shapes.
345 case 2: [[NSCursor IBeamCursor] set]; break;
346 case 3: case 4: [[NSCursor resizeUpDownCursor] set]; break;
347 case 5: case 6: [[NSCursor resizeLeftRightCursor] set]; break;
348 case 9: [[NSCursor crosshairCursor] set]; break;
349 case 10: [[NSCursor pointingHandCursor] set]; break;
350 case 11: [[NSCursor openHandCursor] set]; break;
352 [[NSCursor arrowCursor] set]; break;
355 // Shape 1 indicates that the mouse cursor should be hidden.
357 [NSCursor setHiddenUntilMouseMoves:YES];
360 - (void)adjustLinespace:(int)linespace
362 if (vimView && [vimView textStorage]) {
363 [[vimView textStorage] setLinespace:(float)linespace];
364 shouldUpdateWindowSize = YES;
368 - (void)liveResizeDidEnd
370 // TODO: Don't duplicate code from placeViews.
372 if (!setupDone) return;
374 // NOTE! It is assumed that the window has been resized so that it will
375 // exactly fit the text storage (possibly after resizing it). If this is
376 // not the case the display might be messed up.
377 BOOL resizeFailed = NO;
378 NSWindow *win = [self window];
379 NSRect contentRect = [win contentRectForFrameRect:[win frame]];
380 NSRect textViewRect = [self textViewRectForContentSize:contentRect.size];
381 NSSize tsSize = [self textStorageSizeForTextViewSize:textViewRect.size];
383 int dim[2], rows, cols;
384 [[vimView textStorage] getMaxRows:&rows columns:&cols];
385 [[vimView textStorage] fitToSize:tsSize rows:&dim[0] columns:&dim[1]];
387 if (dim[0] != rows || dim[1] != cols) {
388 NSData *data = [NSData dataWithBytes:dim length:2*sizeof(int)];
390 // NOTE: Since we're at the end of a live resize we want to make sure
391 // that the SetTextDimensionsMsgID message reaches Vim, else Vim and
392 // MacVim will have inconsistent states (i.e. the text view will be too
393 // large or too small for the window size). Thus, add a timeout (this
394 // may have to be tweaked) and take note if the message was sent or
396 resizeFailed = ![vimController sendMessageNow:SetTextDimensionsMsgID
401 [[vimView textView] setFrame:textViewRect];
403 [vimView placeScrollbars];
406 // Force the window size to match the text view size otherwise Vim and
407 // MacVim will have inconsistent states.
408 [self resizeWindowToFit:self];
414 if (!setupDone) return;
416 // NOTE! It is assumed that the window has been resized so that it will
417 // exactly fit the text storage (possibly after resizing it). If this is
418 // not the case the display might be messed up.
419 NSWindow *win = [self window];
420 NSRect contentRect = [win contentRectForFrameRect:[win frame]];
421 NSRect textViewRect = [self textViewRectForContentSize:contentRect.size];
422 NSSize tsSize = [self textStorageSizeForTextViewSize:textViewRect.size];
424 int dim[2], rows, cols;
425 [[vimView textStorage] getMaxRows:&rows columns:&cols];
426 [[vimView textStorage] fitToSize:tsSize rows:&dim[0] columns:&dim[1]];
428 if (dim[0] != rows || dim[1] != cols) {
429 //NSLog(@"Notify Vim that text storage dimensions changed to %dx%d",
431 NSData *data = [NSData dataWithBytes:dim length:2*sizeof(int)];
433 [vimController sendMessage:SetTextDimensionsMsgID data:data];
436 // XXX: put vimView resizing logic in vimView
437 [[vimView textView] setFrame:textViewRect];
439 NSRect vimViewRect = textViewRect;
440 vimViewRect.origin = NSMakePoint(0, 0);
441 if (![[vimView tabBarControl] isHidden])
442 vimViewRect.size.height += [[vimView tabBarControl] frame].size.height;
443 if ([vimView bottomScrollbarVisible])
444 vimViewRect.size.height += [NSScroller scrollerWidth];
445 if ([vimView leftScrollbarVisible])
446 vimViewRect.size.width += [NSScroller scrollerWidth];
447 if ([vimView rightScrollbarVisible])
448 vimViewRect.size.width += [NSScroller scrollerWidth];
452 [vimView setFrame:vimViewRect];
454 [vimView placeScrollbars];
457 - (void)enterFullscreen
459 fullscreenWindow = [[MMFullscreenWindow alloc] initWithWindow:[self window]
461 [fullscreenWindow enterFullscreen];
463 [fullscreenWindow setDelegate:self];
466 - (void)leaveFullscreen
468 [fullscreenWindow leaveFullscreen];
469 [fullscreenWindow release];
470 fullscreenWindow = nil;
474 - (IBAction)addNewTab:(id)sender
476 [vimView addNewTab:sender];
479 - (IBAction)toggleToolbar:(id)sender
481 [vimController sendMessage:ToggleToolbarMsgID data:nil];
486 // -- NSWindow delegate ------------------------------------------------------
488 - (void)windowDidBecomeMain:(NSNotification *)notification
490 [vimController sendMessage:GotFocusMsgID data:nil];
492 if ([vimView textStorage]) {
493 NSFontManager *fontManager = [NSFontManager sharedFontManager];
494 [fontManager setSelectedFont:[[vimView textStorage] font]
499 - (void)windowDidResignMain:(NSNotification *)notification
501 [vimController sendMessage:LostFocusMsgID data:nil];
503 if ([vimView textView])
504 [[vimView textView] hideMarkedTextField];
507 - (BOOL)windowShouldClose:(id)sender
509 [vimController sendMessage:VimShouldCloseMsgID data:nil];
513 - (void)windowDidMove:(NSNotification *)notification
515 if (setupDone && windowAutosaveKey) {
516 NSRect frame = [[self window] frame];
517 NSPoint topLeft = { frame.origin.x, NSMaxY(frame) };
518 NSString *topLeftString = NSStringFromPoint(topLeft);
520 [[NSUserDefaults standardUserDefaults]
521 setObject:topLeftString forKey:windowAutosaveKey];
525 - (void)windowDidResize:(id)sender
527 if (!setupDone) return;
531 - (NSRect)windowWillUseStandardFrame:(NSWindow *)win
532 defaultFrame:(NSRect)frame
534 // HACK! For some reason 'frame' is not always constrained to fit on the
535 // screen (e.g. it may overlap the menu bar), so first constrain it to the
536 // screen; otherwise the new frame we compute may be too large and this
537 // will mess up the display after the window resizes.
538 frame = [win constrainFrameRect:frame toScreen:[win screen]];
540 // HACK! If the top of 'frame' is lower than the current window frame,
541 // increase 'frame' so that their tops align. Really, 'frame' should
542 // already have its top at least as high as the current window frame, but
543 // for some reason this is not always the case.
544 // (See resizeWindowToFit: for a similar hack.)
545 NSRect cur = [win frame];
546 if (NSMaxY(cur) > NSMaxY(frame)) {
547 frame.size.height = cur.origin.y - frame.origin.y + cur.size.height;
550 frame = [self fitWindowToFrame:frame];
552 // Keep old width and horizontal position unless user clicked with the
553 // Command key is held down.
554 NSEvent *event = [NSApp currentEvent];
555 if (!([event type] == NSLeftMouseUp
556 && [event modifierFlags] & NSCommandKeyMask)) {
557 NSRect currentFrame = [win frame];
558 frame.size.width = currentFrame.size.width;
559 frame.origin.x = currentFrame.origin.x;
568 // -- Services menu delegate -------------------------------------------------
570 - (id)validRequestorForSendType:(NSString *)sendType
571 returnType:(NSString *)returnType
573 if ([sendType isEqual:NSStringPboardType]
574 && [self askBackendForStarRegister:nil])
577 return [super validRequestorForSendType:sendType returnType:returnType];
580 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
581 types:(NSArray *)types
583 if (![types containsObject:NSStringPboardType])
586 return [self askBackendForStarRegister:pboard];
589 @end // MMWindowController
593 @implementation MMWindowController (Private)
595 - (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize
597 NSSize size = [vimView contentSizeForTextStorageSize:textViewSize];
598 if (![tablineSeparator isHidden])
603 - (NSRect)textViewRectForContentSize:(NSSize)contentSize
605 NSSize size = { contentSize.width, contentSize.height };
606 if (![tablineSeparator isHidden])
609 return [vimView textViewRectForContentSize:size];
612 - (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize
614 return [vimView textStorageSizeForTextViewSize:textViewSize];
617 - (void)resizeWindowToFit:(id)sender
619 // NOTE: Be very careful when you call this method! Do not call while
620 // processing command queue, instead set 'shouldUpdateWindowSize' to YES.
621 // The only other place it is currently called is when live resize ends.
622 // This is done to ensure that the text view and window sizes match up
623 // (they may become out of sync if a SetTextDimensionsMsgID message to the
624 // backend is dropped).
626 if (!setupDone) return;
628 NSWindow *win = [self window];
629 NSRect frame = [win frame];
630 NSRect contentRect = [win contentRectForFrameRect:frame];
631 NSSize textStorageSize = [[vimView textStorage] size];
632 NSSize newSize = [self contentSizeForTextStorageSize:textStorageSize];
634 // Keep top-left corner of the window fixed when resizing.
635 contentRect.origin.y -= newSize.height - contentRect.size.height;
636 contentRect.size = newSize;
638 frame = [win frameRectForContentRect:contentRect];
639 NSRect maxFrame = [win constrainFrameRect:frame toScreen:[win screen]];
641 // HACK! Assuming the window frame cannot already be placed too high,
642 // adjust 'maxFrame' so that it at least as high up as the current frame.
643 // The reason for doing this is that constrainFrameRect:toScreen: does not
644 // always seem to utilize as much area as possible.
645 if (NSMaxY(frame) > NSMaxY(maxFrame)) {
646 maxFrame.size.height = frame.origin.y - maxFrame.origin.y
650 if (!NSEqualRects(maxFrame, frame)) {
651 // The new window frame is too big to fit on the screen, so fit the
652 // text storage to the biggest frame which will fit on the screen.
653 //NSLog(@"Proposed window frame does not fit on the screen!");
654 frame = [self fitWindowToFrame:maxFrame];
657 //NSLog(@"%s %@", _cmd, NSStringFromRect(frame));
659 // HACK! If the window does resize, then windowDidResize is called which in
660 // turn calls placeViews. In case the computed new size of the window is
661 // no different from the current size, then we need to call placeViews
663 if (NSEqualRects(frame, [win frame])) {
666 [win setFrame:frame display:YES];
670 - (NSRect)fitWindowToFrame:(NSRect)frame
672 if (!setupDone) return frame;
674 NSWindow *win = [self window];
675 NSRect contentRect = [win contentRectForFrameRect:frame];
676 NSSize size = [self textViewRectForContentSize:contentRect.size].size;
677 size = [self textStorageSizeForTextViewSize:size];
678 size = [[vimView textStorage] fitToSize:size];
679 size = [self contentSizeForTextStorageSize:size];
681 // Keep top-left corner of 'frame' fixed.
682 contentRect.origin.y -= size.height - contentRect.size.height;
683 contentRect.size = size;
685 return [win frameRectForContentRect:contentRect];
688 - (void)updateResizeIncrements
690 if (!setupDone) return;
692 NSSize size = [[vimView textStorage] cellSize];
693 [[self window] setContentResizeIncrements:size];
696 - (NSTabViewItem *)addNewTabViewItem
698 return [vimView addNewTabViewItem];
701 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
703 return [vimView representedIndexOfTabViewItem:tvi];
706 - (IBAction)vimMenuItemAction:(id)sender
708 int tag = [sender tag];
710 NSMutableData *data = [NSMutableData data];
711 [data appendBytes:&tag length:sizeof(int)];
713 [vimController sendMessage:ExecuteMenuMsgID data:data];
716 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx
718 return [vimView scrollbarForIdentifier:ident index:idx];
721 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb
724 id backendProxy = [vimController backendProxy];
728 reply = [backendProxy starRegisterToPasteboard:pb];
730 @catch (NSException *e) {
731 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
738 - (void)checkWindowNeedsResizing
740 shouldUpdateWindowSize =
741 shouldUpdateWindowSize || [vimView shouldUpdateWindowSize];
744 @end // MMWindowController (Private)