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"
21 // Scroller type; these must match SBAR_* in gui.h
23 MMScrollerTypeLeft = 0,
31 @interface NSTabView (MMExtras)
32 - (void)removeAllTabViewItems;
37 @interface MMScroller : NSScroller {
42 - (id)initWithIdentifier:(long)ident type:(int)type;
46 - (void)setRange:(NSRange)newRange;
49 @interface MMWindowController (Private)
50 - (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize;
51 - (NSRect)textViewRectForContentSize:(NSSize)contentSize;
52 - (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize;
53 - (void)resizeWindowToFit:(id)sender;
54 - (NSRect)fitWindowToFrame:(NSRect)frame;
55 - (void)updateResizeIncrements;
56 - (NSTabViewItem *)addNewTabViewItem;
57 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi;
58 - (IBAction)vimMenuItemAction:(id)sender;
59 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx;
60 - (BOOL)bottomScrollbarVisible;
61 - (BOOL)leftScrollbarVisible;
62 - (BOOL)rightScrollbarVisible;
63 - (void)placeScrollbars;
64 - (void)scroll:(id)sender;
66 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb;
72 NSString *buildMenuItemDescriptor(NSMenu *menu, NSString *tail)
74 return menu ? buildMenuItemDescriptor([menu supermenu], [[menu title]
75 stringByAppendingString:tail])
79 NSMutableArray *buildMenuAddress(NSMenu *menu)
83 addr = buildMenuAddress([menu supermenu]);
84 [addr addObject:[menu title]];
86 addr = [NSMutableArray array];
93 // Note: This hack allows us to set content shadowing separately from
94 // the window shadow. This is apparently what webkit and terminal do.
95 @interface NSWindow (NSWindowPrivate) // new Tiger private method
96 - (void) _setContentHasShadow:(BOOL)shadow;
100 @implementation MMWindowController
102 - (id)initWithVimController:(MMVimController *)controller
104 if ((self = [super initWithWindowNibName:@"EmptyWindow"])) {
105 vimController = controller;
106 scrollbars = [[NSMutableArray alloc] init];
108 // Window cascading is handled by MMAppController.
109 [self setShouldCascadeWindows:NO];
111 // Setup a complete text system.
112 textStorage = [[MMTextStorage alloc] init];
113 NSLayoutManager *lm = [[NSLayoutManager alloc] init];
114 NSTextContainer *tc = [[NSTextContainer alloc] initWithContainerSize:
115 NSMakeSize(1.0e7,1.0e7)];
117 NSString *typesetterString = [[NSUserDefaults standardUserDefaults]
118 stringForKey:MMTypesetterKey];
119 if (![typesetterString isEqual:@"NSTypesetter"]) {
120 MMTypesetter *typesetter = [[MMTypesetter alloc] init];
121 [lm setTypesetter:typesetter];
122 [typesetter release];
124 // Only MMTypesetter supports different cell width multipliers.
125 [[NSUserDefaults standardUserDefaults]
126 setFloat:1.0 forKey:MMCellWidthMultiplierKey];
129 [tc setWidthTracksTextView:NO];
130 [tc setHeightTracksTextView:NO];
131 [tc setLineFragmentPadding:0];
133 [textStorage addLayoutManager:lm];
134 [lm addTextContainer:tc];
136 NSWindow *win = [self window];
137 NSView *contentView = [win contentView];
138 textView = [[MMTextView alloc] initWithFrame:[contentView frame]
141 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
142 int left = [ud integerForKey:MMTextInsetLeftKey];
143 int top = [ud integerForKey:MMTextInsetTopKey];
144 [textView setTextContainerInset:NSMakeSize(left, top)];
146 [contentView addSubview:textView];
148 // The text storage retains the layout manager which in turn retains
149 // the text container.
153 // Create the tabline separator (which may be visible when the tabline
155 NSRect tabSepRect = [contentView frame];
156 tabSepRect.origin.y = NSMaxY(tabSepRect)-1;
157 tabSepRect.size.height = 1;
158 tablineSeparator = [[NSBox alloc] initWithFrame:tabSepRect];
160 // Create the tab view (which is never visible, but the tab bar control
161 // needs it to function).
162 tabView = [[NSTabView alloc] initWithFrame:NSZeroRect];
164 // Create the tab bar control (which is responsible for actually
165 // drawing the tabline and tabs).
166 NSRect tabFrame = [contentView frame];
167 tabFrame.origin.y = NSMaxY(tabFrame) - 22;
168 tabFrame.size.height = 22;
169 tabBarControl = [[PSMTabBarControl alloc] initWithFrame:tabFrame];
171 [tabView setDelegate:tabBarControl];
173 [tabBarControl setTabView:tabView];
174 [tabBarControl setDelegate:self];
175 [tabBarControl setHidden:YES];
176 [tabBarControl setAutoresizingMask:NSViewWidthSizable|NSViewMinYMargin];
177 [tabBarControl setCellMinWidth:[ud integerForKey:MMTabMinWidthKey]];
178 [tabBarControl setCellMaxWidth:[ud integerForKey:MMTabMaxWidthKey]];
179 [tabBarControl setCellOptimumWidth:
180 [ud integerForKey:MMTabOptimumWidthKey]];
181 [tabBarControl setShowAddTabButton:YES];
182 [[tabBarControl addTabButton] setTarget:self];
183 [[tabBarControl addTabButton] setAction:@selector(addNewTab:)];
184 [tabBarControl setAllowsDragBetweenWindows:NO];
186 [tablineSeparator setBoxType:NSBoxSeparator];
187 [tablineSeparator setHidden:NO];
188 [tablineSeparator setAutoresizingMask:NSViewWidthSizable
191 [contentView setAutoresizesSubviews:YES];
192 [contentView addSubview:tabBarControl];
193 [contentView addSubview:tablineSeparator];
195 [win setDelegate:self];
196 [win setInitialFirstResponder:textView];
198 // Make us safe on pre-tiger OSX
199 if ([win respondsToSelector:@selector(_setContentHasShadow:)])
200 [win _setContentHasShadow:NO];
208 //NSLog(@"%@ %s", [self className], _cmd);
210 [tabBarControl release]; tabBarControl = nil;
211 [tabView release]; tabView = nil;
212 [tablineSeparator release]; tablineSeparator = nil;
213 [windowAutosaveKey release]; windowAutosaveKey = nil;
214 [scrollbars release]; scrollbars = nil;
215 [textView release]; textView = nil;
216 [textStorage release]; textStorage = nil;
221 - (NSString *)description
223 return [NSString stringWithFormat:@"%@ : setupDone=%d windowAutosaveKey=%@ vimController=%@", [self className], setupDone, windowAutosaveKey, vimController];
226 - (MMVimController *)vimController
228 return vimController;
231 - (MMTextView *)textView
236 - (MMTextStorage *)textStorage
241 - (NSString *)windowAutosaveKey
243 return windowAutosaveKey;
246 - (void)setWindowAutosaveKey:(NSString *)key
248 [windowAutosaveKey autorelease];
249 windowAutosaveKey = [key copy];
254 //NSLog(@"%@ %s", [self className], _cmd);
259 // NOTE! There is a bug in PSMTabBarControl in that it retains the delegate
260 // (which is the MMWindowController) so reset the delegate here, otherwise
261 // the MMWindowController never gets released resulting in a pretty serious
263 [tabView setDelegate:nil];
264 [tabBarControl setDelegate:nil];
265 [tabBarControl setTabView:nil];
266 [[self window] setDelegate:nil];
268 // NOTE! There is another bug in PSMTabBarControl where the control is not
269 // removed as an observer, so remove it here (else lots of evil nasty bugs
270 // will come and gnaw at your feet while you are sleeping).
271 [[NSNotificationCenter defaultCenter] removeObserver:tabBarControl];
273 [tabBarControl removeFromSuperviewWithoutNeedingDisplay];
274 [tablineSeparator removeFromSuperviewWithoutNeedingDisplay];
275 [textView removeFromSuperviewWithoutNeedingDisplay];
277 unsigned i, count = [scrollbars count];
278 for (i = 0; i < count; ++i) {
279 MMScroller *sb = [scrollbars objectAtIndex:i];
280 [sb removeFromSuperviewWithoutNeedingDisplay];
283 [tabView removeAllTabViewItems];
285 [[self window] orderOut:self];
290 [[NSApp delegate] windowControllerWillOpen:self];
292 [self addNewTabViewItem];
296 [self updateResizeIncrements];
297 [self resizeWindowToFit:self];
298 [[self window] makeKeyAndOrderFront:self];
301 - (void)updateTabsWithData:(NSData *)data
303 const void *p = [data bytes];
304 const void *end = p + [data length];
307 // HACK! Current tab is first in the message. This way it is not
308 // necessary to guess which tab should be the selected one (this can be
309 // problematic for instance when new tabs are created).
310 int curtabIdx = *((int*)p); p += sizeof(int);
312 NSArray *tabViewItems = [tabBarControl representedTabViewItems];
315 //int wincount = *((int*)p); p += sizeof(int);
316 int length = *((int*)p); p += sizeof(int);
318 NSString *label = [[NSString alloc]
319 initWithBytesNoCopy:(void*)p
321 encoding:NSUTF8StringEncoding
325 // Set the label of the tab; add a new tab when needed.
326 NSTabViewItem *tvi = [tabView numberOfTabViewItems] <= tabIdx
327 ? [self addNewTabViewItem]
328 : [tabViewItems objectAtIndex:tabIdx];
330 [tvi setLabel:label];
337 // Remove unused tabs from the NSTabView. Note that when a tab is closed
338 // the NSTabView will automatically select another tab, but we want Vim to
339 // take care of which tab to select so set the vimTaskSelectedTab flag to
340 // prevent the tab selection message to be passed on to the VimTask.
341 vimTaskSelectedTab = YES;
342 int i, count = [tabView numberOfTabViewItems];
343 for (i = count-1; i >= tabIdx; --i) {
344 id tvi = [tabViewItems objectAtIndex:i];
345 //NSLog(@"Removing tab with index %d", i);
346 [tabView removeTabViewItem:tvi];
348 vimTaskSelectedTab = NO;
350 [self selectTabWithIndex:curtabIdx];
353 - (void)selectTabWithIndex:(int)idx
355 //NSLog(@"%s%d", _cmd, idx);
357 NSArray *tabViewItems = [tabBarControl representedTabViewItems];
358 if (idx < 0 || idx >= [tabViewItems count]) {
359 NSLog(@"WARNING: No tab with index %d exists.", idx);
363 // Do not try to select a tab if already selected.
364 NSTabViewItem *tvi = [tabViewItems objectAtIndex:idx];
365 if (tvi != [tabView selectedTabViewItem]) {
366 vimTaskSelectedTab = YES;
367 [tabView selectTabViewItem:tvi];
368 vimTaskSelectedTab = NO;
372 - (void)setTextDimensionsWithRows:(int)rows columns:(int)cols
374 //NSLog(@"setTextDimensionsWithRows:%d columns:%d", rows, cols);
376 [textStorage setMaxRows:rows columns:cols];
378 if (setupDone && ![textView inLiveResize])
379 shouldUpdateWindowSize = YES;
382 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
384 //NSLog(@"Create scroller %d of type %d", ident, type);
386 MMScroller *scroller = [[MMScroller alloc] initWithIdentifier:ident
388 [scroller setTarget:self];
389 [scroller setAction:@selector(scroll:)];
391 [[[self window] contentView] addSubview:scroller];
392 [scrollbars addObject:scroller];
396 - (void)destroyScrollbarWithIdentifier:(long)ident
398 //NSLog(@"Destroy scroller %d", ident);
401 MMScroller *scroller = [self scrollbarForIdentifier:ident index:&idx];
403 [scroller removeFromSuperview];
404 [scrollbars removeObjectAtIndex:idx];
406 if (![scroller isHidden]) {
407 // A visible scroller was removed, so the window must resize to
409 //NSLog(@"Visible scroller %d was destroyed, resizing window.",
411 shouldUpdateWindowSize = YES;
416 - (void)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
418 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
419 if (!scroller) return;
421 BOOL wasVisible = ![scroller isHidden];
422 //NSLog(@"%s scroller %d (was %svisible)", visible ? "Show" : "Hide",
423 // ident, wasVisible ? "" : "in");
424 [scroller setHidden:!visible];
426 if (wasVisible != visible) {
427 // A scroller was hidden or shown, so the window must resize to fit.
428 //NSLog(@"%s scroller %d and resize.", visible ? "Show" : "Hide",
430 shouldUpdateWindowSize = YES;
434 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
436 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
437 NSRange range = NSMakeRange(pos, len);
438 if (!NSEqualRanges(range, [scroller range])) {
439 //NSLog(@"Set range %@ for scroller %d",
440 // NSStringFromRange(range), ident);
441 [scroller setRange:range];
442 // TODO! Should only do this once per update.
443 [self placeScrollbars];
447 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
448 identifier:(long)ident
450 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
451 //NSLog(@"Set thumb value %.2f proportion %.2f for scroller %d",
452 // val, prop, ident);
453 [scroller setFloatValue:val knobProportion:prop];
456 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
458 // NOTE: This is called when the transparency changes so set the opacity
459 // flag on the window here (should be faster if the window is opaque).
460 BOOL isOpaque = [back alphaComponent] == 1.0f;
461 [[self window] setOpaque:isOpaque];
463 [textStorage setDefaultColorsBackground:back foreground:fore];
464 [textView setBackgroundColor:back];
467 - (void)setFont:(NSFont *)font
469 [[NSFontManager sharedFontManager] setSelectedFont:font isMultiple:NO];
470 [textStorage setFont:font];
471 [self updateResizeIncrements];
474 - (void)processCommandQueueDidFinish
476 if (shouldUpdateWindowSize) {
477 shouldUpdateWindowSize = NO;
478 [self resizeWindowToFit:self];
482 - (void)popupMenu:(NSMenu *)menu atRow:(int)row column:(int)col
484 if (!setupDone) return;
487 if (row >= 0 && col >= 0) {
488 NSSize cellSize = [textStorage cellSize];
489 NSPoint pt = { (col+1)*cellSize.width, [textView frame].size.height
490 - (row+1)*cellSize.height };
492 event = [NSEvent mouseEventWithType:NSRightMouseDown
496 windowNumber:[[self window] windowNumber]
502 event = [textView lastMouseDownEvent];
505 [NSMenu popUpContextMenu:menu withEvent:event forView:textView];
508 - (void)showTabBar:(BOOL)on
510 [tabBarControl setHidden:!on];
513 NSToolbar *toolbar = [[self window] toolbar];
514 [tablineSeparator setHidden:![toolbar isVisible]];
516 [tablineSeparator setHidden:on];
520 shouldUpdateWindowSize = YES;
523 - (void)showToolbar:(BOOL)on size:(int)size mode:(int)mode
525 NSToolbar *toolbar = [[self window] toolbar];
526 if (!toolbar) return;
528 [toolbar setSizeMode:size];
529 [toolbar setDisplayMode:mode];
530 [toolbar setVisible:on];
533 [tablineSeparator setHidden:YES];
535 [tablineSeparator setHidden:![tabBarControl isHidden]];
539 - (void)setMouseShape:(int)shape
541 // This switch should match mshape_names[] in misc2.c.
543 // TODO: Add missing cursor shapes.
545 case 2: [[NSCursor IBeamCursor] set]; break;
546 case 3: case 4: [[NSCursor resizeUpDownCursor] set]; break;
547 case 5: case 6: [[NSCursor resizeLeftRightCursor] set]; break;
548 case 9: [[NSCursor crosshairCursor] set]; break;
549 case 10: [[NSCursor pointingHandCursor] set]; break;
550 case 11: [[NSCursor openHandCursor] set]; break;
552 [[NSCursor arrowCursor] set]; break;
555 // Shape 1 indicates that the mouse cursor should be hidden.
557 [NSCursor setHiddenUntilMouseMoves:YES];
560 - (void)adjustLinespace:(int)linespace
563 [textStorage setLinespace:(float)linespace];
564 shouldUpdateWindowSize = YES;
568 - (void)liveResizeDidEnd
570 // TODO: Don't duplicate code from placeViews.
572 if (!setupDone) return;
574 // NOTE! It is assumed that the window has been resized so that it will
575 // exactly fit the text storage (possibly after resizing it). If this is
576 // not the case the display might be messed up.
577 BOOL resizeFailed = NO;
578 NSWindow *win = [self window];
579 NSRect contentRect = [win contentRectForFrameRect:[win frame]];
580 NSRect textViewRect = [self textViewRectForContentSize:contentRect.size];
581 NSSize tsSize = [self textStorageSizeForTextViewSize:textViewRect.size];
583 int dim[2], rows, cols;
584 [textStorage getMaxRows:&rows columns:&cols];
585 [textStorage fitToSize:tsSize rows:&dim[0] columns:&dim[1]];
587 if (dim[0] != rows || dim[1] != cols) {
588 NSData *data = [NSData dataWithBytes:dim length:2*sizeof(int)];
590 // NOTE: Since we're at the end of a live resize we want to make sure
591 // that the SetTextDimensionsMsgID message reaches Vim, else Vim and
592 // MacVim will have inconsistent states (i.e. the text view will be too
593 // large or too small for the window size). Thus, add a timeout (this
594 // may have to be tweaked) and take note if the message was sent or
596 resizeFailed = ![vimController sendMessageNow:SetTextDimensionsMsgID
601 [textView setFrame:textViewRect];
603 [self placeScrollbars];
606 // Force the window size to match the text view size otherwise Vim and
607 // MacVim will have inconsistent states.
608 [self resizeWindowToFit:self];
612 - (IBAction)addNewTab:(id)sender
614 // NOTE! This can get called a lot if the user holds down the key
615 // equivalent for this action, which causes the ports to fill up. If we
616 // wait for the message to be sent then the app might become unresponsive.
617 [vimController sendMessage:AddNewTabMsgID data:nil];
620 - (IBAction)toggleToolbar:(id)sender
622 [vimController sendMessage:ToggleToolbarMsgID data:nil];
627 // -- PSMTabBarControl delegate ----------------------------------------------
630 - (BOOL)tabView:(NSTabView *)theTabView shouldSelectTabViewItem:
631 (NSTabViewItem *)tabViewItem
633 // NOTE: It would be reasonable to think that 'shouldSelect...' implies
634 // that this message only gets sent when the user clicks the tab.
635 // Unfortunately it is not so, which is why we need the
636 // 'vimTaskSelectedTab' flag.
638 // HACK! The selection message should not be propagated to the VimTask if
639 // the VimTask selected the tab (e.g. as opposed the user clicking the
640 // tab). The delegate method has no way of knowing who initiated the
641 // selection so a flag is set when the VimTask initiated the selection.
642 if (!vimTaskSelectedTab) {
643 // Propagate the selection message to the VimTask.
644 int idx = [self representedIndexOfTabViewItem:tabViewItem];
645 if (NSNotFound != idx) {
646 NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
647 [vimController sendMessage:SelectTabMsgID data:data];
651 // Unless Vim selected the tab, return NO, and let Vim decide if the tab
652 // should get selected or not.
653 return vimTaskSelectedTab;
656 - (BOOL)tabView:(NSTabView *)theTabView shouldCloseTabViewItem:
657 (NSTabViewItem *)tabViewItem
659 // HACK! This method is only called when the user clicks the close button
660 // on the tab. Instead of letting the tab bar close the tab, we return NO
661 // and pass a message on to Vim to let it handle the closing.
662 int idx = [self representedIndexOfTabViewItem:tabViewItem];
663 //NSLog(@"Closing tab with index %d", idx);
664 NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
665 [vimController sendMessage:CloseTabMsgID data:data];
670 - (void)tabView:(NSTabView *)theTabView didDragTabViewItem:
671 (NSTabViewItem *)tabViewItem toIndex:(int)idx
673 NSMutableData *data = [NSMutableData data];
674 [data appendBytes:&idx length:sizeof(int)];
676 [vimController sendMessage:DraggedTabMsgID data:data];
682 // -- NSWindow delegate ------------------------------------------------------
684 - (void)windowDidBecomeMain:(NSNotification *)notification
686 [vimController sendMessage:GotFocusMsgID data:nil];
689 [[NSFontManager sharedFontManager] setSelectedFont:[textStorage font]
693 - (void)windowDidResignMain:(NSNotification *)notification
695 [vimController sendMessage:LostFocusMsgID data:nil];
698 [textView hideMarkedTextField];
701 - (BOOL)windowShouldClose:(id)sender
703 [vimController sendMessage:VimShouldCloseMsgID data:nil];
707 - (void)windowDidMove:(NSNotification *)notification
709 if (setupDone && windowAutosaveKey) {
710 NSRect frame = [[self window] frame];
711 NSPoint topLeft = { frame.origin.x, NSMaxY(frame) };
712 NSString *topLeftString = NSStringFromPoint(topLeft);
714 [[NSUserDefaults standardUserDefaults]
715 setObject:topLeftString forKey:windowAutosaveKey];
719 - (void)windowDidResize:(id)sender
721 if (!setupDone) return;
725 - (NSRect)windowWillUseStandardFrame:(NSWindow *)win
726 defaultFrame:(NSRect)frame
728 // HACK! For some reason 'frame' is not always constrained to fit on the
729 // screen (e.g. it may overlap the menu bar), so first constrain it to the
730 // screen; otherwise the new frame we compute may be too large and this
731 // will mess up the display after the window resizes.
732 frame = [win constrainFrameRect:frame toScreen:[win screen]];
734 // HACK! If the top of 'frame' is lower than the current window frame,
735 // increase 'frame' so that their tops align. Really, 'frame' should
736 // already have its top at least as high as the current window frame, but
737 // for some reason this is not always the case.
738 // (See resizeWindowToFit: for a similar hack.)
739 NSRect cur = [win frame];
740 if (NSMaxY(cur) > NSMaxY(frame)) {
741 frame.size.height = cur.origin.y - frame.origin.y + cur.size.height;
744 frame = [self fitWindowToFrame:frame];
746 // Keep old width and horizontal position unless user clicked with the
747 // Command key is held down.
748 NSEvent *event = [NSApp currentEvent];
749 if (!([event type] == NSLeftMouseUp
750 && [event modifierFlags] & NSCommandKeyMask)) {
751 NSRect currentFrame = [win frame];
752 frame.size.width = currentFrame.size.width;
753 frame.origin.x = currentFrame.origin.x;
762 // -- Services menu delegate -------------------------------------------------
764 - (id)validRequestorForSendType:(NSString *)sendType
765 returnType:(NSString *)returnType
767 if ([sendType isEqual:NSStringPboardType]
768 && [self askBackendForStarRegister:nil])
771 return [super validRequestorForSendType:sendType returnType:returnType];
774 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
775 types:(NSArray *)types
777 if (![types containsObject:NSStringPboardType])
780 return [self askBackendForStarRegister:pboard];
783 @end // MMWindowController
787 @implementation MMWindowController (Private)
789 - (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize
791 NSSize size = textViewSize;
793 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
794 int right = [ud integerForKey:MMTextInsetRightKey];
795 int bot = [ud integerForKey:MMTextInsetBottomKey];
797 size.width += [textView textContainerOrigin].x + right;
798 size.height += [textView textContainerOrigin].y + bot;
800 if (![tablineSeparator isHidden])
802 if (![tabBarControl isHidden])
803 size.height += [tabBarControl frame].size.height;
805 if ([self bottomScrollbarVisible])
806 size.height += [NSScroller scrollerWidth];
807 if ([self leftScrollbarVisible])
808 size.width += [NSScroller scrollerWidth];
809 if ([self rightScrollbarVisible])
810 size.width += [NSScroller scrollerWidth];
815 - (NSRect)textViewRectForContentSize:(NSSize)contentSize
817 NSRect rect = { 0, 0, contentSize.width, contentSize.height };
819 if (![tablineSeparator isHidden])
821 if (![tabBarControl isHidden])
822 rect.size.height -= [tabBarControl frame].size.height;
824 if ([self bottomScrollbarVisible]) {
825 rect.size.height -= [NSScroller scrollerWidth];
826 rect.origin.y += [NSScroller scrollerWidth];
828 if ([self leftScrollbarVisible]) {
829 rect.size.width -= [NSScroller scrollerWidth];
830 rect.origin.x += [NSScroller scrollerWidth];
832 if ([self rightScrollbarVisible])
833 rect.size.width -= [NSScroller scrollerWidth];
838 - (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize
840 NSSize size = textViewSize;
842 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
843 int right = [ud integerForKey:MMTextInsetRightKey];
844 int bot = [ud integerForKey:MMTextInsetBottomKey];
846 size.width -= [textView textContainerOrigin].x + right;
847 size.height -= [textView textContainerOrigin].y + bot;
852 - (void)resizeWindowToFit:(id)sender
854 // NOTE: Be very careful when you call this method! Do not call while
855 // processing command queue, instead set 'shouldUpdateWindowSize' to YES.
856 // The only other place it is currently called is when live resize ends.
857 // This is done to ensure that the text view and window sizes match up
858 // (they may become out of sync if a SetTextDimensionsMsgID message to the
859 // backend is dropped).
861 if (!setupDone) return;
863 NSWindow *win = [self window];
864 NSRect frame = [win frame];
865 NSRect contentRect = [win contentRectForFrameRect:frame];
866 NSSize newSize = [self contentSizeForTextStorageSize:[textStorage size]];
868 // Keep top-left corner of the window fixed when resizing.
869 contentRect.origin.y -= newSize.height - contentRect.size.height;
870 contentRect.size = newSize;
872 frame = [win frameRectForContentRect:contentRect];
873 NSRect maxFrame = [win constrainFrameRect:frame toScreen:[win screen]];
875 // HACK! Assuming the window frame cannot already be placed too high,
876 // adjust 'maxFrame' so that it at least as high up as the current frame.
877 // The reason for doing this is that constrainFrameRect:toScreen: does not
878 // always seem to utilize as much area as possible.
879 if (NSMaxY(frame) > NSMaxY(maxFrame)) {
880 maxFrame.size.height = frame.origin.y - maxFrame.origin.y
884 if (!NSEqualRects(maxFrame, frame)) {
885 // The new window frame is too big to fit on the screen, so fit the
886 // text storage to the biggest frame which will fit on the screen.
887 //NSLog(@"Proposed window frame does not fit on the screen!");
888 frame = [self fitWindowToFrame:maxFrame];
891 //NSLog(@"%s %@", _cmd, NSStringFromRect(frame));
893 // HACK! If the window does resize, then windowDidResize is called which in
894 // turn calls placeViews. In case the computed new size of the window is
895 // no different from the current size, then we need to call placeViews
897 if (NSEqualRects(frame, [win frame])) {
900 [win setFrame:frame display:YES];
904 - (NSRect)fitWindowToFrame:(NSRect)frame
906 if (!setupDone) return frame;
908 NSWindow *win = [self window];
909 NSRect contentRect = [win contentRectForFrameRect:frame];
910 NSSize size = [self textViewRectForContentSize:contentRect.size].size;
911 size = [self textStorageSizeForTextViewSize:size];
912 size = [textStorage fitToSize:size];
913 size = [self contentSizeForTextStorageSize:size];
915 // Keep top-left corner of 'frame' fixed.
916 contentRect.origin.y -= size.height - contentRect.size.height;
917 contentRect.size = size;
919 return [win frameRectForContentRect:contentRect];
922 - (void)updateResizeIncrements
924 if (!setupDone) return;
926 NSSize size = [textStorage cellSize];
927 [[self window] setContentResizeIncrements:size];
930 - (NSTabViewItem *)addNewTabViewItem
932 // NOTE! A newly created tab is not by selected by default; the VimTask
933 // decides which tab should be selected at all times. However, the AppKit
934 // will automatically select the first tab added to a tab view.
936 NSTabViewItem *tvi = [[NSTabViewItem alloc] initWithIdentifier:nil];
938 // NOTE: If this is the first tab it will be automatically selected.
939 vimTaskSelectedTab = YES;
940 [tabView addTabViewItem:tvi];
941 vimTaskSelectedTab = NO;
948 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
950 NSArray *tabViewItems = [tabBarControl representedTabViewItems];
951 return [tabViewItems indexOfObject:tvi];
954 - (IBAction)vimMenuItemAction:(id)sender
956 int tag = [sender tag];
958 NSMutableData *data = [NSMutableData data];
959 [data appendBytes:&tag length:sizeof(int)];
961 [vimController sendMessage:ExecuteMenuMsgID data:data];
964 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx
966 unsigned i, count = [scrollbars count];
967 for (i = 0; i < count; ++i) {
968 MMScroller *scroller = [scrollbars objectAtIndex:i];
969 if ([scroller identifier] == ident) {
978 - (BOOL)bottomScrollbarVisible
980 unsigned i, count = [scrollbars count];
981 for (i = 0; i < count; ++i) {
982 MMScroller *scroller = [scrollbars objectAtIndex:i];
983 if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden])
990 - (BOOL)leftScrollbarVisible
992 unsigned i, count = [scrollbars count];
993 for (i = 0; i < count; ++i) {
994 MMScroller *scroller = [scrollbars objectAtIndex:i];
995 if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden])
1002 - (BOOL)rightScrollbarVisible
1004 unsigned i, count = [scrollbars count];
1005 for (i = 0; i < count; ++i) {
1006 MMScroller *scroller = [scrollbars objectAtIndex:i];
1007 if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden])
1014 - (void)placeScrollbars
1016 if (!setupDone) return;
1018 NSRect textViewFrame = [textView frame];
1019 BOOL lsbVisible = [self leftScrollbarVisible];
1021 // HACK! Find the lowest left&right vertical scrollbars, as well as the
1022 // rightmost horizontal scrollbar. This hack continues further down.
1024 // TODO! Can there be no more than one horizontal scrollbar? If so, the
1025 // code can be simplified.
1026 unsigned lowestLeftSbIdx = (unsigned)-1;
1027 unsigned lowestRightSbIdx = (unsigned)-1;
1028 unsigned rightmostSbIdx = (unsigned)-1;
1029 unsigned rowMaxLeft = 0, rowMaxRight = 0, colMax = 0;
1030 unsigned i, count = [scrollbars count];
1031 for (i = 0; i < count; ++i) {
1032 MMScroller *scroller = [scrollbars objectAtIndex:i];
1033 if (![scroller isHidden]) {
1034 NSRange range = [scroller range];
1035 if ([scroller type] == MMScrollerTypeLeft
1036 && range.location >= rowMaxLeft) {
1037 rowMaxLeft = range.location;
1038 lowestLeftSbIdx = i;
1039 } else if ([scroller type] == MMScrollerTypeRight
1040 && range.location >= rowMaxRight) {
1041 rowMaxRight = range.location;
1042 lowestRightSbIdx = i;
1043 } else if ([scroller type] == MMScrollerTypeBottom
1044 && range.location >= colMax) {
1045 colMax = range.location;
1051 // Place the scrollbars.
1052 for (i = 0; i < count; ++i) {
1053 MMScroller *scroller = [scrollbars objectAtIndex:i];
1054 if ([scroller isHidden])
1058 if ([scroller type] == MMScrollerTypeBottom) {
1059 rect = [textStorage rectForColumnsInRange:[scroller range]];
1060 rect.size.height = [NSScroller scrollerWidth];
1062 rect.origin.x += [NSScroller scrollerWidth];
1064 // HACK! Make sure the rightmost horizontal scrollbar covers the
1065 // text view all the way to the right, otherwise it looks ugly when
1066 // the user drags the window to resize.
1067 if (i == rightmostSbIdx) {
1068 float w = NSMaxX(textViewFrame) - NSMaxX(rect);
1070 rect.size.width += w;
1073 // Make sure scrollbar rect is bounded by the text view frame.
1074 if (rect.origin.x < textViewFrame.origin.x)
1075 rect.origin.x = textViewFrame.origin.x;
1076 else if (rect.origin.x > NSMaxX(textViewFrame))
1077 rect.origin.x = NSMaxX(textViewFrame);
1078 if (NSMaxX(rect) > NSMaxX(textViewFrame))
1079 rect.size.width -= NSMaxX(rect) - NSMaxX(textViewFrame);
1080 if (rect.size.width < 0)
1081 rect.size.width = 0;
1083 rect = [textStorage rectForRowsInRange:[scroller range]];
1084 // Adjust for the fact that text layout is flipped.
1085 rect.origin.y = NSMaxY(textViewFrame) - rect.origin.y
1087 rect.size.width = [NSScroller scrollerWidth];
1088 if ([scroller type] == MMScrollerTypeRight)
1089 rect.origin.x = NSMaxX(textViewFrame);
1091 // HACK! Make sure the lowest vertical scrollbar covers the text
1092 // view all the way to the bottom. This is done because Vim only
1093 // makes the scrollbar cover the (vim-)window it is associated with
1094 // and this means there is always an empty gap in the scrollbar
1095 // region next to the command line.
1096 // TODO! Find a nicer way to do this.
1097 if (i == lowestLeftSbIdx || i == lowestRightSbIdx) {
1098 float h = rect.origin.y + rect.size.height
1099 - textViewFrame.origin.y;
1100 if (rect.size.height < h) {
1101 rect.origin.y = textViewFrame.origin.y;
1102 rect.size.height = h;
1106 // Vertical scrollers must not cover the resize box in the
1107 // bottom-right corner of the window.
1108 if (rect.origin.y < [NSScroller scrollerWidth]) {
1109 rect.size.height -= [NSScroller scrollerWidth] - rect.origin.y;
1110 rect.origin.y = [NSScroller scrollerWidth];
1113 // Make sure scrollbar rect is bounded by the text view frame.
1114 if (rect.origin.y < textViewFrame.origin.y) {
1115 rect.size.height -= textViewFrame.origin.y - rect.origin.y;
1116 rect.origin.y = textViewFrame.origin.y;
1117 } else if (rect.origin.y > NSMaxY(textViewFrame))
1118 rect.origin.y = NSMaxY(textViewFrame);
1119 if (NSMaxY(rect) > NSMaxY(textViewFrame))
1120 rect.size.height -= NSMaxY(rect) - NSMaxY(textViewFrame);
1121 if (rect.size.height < 0)
1122 rect.size.height = 0;
1125 //NSLog(@"set scroller #%d frame = %@", i, NSStringFromRect(rect));
1126 NSRect oldRect = [scroller frame];
1127 if (!NSEqualRects(oldRect, rect)) {
1128 [scroller setFrame:rect];
1129 // Clear behind the old scroller frame, or parts of the old
1130 // scroller might still be visible after setFrame:.
1131 [[[self window] contentView] setNeedsDisplayInRect:oldRect];
1132 [scroller setNeedsDisplay:YES];
1137 - (void)scroll:(id)sender
1139 NSMutableData *data = [NSMutableData data];
1140 long ident = [(MMScroller*)sender identifier];
1141 int hitPart = [sender hitPart];
1142 float value = [sender floatValue];
1144 [data appendBytes:&ident length:sizeof(long)];
1145 [data appendBytes:&hitPart length:sizeof(int)];
1146 [data appendBytes:&value length:sizeof(float)];
1148 [vimController sendMessage:ScrollbarEventMsgID data:data];
1153 if (!setupDone) return;
1155 // NOTE! It is assumed that the window has been resized so that it will
1156 // exactly fit the text storage (possibly after resizing it). If this is
1157 // not the case the display might be messed up.
1158 NSWindow *win = [self window];
1159 NSRect contentRect = [win contentRectForFrameRect:[win frame]];
1160 NSRect textViewRect = [self textViewRectForContentSize:contentRect.size];
1161 NSSize tsSize = [self textStorageSizeForTextViewSize:textViewRect.size];
1163 int dim[2], rows, cols;
1164 [textStorage getMaxRows:&rows columns:&cols];
1165 [textStorage fitToSize:tsSize rows:&dim[0] columns:&dim[1]];
1167 if (dim[0] != rows || dim[1] != cols) {
1168 //NSLog(@"Notify Vim that text storage dimensions changed to %dx%d",
1170 NSData *data = [NSData dataWithBytes:dim length:2*sizeof(int)];
1172 [vimController sendMessage:SetTextDimensionsMsgID data:data];
1175 [textView setFrame:textViewRect];
1177 [self placeScrollbars];
1180 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb
1183 id backendProxy = [vimController backendProxy];
1187 reply = [backendProxy starRegisterToPasteboard:pb];
1189 @catch (NSException *e) {
1190 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1197 @end // MMWindowController (Private)
1201 @implementation NSTabView (MMExtras)
1203 - (void)removeAllTabViewItems
1205 NSArray *existingItems = [self tabViewItems];
1206 NSEnumerator *e = [existingItems objectEnumerator];
1207 NSTabViewItem *item;
1208 while (item = [e nextObject]){
1209 [self removeTabViewItem:item];
1213 @end // NSTabView (MMExtras)
1218 @implementation MMScroller
1220 - (id)initWithIdentifier:(long)ident type:(int)theType
1222 // HACK! NSScroller creates a horizontal scroller if it is init'ed with a
1223 // frame whose with exceeds its height; so create a bogus rect and pass it
1224 // to initWithFrame.
1225 NSRect frame = theType == MMScrollerTypeBottom
1226 ? NSMakeRect(0, 0, 1, 0)
1227 : NSMakeRect(0, 0, 0, 1);
1229 if ((self = [super initWithFrame:frame])) {
1232 [self setHidden:YES];
1233 [self setEnabled:YES];
1254 - (void)setRange:(NSRange)newRange
1259 - (void)scrollWheel:(NSEvent *)event
1261 // HACK! Pass message on to the text view.
1262 MMWindowController *wc = [[self window] windowController];
1263 [[wc textView] scrollWheel:event];