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 * A view class with a tabline, scrollbars, and a text view. The tabline may
14 * appear at the top of the view in which case it fills up the view from left
15 * to right edge. Any number of scrollbars may appear adjacent to all other
16 * edges of the view (there may be more than one scrollbar per edge and
17 * scrollbars may also be placed on the left edge of the view). The rest of
18 * the view is filled by the text view.
23 #import <PSMTabBarControl.h>
25 #import "MMTextView.h"
26 #import "MMVimController.h"
27 #import "MMAtsuiTextView.h"
31 // Scroller type; these must match SBAR_* in gui.h
33 MMScrollerTypeLeft = 0,
39 @interface NSTabView (MMExtras)
40 - (void)removeAllTabViewItems;
45 @interface MMScroller : NSScroller {
50 - (id)initWithIdentifier:(long)ident type:(int)type;
54 - (void)setRange:(NSRange)newRange;
58 @interface MMVimView (Private)
59 - (BOOL)bottomScrollbarVisible;
60 - (BOOL)leftScrollbarVisible;
61 - (BOOL)rightScrollbarVisible;
62 - (void)placeScrollbars;
63 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi;
64 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx;
65 - (NSSize)vimViewSizeForTextViewSize:(NSSize)textViewSize;
66 - (NSRect)textViewRectForVimViewSize:(NSSize)contentSize;
67 - (NSTabView *)tabView;
68 - (void)frameSizeMayHaveChanged;
72 // This is an informal protocol implemented by MMWindowController (maybe it
73 // shold be a formal protocol, but ...).
74 @interface NSWindowController (MMVimViewDelegate)
75 - (void)liveResizeWillStart;
76 - (void)liveResizeDidEnd;
81 @implementation MMVimView
83 - (MMVimView *)initWithFrame:(NSRect)frame
84 vimController:(MMVimController *)controller
86 if (![super initWithFrame:frame])
89 vimController = controller;
90 scrollbars = [[NSMutableArray alloc] init];
92 // Only the tabline is autoresized, all other subview placement is done in
93 // frameSizeMayHaveChanged.
94 [self setAutoresizesSubviews:YES];
96 if ([[NSUserDefaults standardUserDefaults] boolForKey:MMAtsuiRendererKey]) {
97 // Use ATSUI for text rendering.
99 // HACK! 'textView' has type MMTextView, but MMAtsuiTextView is not
100 // derived from MMTextView.
101 textView = [[MMAtsuiTextView alloc] initWithFrame:frame];
103 // Use Cocoa text system for text rendering.
104 textView = [[MMTextView alloc] initWithFrame:frame];
107 // Allow control of text view inset via MMTextInset* user defaults.
108 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
109 int left = [ud integerForKey:MMTextInsetLeftKey];
110 int top = [ud integerForKey:MMTextInsetTopKey];
111 [textView setTextContainerInset:NSMakeSize(left, top)];
113 [textView setAutoresizingMask:NSViewNotSizable];
114 [self addSubview:textView];
116 // Create the tab view (which is never visible, but the tab bar control
117 // needs it to function).
118 tabView = [[NSTabView alloc] initWithFrame:NSZeroRect];
120 // Create the tab bar control (which is responsible for actually
121 // drawing the tabline and tabs).
122 NSRect tabFrame = { { 0, frame.size.height - 22 },
123 { frame.size.width, 22 } };
124 tabBarControl = [[PSMTabBarControl alloc] initWithFrame:tabFrame];
126 [tabView setDelegate:tabBarControl];
128 [tabBarControl setTabView:tabView];
129 [tabBarControl setDelegate:self];
130 [tabBarControl setHidden:YES];
132 [tabBarControl setCellMinWidth:[ud integerForKey:MMTabMinWidthKey]];
133 [tabBarControl setCellMaxWidth:[ud integerForKey:MMTabMaxWidthKey]];
134 [tabBarControl setCellOptimumWidth:
135 [ud integerForKey:MMTabOptimumWidthKey]];
137 [tabBarControl setShowAddTabButton:YES];
138 [[tabBarControl addTabButton] setTarget:self];
139 [[tabBarControl addTabButton] setAction:@selector(addNewTab:)];
140 [tabBarControl setAllowsDragBetweenWindows:NO];
142 [tabBarControl setAutoresizingMask:NSViewWidthSizable|NSViewMinYMargin];
144 //[tabBarControl setPartnerView:textView];
146 // tab bar resizing only works if awakeFromNib is called (that's where
147 // the NSViewFrameDidChangeNotification callback is installed). Sounds like
148 // a PSMTabBarControl bug, let's live with it for now.
149 [tabBarControl awakeFromNib];
151 [self addSubview:tabBarControl];
158 [tabBarControl release]; tabBarControl = nil;
159 [tabView release]; tabView = nil;
160 [scrollbars release]; scrollbars = nil;
162 // HACK! The text storage is the principal owner of the text system, but we
163 // keep only a reference to the text view, so release the text storage
164 // first (unless we are using the ATSUI renderer).
165 if (![[NSUserDefaults standardUserDefaults]
166 boolForKey:MMAtsuiRendererKey])
167 [[textView textStorage] release];
169 [textView release]; textView = nil;
174 - (void)drawRect:(NSRect)rect
176 // On Leopard, we want to have a textured window background for nice
177 // looking tabs. However, the textured window background looks really
178 // weird behind the window resize throbber, so emulate the look of an
179 // NSScrollView in the bottom right corner.
180 if (![[self window] showsResizeIndicator] // XXX: make this a flag
181 || !([[self window] styleMask] & NSTexturedBackgroundWindowMask))
184 int sw = [NSScroller scrollerWidth];
186 // add .5 to the pixel locations to put the lines on a pixel boundary.
187 // the top and right edges of the rect will be outside of the bounds rect
189 NSRect sizerRect = NSMakeRect([self bounds].size.width - sw + .5, -.5,
191 //NSBezierPath* path = [NSBezierPath bezierPath];
192 NSBezierPath* path = [NSBezierPath bezierPathWithRect:sizerRect];
194 // On Tiger, we have color #E8E8E8 behind the resize throbber
195 // (which is windowBackgroundColor on untextured windows or controlColor in
196 // general). Terminal.app on Leopard has #FFFFFF background and #D9D9D9 as
197 // stroke. The colors below are #FFFFFF and #D4D4D4, which is close enough
199 [[NSColor controlBackgroundColor] set];
202 [[NSColor secondarySelectedControlColor] set];
206 - (MMTextView *)textView
211 - (NSMutableArray *)scrollbars
216 - (PSMTabBarControl *)tabBarControl
218 return tabBarControl;
225 // NOTE! There is a bug in PSMTabBarControl in that it retains the delegate
226 // so reset the delegate here, otherwise the delegate may never get
228 [tabView setDelegate:nil];
229 [tabBarControl setDelegate:nil];
230 [tabBarControl setTabView:nil];
231 [[self window] setDelegate:nil];
233 // NOTE! There is another bug in PSMTabBarControl where the control is not
234 // removed as an observer, so remove it here (failing to remove an observer
235 // may lead to very strange bugs).
236 [[NSNotificationCenter defaultCenter] removeObserver:tabBarControl];
238 [tabBarControl removeFromSuperviewWithoutNeedingDisplay];
239 [textView removeFromSuperviewWithoutNeedingDisplay];
241 unsigned i, count = [scrollbars count];
242 for (i = 0; i < count; ++i) {
243 MMScroller *sb = [scrollbars objectAtIndex:i];
244 [sb removeFromSuperviewWithoutNeedingDisplay];
247 [tabView removeAllTabViewItems];
250 - (NSSize)desiredSize
252 return [self vimViewSizeForTextViewSize:[textView desiredSize]];
257 return [self vimViewSizeForTextViewSize:[textView minSize]];
260 - (NSSize)constrainRows:(int *)r columns:(int *)c toSize:(NSSize)size
262 NSSize textViewSize = [self textViewRectForVimViewSize:size].size;
263 textViewSize = [textView constrainRows:r columns:c toSize:textViewSize];
264 return [self vimViewSizeForTextViewSize:textViewSize];
267 - (void)setDesiredRows:(int)r columns:(int)c
269 [textView setMaxRows:r columns:c];
272 - (IBAction)addNewTab:(id)sender
274 [vimController sendMessage:AddNewTabMsgID data:nil];
277 - (void)updateTabsWithData:(NSData *)data
279 const void *p = [data bytes];
280 const void *end = p + [data length];
283 // HACK! Current tab is first in the message. This way it is not
284 // necessary to guess which tab should be the selected one (this can be
285 // problematic for instance when new tabs are created).
286 int curtabIdx = *((int*)p); p += sizeof(int);
288 NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
291 //int wincount = *((int*)p); p += sizeof(int);
292 int length = *((int*)p); p += sizeof(int);
294 NSString *label = [[NSString alloc]
295 initWithBytes:(void*)p length:length
296 encoding:NSUTF8StringEncoding];
299 // Set the label of the tab; add a new tab when needed.
300 NSTabViewItem *tvi = [[self tabView] numberOfTabViewItems] <= tabIdx
301 ? [self addNewTabViewItem]
302 : [tabViewItems objectAtIndex:tabIdx];
304 [tvi setLabel:label];
311 // Remove unused tabs from the NSTabView. Note that when a tab is closed
312 // the NSTabView will automatically select another tab, but we want Vim to
313 // take care of which tab to select so set the vimTaskSelectedTab flag to
314 // prevent the tab selection message to be passed on to the VimTask.
315 vimTaskSelectedTab = YES;
316 int i, count = [[self tabView] numberOfTabViewItems];
317 for (i = count-1; i >= tabIdx; --i) {
318 id tvi = [tabViewItems objectAtIndex:i];
319 //NSLog(@"Removing tab with index %d", i);
320 [[self tabView] removeTabViewItem:tvi];
322 vimTaskSelectedTab = NO;
324 [self selectTabWithIndex:curtabIdx];
327 - (void)selectTabWithIndex:(int)idx
329 //NSLog(@"%s%d", _cmd, idx);
331 NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
332 if (idx < 0 || idx >= [tabViewItems count]) {
333 NSLog(@"WARNING: No tab with index %d exists.", idx);
337 // Do not try to select a tab if already selected.
338 NSTabViewItem *tvi = [tabViewItems objectAtIndex:idx];
339 if (tvi != [[self tabView] selectedTabViewItem]) {
340 vimTaskSelectedTab = YES;
341 [[self tabView] selectTabViewItem:tvi];
342 vimTaskSelectedTab = NO;
344 // We might need to change the scrollbars that are visible.
345 [self placeScrollbars];
349 - (NSTabViewItem *)addNewTabViewItem
351 // NOTE! A newly created tab is not by selected by default; Vim decides
352 // which tab should be selected at all times. However, the AppKit will
353 // automatically select the first tab added to a tab view.
355 NSTabViewItem *tvi = [[NSTabViewItem alloc] initWithIdentifier:nil];
357 // NOTE: If this is the first tab it will be automatically selected.
358 vimTaskSelectedTab = YES;
359 [[self tabView] addTabViewItem:tvi];
360 vimTaskSelectedTab = NO;
367 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
369 //NSLog(@"Create scroller %d of type %d", ident, type);
371 MMScroller *scroller = [[MMScroller alloc] initWithIdentifier:ident
373 [scroller setTarget:self];
374 [scroller setAction:@selector(scroll:)];
376 [self addSubview:scroller];
377 [[self scrollbars] addObject:scroller];
381 - (BOOL)destroyScrollbarWithIdentifier:(long)ident
383 //NSLog(@"Destroy scroller %d", ident);
386 MMScroller *scroller = [self scrollbarForIdentifier:ident index:&idx];
387 if (!scroller) return NO;
389 [scroller removeFromSuperview];
390 [[self scrollbars] removeObjectAtIndex:idx];
392 // If a visible scroller was removed then the vim view must resize. This
393 // is handled by the window controller (the vim view never resizes itself).
394 return ![scroller isHidden];
397 - (BOOL)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
399 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
400 if (!scroller) return NO;
402 BOOL wasVisible = ![scroller isHidden];
403 //NSLog(@"%s scroller %d (was %svisible)", visible ? "Show" : "Hide",
404 // ident, wasVisible ? "" : "in");
405 [scroller setHidden:!visible];
407 // If a scroller was hidden or shown then the vim view must resize. This
408 // is handled by the window controller (the vim view never resizes itself).
409 return wasVisible != visible;
412 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
413 identifier:(long)ident
415 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
416 //NSLog(@"Set thumb value %.2f proportion %.2f for scroller %d",
417 // val, prop, ident);
418 [scroller setFloatValue:val knobProportion:prop];
419 [scroller setEnabled:prop != 1.f];
423 - (void)scroll:(id)sender
425 NSMutableData *data = [NSMutableData data];
426 long ident = [(MMScroller*)sender identifier];
427 int hitPart = [sender hitPart];
428 float value = [sender floatValue];
430 [data appendBytes:&ident length:sizeof(long)];
431 [data appendBytes:&hitPart length:sizeof(int)];
432 [data appendBytes:&value length:sizeof(float)];
434 [vimController sendMessage:ScrollbarEventMsgID data:data];
437 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
439 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
440 NSRange range = NSMakeRange(pos, len);
441 if (!NSEqualRanges(range, [scroller range])) {
442 //NSLog(@"Set range %@ for scroller %d",
443 // NSStringFromRange(range), ident);
444 [scroller setRange:range];
445 // TODO! Should only do this once per update.
447 // This could be sent because a text window was created or closed, so
448 // we might need to update which scrollbars are visible.
449 [self placeScrollbars];
453 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
455 [textView setDefaultColorsBackground:back foreground:fore];
459 // -- PSMTabBarControl delegate ----------------------------------------------
462 - (BOOL)tabView:(NSTabView *)theTabView shouldSelectTabViewItem:
463 (NSTabViewItem *)tabViewItem
465 // NOTE: It would be reasonable to think that 'shouldSelect...' implies
466 // that this message only gets sent when the user clicks the tab.
467 // Unfortunately it is not so, which is why we need the
468 // 'vimTaskSelectedTab' flag.
470 // HACK! The selection message should not be propagated to Vim if Vim
471 // selected the tab (e.g. as opposed the user clicking the tab). The
472 // delegate method has no way of knowing who initiated the selection so a
473 // flag is set when Vim initiated the selection.
474 if (!vimTaskSelectedTab) {
475 // Propagate the selection message to Vim.
476 int idx = [self representedIndexOfTabViewItem:tabViewItem];
477 if (NSNotFound != idx) {
478 NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
479 [vimController sendMessage:SelectTabMsgID data:data];
483 // Unless Vim selected the tab, return NO, and let Vim decide if the tab
484 // should get selected or not.
485 return vimTaskSelectedTab;
488 - (BOOL)tabView:(NSTabView *)theTabView shouldCloseTabViewItem:
489 (NSTabViewItem *)tabViewItem
491 // HACK! This method is only called when the user clicks the close button
492 // on the tab. Instead of letting the tab bar close the tab, we return NO
493 // and pass a message on to Vim to let it handle the closing.
494 int idx = [self representedIndexOfTabViewItem:tabViewItem];
495 //NSLog(@"Closing tab with index %d", idx);
496 NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
497 [vimController sendMessage:CloseTabMsgID data:data];
502 - (void)tabView:(NSTabView *)theTabView didDragTabViewItem:
503 (NSTabViewItem *)tabViewItem toIndex:(int)idx
505 NSMutableData *data = [NSMutableData data];
506 [data appendBytes:&idx length:sizeof(int)];
508 [vimController sendMessage:DraggedTabMsgID data:data];
512 // -- NSView customization ---------------------------------------------------
515 - (void)viewWillStartLiveResize
517 id windowController = [[self window] windowController];
518 [windowController liveResizeWillStart];
520 [super viewWillStartLiveResize];
523 - (void)viewDidEndLiveResize
525 id windowController = [[self window] windowController];
526 [windowController liveResizeDidEnd];
528 [super viewDidEndLiveResize];
531 - (void)setFrameSize:(NSSize)size
533 // NOTE: Instead of only acting when a frame was resized, we do some
534 // updating each time a frame may be resized. (At the moment, if we only
535 // respond to actual frame changes then typing ":set lines=1000" twice in a
536 // row will result in the vim view holding more rows than the can fit
537 // inside the window.)
538 [super setFrameSize:size];
539 [self frameSizeMayHaveChanged];
542 - (void)setFrame:(NSRect)frame
544 // See comment in setFrameSize: above.
545 [super setFrame:frame];
546 [self frameSizeMayHaveChanged];
554 @implementation MMVimView (Private)
556 - (BOOL)bottomScrollbarVisible
558 unsigned i, count = [scrollbars count];
559 for (i = 0; i < count; ++i) {
560 MMScroller *scroller = [scrollbars objectAtIndex:i];
561 if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden])
568 - (BOOL)leftScrollbarVisible
570 unsigned i, count = [scrollbars count];
571 for (i = 0; i < count; ++i) {
572 MMScroller *scroller = [scrollbars objectAtIndex:i];
573 if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden])
580 - (BOOL)rightScrollbarVisible
582 unsigned i, count = [scrollbars count];
583 for (i = 0; i < count; ++i) {
584 MMScroller *scroller = [scrollbars objectAtIndex:i];
585 if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden])
592 - (void)placeScrollbars
594 NSRect textViewFrame = [textView frame];
595 BOOL lsbVisible = [self leftScrollbarVisible];
597 // HACK! Find the lowest left&right vertical scrollbars, as well as the
598 // rightmost horizontal scrollbar. This hack continues further down.
600 // TODO! Can there be no more than one horizontal scrollbar? If so, the
601 // code can be simplified.
602 unsigned lowestLeftSbIdx = (unsigned)-1;
603 unsigned lowestRightSbIdx = (unsigned)-1;
604 unsigned rightmostSbIdx = (unsigned)-1;
605 unsigned rowMaxLeft = 0, rowMaxRight = 0, colMax = 0;
606 unsigned i, count = [scrollbars count];
607 for (i = 0; i < count; ++i) {
608 MMScroller *scroller = [scrollbars objectAtIndex:i];
609 if (![scroller isHidden]) {
610 NSRange range = [scroller range];
611 if ([scroller type] == MMScrollerTypeLeft
612 && range.location >= rowMaxLeft) {
613 rowMaxLeft = range.location;
615 } else if ([scroller type] == MMScrollerTypeRight
616 && range.location >= rowMaxRight) {
617 rowMaxRight = range.location;
618 lowestRightSbIdx = i;
619 } else if ([scroller type] == MMScrollerTypeBottom
620 && range.location >= colMax) {
621 colMax = range.location;
627 // Place the scrollbars.
628 for (i = 0; i < count; ++i) {
629 MMScroller *scroller = [scrollbars objectAtIndex:i];
630 if ([scroller isHidden])
634 if ([scroller type] == MMScrollerTypeBottom) {
635 rect = [textView rectForColumnsInRange:[scroller range]];
636 rect.size.height = [NSScroller scrollerWidth];
638 rect.origin.x += [NSScroller scrollerWidth];
640 // HACK! Make sure the rightmost horizontal scrollbar covers the
641 // text view all the way to the right, otherwise it looks ugly when
642 // the user drags the window to resize.
643 if (i == rightmostSbIdx) {
644 float w = NSMaxX(textViewFrame) - NSMaxX(rect);
646 rect.size.width += w;
649 // Make sure scrollbar rect is bounded by the text view frame.
650 if (rect.origin.x < textViewFrame.origin.x)
651 rect.origin.x = textViewFrame.origin.x;
652 else if (rect.origin.x > NSMaxX(textViewFrame))
653 rect.origin.x = NSMaxX(textViewFrame);
654 if (NSMaxX(rect) > NSMaxX(textViewFrame))
655 rect.size.width -= NSMaxX(rect) - NSMaxX(textViewFrame);
656 if (rect.size.width < 0)
659 rect = [textView rectForRowsInRange:[scroller range]];
660 // Adjust for the fact that text layout is flipped.
661 rect.origin.y = NSMaxY(textViewFrame) - rect.origin.y
663 rect.size.width = [NSScroller scrollerWidth];
664 if ([scroller type] == MMScrollerTypeRight)
665 rect.origin.x = NSMaxX(textViewFrame);
667 // HACK! Make sure the lowest vertical scrollbar covers the text
668 // view all the way to the bottom. This is done because Vim only
669 // makes the scrollbar cover the (vim-)window it is associated with
670 // and this means there is always an empty gap in the scrollbar
671 // region next to the command line.
672 // TODO! Find a nicer way to do this.
673 if (i == lowestLeftSbIdx || i == lowestRightSbIdx) {
674 float h = rect.origin.y + rect.size.height
675 - textViewFrame.origin.y;
676 if (rect.size.height < h) {
677 rect.origin.y = textViewFrame.origin.y;
678 rect.size.height = h;
682 // Vertical scrollers must not cover the resize box in the
683 // bottom-right corner of the window.
684 if ([[self window] showsResizeIndicator] // XXX: make this a flag
685 && rect.origin.y < [NSScroller scrollerWidth]) {
686 rect.size.height -= [NSScroller scrollerWidth] - rect.origin.y;
687 rect.origin.y = [NSScroller scrollerWidth];
690 // Make sure scrollbar rect is bounded by the text view frame.
691 if (rect.origin.y < textViewFrame.origin.y) {
692 rect.size.height -= textViewFrame.origin.y - rect.origin.y;
693 rect.origin.y = textViewFrame.origin.y;
694 } else if (rect.origin.y > NSMaxY(textViewFrame))
695 rect.origin.y = NSMaxY(textViewFrame);
696 if (NSMaxY(rect) > NSMaxY(textViewFrame))
697 rect.size.height -= NSMaxY(rect) - NSMaxY(textViewFrame);
698 if (rect.size.height < 0)
699 rect.size.height = 0;
702 //NSLog(@"set scroller #%d frame = %@", i, NSStringFromRect(rect));
703 NSRect oldRect = [scroller frame];
704 if (!NSEqualRects(oldRect, rect)) {
705 [scroller setFrame:rect];
706 // Clear behind the old scroller frame, or parts of the old
707 // scroller might still be visible after setFrame:.
708 [[[self window] contentView] setNeedsDisplayInRect:oldRect];
709 [scroller setNeedsDisplay:YES];
714 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
716 NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
717 return [tabViewItems indexOfObject:tvi];
720 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx
722 unsigned i, count = [[self scrollbars] count];
723 for (i = 0; i < count; ++i) {
724 MMScroller *scroller = [[self scrollbars] objectAtIndex:i];
725 if ([scroller identifier] == ident) {
734 - (NSSize)vimViewSizeForTextViewSize:(NSSize)textViewSize
736 NSSize size = textViewSize;
738 if (![[self tabBarControl] isHidden])
739 size.height += [[self tabBarControl] frame].size.height;
741 if ([self bottomScrollbarVisible])
742 size.height += [NSScroller scrollerWidth];
743 if ([self leftScrollbarVisible])
744 size.width += [NSScroller scrollerWidth];
745 if ([self rightScrollbarVisible])
746 size.width += [NSScroller scrollerWidth];
751 - (NSRect)textViewRectForVimViewSize:(NSSize)contentSize
753 NSRect rect = { 0, 0, contentSize.width, contentSize.height };
755 if (![[self tabBarControl] isHidden])
756 rect.size.height -= [[self tabBarControl] frame].size.height;
758 if ([self bottomScrollbarVisible]) {
759 rect.size.height -= [NSScroller scrollerWidth];
760 rect.origin.y += [NSScroller scrollerWidth];
762 if ([self leftScrollbarVisible]) {
763 rect.size.width -= [NSScroller scrollerWidth];
764 rect.origin.x += [NSScroller scrollerWidth];
766 if ([self rightScrollbarVisible])
767 rect.size.width -= [NSScroller scrollerWidth];
772 - (NSTabView *)tabView
777 - (void)frameSizeMayHaveChanged
779 // NOTE: Whenever a call is made that may have changed the frame size we
780 // take the opportunity to make sure all subviews are in place and that the
781 // (rows,columns) are constrained to lie inside the new frame. We not only
782 // do this when the frame really has changed since it is possible to modify
783 // the number of (rows,columns) without changing the frame size.
785 // Give all superfluous space to the text view. It might be smaller or
786 // larger than it wants to be, but this is needed during live resizing.
787 NSRect textViewRect = [self textViewRectForVimViewSize:[self frame].size];
788 [textView setFrame:textViewRect];
790 [self placeScrollbars];
792 // It is possible that the current number of (rows,columns) is too big or
793 // too small to fit the new frame. If so, notify Vim that the text
794 // dimensions should change, but don't actually change the number of
795 // (rows,columns). These numbers may only change when Vim initiates the
796 // change (as opposed to the user dragging the window resizer, for
799 // Note that the message sent to Vim depends on whether we're in
800 // a live resize or not -- this is necessary to avoid the window jittering
801 // when the user drags to resize.
803 NSSize textViewSize = [textView frame].size;
804 [textView constrainRows:&constrained[0] columns:&constrained[1]
805 toSize:textViewSize];
808 [textView getMaxRows:&rows columns:&cols];
810 if (constrained[0] != rows || constrained[1] != cols) {
811 NSData *data = [NSData dataWithBytes:constrained length:2*sizeof(int)];
812 int msgid = [self inLiveResize] ? LiveResizeMsgID
813 : SetTextDimensionsMsgID;
815 //NSLog(@"Notify Vim that text dimensions changed from %dx%d to %dx%d"
816 // " (%s)", cols, rows, constrained[1], constrained[0],
817 // MessageStrings[msgid]);
819 [vimController sendMessage:msgid data:data];
821 // We only want to set the window title if this resize came from
822 // a live-resize, not (for example) setting 'columns' or 'lines'.
823 if ([self inLiveResize]) {
824 [[self window] setTitle:[NSString stringWithFormat:@"%dx%d",
825 constrained[1], constrained[0]]];
830 @end // MMVimView (Private)
835 @implementation NSTabView (MMExtras)
837 - (void)removeAllTabViewItems
839 NSArray *existingItems = [self tabViewItems];
840 NSEnumerator *e = [existingItems objectEnumerator];
842 while (item = [e nextObject]){
843 [self removeTabViewItem:item];
847 @end // NSTabView (MMExtras)
852 @implementation MMScroller
854 - (id)initWithIdentifier:(long)ident type:(int)theType
856 // HACK! NSScroller creates a horizontal scroller if it is init'ed with a
857 // frame whose with exceeds its height; so create a bogus rect and pass it
859 NSRect frame = theType == MMScrollerTypeBottom
860 ? NSMakeRect(0, 0, 1, 0)
861 : NSMakeRect(0, 0, 0, 1);
863 self = [super initWithFrame:frame];
864 if (!self) return nil;
868 [self setHidden:YES];
869 [self setEnabled:YES];
870 [self setAutoresizingMask:NSViewNotSizable];
890 - (void)setRange:(NSRange)newRange
895 - (void)scrollWheel:(NSEvent *)event
897 // HACK! Pass message on to the text view.
898 NSView *vimView = [self superview];
899 if ([vimView isKindOfClass:[MMVimView class]])
900 [[(MMVimView*)vimView textView] scrollWheel:event];