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.
22 # import "MMAtsuiTextView.h"
24 # import "MMCoreTextView.h"
26 #import "MMTextView.h"
27 #import "MMVimController.h"
29 #import "MMWindowController.h"
30 #import "Miscellaneous.h"
31 #import <PSMTabBarControl/PSMTabBarControl.h>
35 // Scroller type; these must match SBAR_* in gui.h
37 MMScrollerTypeLeft = 0,
44 @interface MMScroller : NSScroller {
49 - (id)initWithIdentifier:(int32_t)ident type:(int)type;
50 - (int32_t)scrollerId;
53 - (void)setRange:(NSRange)newRange;
57 @interface MMVimView (Private)
58 - (BOOL)bottomScrollbarVisible;
59 - (BOOL)leftScrollbarVisible;
60 - (BOOL)rightScrollbarVisible;
61 - (void)placeScrollbars;
62 - (NSUInteger)representedIndexOfTabViewItem:(NSTabViewItem *)tvi;
63 - (MMScroller *)scrollbarForIdentifier:(int32_t)ident index:(unsigned *)idx;
64 - (NSSize)vimViewSizeForTextViewSize:(NSSize)textViewSize;
65 - (NSRect)textViewRectForVimViewSize:(NSSize)contentSize;
66 - (NSTabView *)tabView;
67 - (void)frameSizeMayHaveChanged;
71 // This is an informal protocol implemented by MMWindowController (maybe it
72 // shold be a formal protocol, but ...).
73 @interface NSWindowController (MMVimViewDelegate)
74 - (void)liveResizeWillStart;
75 - (void)liveResizeDidEnd;
80 @implementation MMVimView
82 - (MMVimView *)initWithFrame:(NSRect)frame
83 vimController:(MMVimController *)controller
85 if (![super initWithFrame:frame])
88 vimController = controller;
89 scrollbars = [[NSMutableArray alloc] init];
91 // Only the tabline is autoresized, all other subview placement is done in
92 // frameSizeMayHaveChanged.
93 [self setAutoresizesSubviews:YES];
95 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
96 NSInteger renderer = [ud integerForKey:MMRendererKey];
97 ASLogInfo(@"Use renderer=%d", renderer);
99 if (MMRendererCoreText == renderer) {
100 // HACK! 'textView' has type MMTextView, but MMCoreTextView is not
101 // derived from MMTextView.
102 textView = [[MMCoreTextView alloc] initWithFrame:frame];
104 } else if (MMRendererATSUI == renderer) {
105 // HACK! 'textView' has type MMTextView, but MMAtsuiTextView is not
106 // derived from MMTextView.
107 textView = [[MMAtsuiTextView alloc] initWithFrame:frame];
110 // Use Cocoa text system for text rendering.
111 textView = [[MMTextView alloc] initWithFrame:frame];
114 // Allow control of text view inset via MMTextInset* user defaults.
115 int left = [ud integerForKey:MMTextInsetLeftKey];
116 int top = [ud integerForKey:MMTextInsetTopKey];
117 [textView setTextContainerInset:NSMakeSize(left, top)];
119 [textView setAutoresizingMask:NSViewNotSizable];
120 [self addSubview:textView];
122 // Create the tab view (which is never visible, but the tab bar control
123 // needs it to function).
124 tabView = [[NSTabView alloc] initWithFrame:NSZeroRect];
126 // Create the tab bar control (which is responsible for actually
127 // drawing the tabline and tabs).
128 NSRect tabFrame = { { 0, frame.size.height - 22 },
129 { frame.size.width, 22 } };
130 tabBarControl = [[PSMTabBarControl alloc] initWithFrame:tabFrame];
132 [tabView setDelegate:tabBarControl];
134 [tabBarControl setTabView:tabView];
135 [tabBarControl setDelegate:self];
136 [tabBarControl setHidden:YES];
138 [tabBarControl setCellMinWidth:[ud integerForKey:MMTabMinWidthKey]];
139 [tabBarControl setCellMaxWidth:[ud integerForKey:MMTabMaxWidthKey]];
140 [tabBarControl setCellOptimumWidth:
141 [ud integerForKey:MMTabOptimumWidthKey]];
143 [tabBarControl setShowAddTabButton:[ud boolForKey:MMShowAddTabButtonKey]];
144 [[tabBarControl addTabButton] setTarget:self];
145 [[tabBarControl addTabButton] setAction:@selector(addNewTab:)];
146 [tabBarControl setAllowsDragBetweenWindows:NO];
147 [tabBarControl registerForDraggedTypes:
148 [NSArray arrayWithObject:NSFilenamesPboardType]];
150 [tabBarControl setAutoresizingMask:NSViewWidthSizable|NSViewMinYMargin];
152 //[tabBarControl setPartnerView:textView];
154 // tab bar resizing only works if awakeFromNib is called (that's where
155 // the NSViewFrameDidChangeNotification callback is installed). Sounds like
156 // a PSMTabBarControl bug, let's live with it for now.
157 [tabBarControl awakeFromNib];
159 [self addSubview:tabBarControl];
168 [tabBarControl release]; tabBarControl = nil;
169 [tabView release]; tabView = nil;
170 [scrollbars release]; scrollbars = nil;
172 // HACK! The text storage is the principal owner of the text system, but we
173 // keep only a reference to the text view, so release the text storage
174 // first (unless we are using the ATSUI renderer).
175 if ([textView isKindOfClass:[MMTextView class]])
176 [[textView textStorage] release];
178 [textView release]; textView = nil;
188 - (void)drawRect:(NSRect)rect
190 // On Leopard, we want to have a textured window background for nice
191 // looking tabs. However, the textured window background looks really
192 // weird behind the window resize throbber, so emulate the look of an
193 // NSScrollView in the bottom right corner.
194 if (![[self window] showsResizeIndicator] // XXX: make this a flag
195 || !([[self window] styleMask] & NSTexturedBackgroundWindowMask))
198 int sw = [NSScroller scrollerWidth];
200 // add .5 to the pixel locations to put the lines on a pixel boundary.
201 // the top and right edges of the rect will be outside of the bounds rect
203 NSRect sizerRect = NSMakeRect([self bounds].size.width - sw + .5, -.5,
205 //NSBezierPath* path = [NSBezierPath bezierPath];
206 NSBezierPath* path = [NSBezierPath bezierPathWithRect:sizerRect];
208 // On Tiger, we have color #E8E8E8 behind the resize throbber
209 // (which is windowBackgroundColor on untextured windows or controlColor in
210 // general). Terminal.app on Leopard has #FFFFFF background and #D9D9D9 as
211 // stroke. The colors below are #FFFFFF and #D4D4D4, which is close enough
213 [[NSColor controlBackgroundColor] set];
216 [[NSColor secondarySelectedControlColor] set];
220 - (MMTextView *)textView
225 - (PSMTabBarControl *)tabBarControl
227 return tabBarControl;
234 // NOTE! There is a bug in PSMTabBarControl in that it retains the delegate
235 // so reset the delegate here, otherwise the delegate may never get
237 [tabView setDelegate:nil];
238 [tabBarControl setDelegate:nil];
239 [tabBarControl setTabView:nil];
240 [[self window] setDelegate:nil];
242 // NOTE! There is another bug in PSMTabBarControl where the control is not
243 // removed as an observer, so remove it here (failing to remove an observer
244 // may lead to very strange bugs).
245 [[NSNotificationCenter defaultCenter] removeObserver:tabBarControl];
247 [tabBarControl removeFromSuperviewWithoutNeedingDisplay];
248 [textView removeFromSuperviewWithoutNeedingDisplay];
250 unsigned i, count = [scrollbars count];
251 for (i = 0; i < count; ++i) {
252 MMScroller *sb = [scrollbars objectAtIndex:i];
253 [sb removeFromSuperviewWithoutNeedingDisplay];
256 [tabView removeAllTabViewItems];
259 - (NSSize)desiredSize
261 return [self vimViewSizeForTextViewSize:[textView desiredSize]];
266 return [self vimViewSizeForTextViewSize:[textView minSize]];
269 - (NSSize)constrainRows:(int *)r columns:(int *)c toSize:(NSSize)size
271 NSSize textViewSize = [self textViewRectForVimViewSize:size].size;
272 textViewSize = [textView constrainRows:r columns:c toSize:textViewSize];
273 return [self vimViewSizeForTextViewSize:textViewSize];
276 - (void)setDesiredRows:(int)r columns:(int)c
278 [textView setMaxRows:r columns:c];
281 - (IBAction)addNewTab:(id)sender
283 [vimController sendMessage:AddNewTabMsgID data:nil];
286 - (void)updateTabsWithData:(NSData *)data
288 const void *p = [data bytes];
289 const void *end = p + [data length];
292 // HACK! Current tab is first in the message. This way it is not
293 // necessary to guess which tab should be the selected one (this can be
294 // problematic for instance when new tabs are created).
295 int curtabIdx = *((int*)p); p += sizeof(int);
297 NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
300 NSTabViewItem *tvi = nil;
302 //int wincount = *((int*)p); p += sizeof(int);
303 int infoCount = *((int*)p); p += sizeof(int);
305 for (i = 0; i < infoCount; ++i) {
306 int length = *((int*)p); p += sizeof(int);
310 NSString *val = [[NSString alloc]
311 initWithBytes:(void*)p length:length
312 encoding:NSUTF8StringEncoding];
317 // Set the label of the tab, adding a new tab when needed.
318 tvi = [[self tabView] numberOfTabViewItems] <= tabIdx
319 ? [self addNewTabViewItem]
320 : [tabViewItems objectAtIndex:tabIdx];
326 [[self tabBarControl] setToolTip:val
330 ASLogWarn(@"Unknown tab info for index: %d", i);
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 = [[self tabView] numberOfTabViewItems];
343 for (i = count-1; i >= tabIdx; --i) {
344 id tvi = [tabViewItems objectAtIndex:i];
345 [[self tabView] removeTabViewItem:tvi];
347 vimTaskSelectedTab = NO;
349 [self selectTabWithIndex:curtabIdx];
352 - (void)selectTabWithIndex:(int)idx
354 NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
355 if (idx < 0 || idx >= [tabViewItems count]) {
356 ASLogWarn(@"No tab with index %d exists.", idx);
360 // Do not try to select a tab if already selected.
361 NSTabViewItem *tvi = [tabViewItems objectAtIndex:idx];
362 if (tvi != [[self tabView] selectedTabViewItem]) {
363 vimTaskSelectedTab = YES;
364 [[self tabView] selectTabViewItem:tvi];
365 vimTaskSelectedTab = NO;
367 // We might need to change the scrollbars that are visible.
368 [self placeScrollbars];
372 - (NSTabViewItem *)addNewTabViewItem
374 // NOTE! A newly created tab is not by selected by default; Vim decides
375 // which tab should be selected at all times. However, the AppKit will
376 // automatically select the first tab added to a tab view.
378 NSTabViewItem *tvi = [[NSTabViewItem alloc] initWithIdentifier:nil];
380 // NOTE: If this is the first tab it will be automatically selected.
381 vimTaskSelectedTab = YES;
382 [[self tabView] addTabViewItem:tvi];
383 vimTaskSelectedTab = NO;
390 - (void)createScrollbarWithIdentifier:(int32_t)ident type:(int)type
392 MMScroller *scroller = [[MMScroller alloc] initWithIdentifier:ident
394 [scroller setTarget:self];
395 [scroller setAction:@selector(scroll:)];
397 [self addSubview:scroller];
398 [scrollbars addObject:scroller];
402 - (BOOL)destroyScrollbarWithIdentifier:(int32_t)ident
405 MMScroller *scroller = [self scrollbarForIdentifier:ident index:&idx];
406 if (!scroller) return NO;
408 [scroller removeFromSuperview];
409 [scrollbars removeObjectAtIndex:idx];
411 // If a visible scroller was removed then the vim view must resize. This
412 // is handled by the window controller (the vim view never resizes itself).
413 return ![scroller isHidden];
416 - (BOOL)showScrollbarWithIdentifier:(int32_t)ident state:(BOOL)visible
418 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
419 if (!scroller) return NO;
421 BOOL wasVisible = ![scroller isHidden];
422 [scroller setHidden:!visible];
424 // If a scroller was hidden or shown then the vim view must resize. This
425 // is handled by the window controller (the vim view never resizes itself).
426 return wasVisible != visible;
429 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
430 identifier:(int32_t)ident
432 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
433 #if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)
434 [scroller setDoubleValue:val];
435 [scroller setKnobProportion:prop];
437 [scroller setFloatValue:val knobProportion:prop];
439 [scroller setEnabled:prop != 1.f];
443 - (void)scroll:(id)sender
445 NSMutableData *data = [NSMutableData data];
446 int32_t ident = [(MMScroller*)sender scrollerId];
447 int hitPart = [sender hitPart];
448 float value = [sender floatValue];
450 [data appendBytes:&ident length:sizeof(int32_t)];
451 [data appendBytes:&hitPart length:sizeof(int)];
452 [data appendBytes:&value length:sizeof(float)];
454 [vimController sendMessage:ScrollbarEventMsgID data:data];
457 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(int32_t)ident
459 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
460 NSRange range = NSMakeRange(pos, len);
461 if (!NSEqualRanges(range, [scroller range])) {
462 [scroller setRange:range];
463 // TODO! Should only do this once per update.
465 // This could be sent because a text window was created or closed, so
466 // we might need to update which scrollbars are visible.
467 [self placeScrollbars];
471 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
473 [textView setDefaultColorsBackground:back foreground:fore];
477 // -- PSMTabBarControl delegate ----------------------------------------------
480 - (BOOL)tabView:(NSTabView *)theTabView shouldSelectTabViewItem:
481 (NSTabViewItem *)tabViewItem
483 // NOTE: It would be reasonable to think that 'shouldSelect...' implies
484 // that this message only gets sent when the user clicks the tab.
485 // Unfortunately it is not so, which is why we need the
486 // 'vimTaskSelectedTab' flag.
488 // HACK! The selection message should not be propagated to Vim if Vim
489 // selected the tab (e.g. as opposed the user clicking the tab). The
490 // delegate method has no way of knowing who initiated the selection so a
491 // flag is set when Vim initiated the selection.
492 if (!vimTaskSelectedTab) {
493 // Propagate the selection message to Vim.
494 NSUInteger idx = [self representedIndexOfTabViewItem:tabViewItem];
495 if (NSNotFound != idx) {
496 int i = (int)idx; // HACK! Never more than MAXINT tabs?!
497 NSData *data = [NSData dataWithBytes:&i length:sizeof(int)];
498 [vimController sendMessage:SelectTabMsgID data:data];
502 // Unless Vim selected the tab, return NO, and let Vim decide if the tab
503 // should get selected or not.
504 return vimTaskSelectedTab;
507 - (BOOL)tabView:(NSTabView *)theTabView shouldCloseTabViewItem:
508 (NSTabViewItem *)tabViewItem
510 // HACK! This method is only called when the user clicks the close button
511 // on the tab. Instead of letting the tab bar close the tab, we return NO
512 // and pass a message on to Vim to let it handle the closing.
513 NSUInteger idx = [self representedIndexOfTabViewItem:tabViewItem];
514 int i = (int)idx; // HACK! Never more than MAXINT tabs?!
515 NSData *data = [NSData dataWithBytes:&i length:sizeof(int)];
516 [vimController sendMessage:CloseTabMsgID data:data];
521 - (void)tabView:(NSTabView *)theTabView didDragTabViewItem:
522 (NSTabViewItem *)tabViewItem toIndex:(int)idx
524 NSMutableData *data = [NSMutableData data];
525 [data appendBytes:&idx length:sizeof(int)];
527 [vimController sendMessage:DraggedTabMsgID data:data];
530 - (NSDragOperation)tabBarControl:(PSMTabBarControl *)theTabBarControl
531 draggingEntered:(id <NSDraggingInfo>)sender
532 forTabAtIndex:(NSUInteger)tabIndex
534 NSPasteboard *pb = [sender draggingPasteboard];
535 return [[pb types] containsObject:NSFilenamesPboardType]
536 ? NSDragOperationCopy
537 : NSDragOperationNone;
540 - (BOOL)tabBarControl:(PSMTabBarControl *)theTabBarControl
541 performDragOperation:(id <NSDraggingInfo>)sender
542 forTabAtIndex:(NSUInteger)tabIndex
544 NSPasteboard *pb = [sender draggingPasteboard];
545 if ([[pb types] containsObject:NSFilenamesPboardType]) {
546 NSArray *filenames = [pb propertyListForType:NSFilenamesPboardType];
547 if ([filenames count] == 0)
549 if (tabIndex != NSNotFound) {
550 // If dropping on a specific tab, only open one file
551 [vimController file:[filenames objectAtIndex:0]
552 draggedToTabAtIndex:tabIndex];
554 // Files were dropped on empty part of tab bar; open them all
555 [vimController filesDraggedToTabBar:filenames];
565 // -- NSView customization ---------------------------------------------------
568 - (void)viewWillStartLiveResize
570 id windowController = [[self window] windowController];
571 [windowController liveResizeWillStart];
573 [super viewWillStartLiveResize];
576 - (void)viewDidEndLiveResize
578 id windowController = [[self window] windowController];
579 [windowController liveResizeDidEnd];
581 [super viewDidEndLiveResize];
584 - (void)setFrameSize:(NSSize)size
586 // NOTE: Instead of only acting when a frame was resized, we do some
587 // updating each time a frame may be resized. (At the moment, if we only
588 // respond to actual frame changes then typing ":set lines=1000" twice in a
589 // row will result in the vim view holding more rows than the can fit
590 // inside the window.)
591 [super setFrameSize:size];
592 [self frameSizeMayHaveChanged];
595 - (void)setFrame:(NSRect)frame
597 // See comment in setFrameSize: above.
598 [super setFrame:frame];
599 [self frameSizeMayHaveChanged];
607 @implementation MMVimView (Private)
609 - (BOOL)bottomScrollbarVisible
611 unsigned i, count = [scrollbars count];
612 for (i = 0; i < count; ++i) {
613 MMScroller *scroller = [scrollbars objectAtIndex:i];
614 if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden])
621 - (BOOL)leftScrollbarVisible
623 unsigned i, count = [scrollbars count];
624 for (i = 0; i < count; ++i) {
625 MMScroller *scroller = [scrollbars objectAtIndex:i];
626 if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden])
633 - (BOOL)rightScrollbarVisible
635 unsigned i, count = [scrollbars count];
636 for (i = 0; i < count; ++i) {
637 MMScroller *scroller = [scrollbars objectAtIndex:i];
638 if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden])
645 - (void)placeScrollbars
647 NSRect textViewFrame = [textView frame];
648 BOOL lsbVisible = [self leftScrollbarVisible];
650 // HACK! Find the lowest left&right vertical scrollbars, as well as the
651 // rightmost horizontal scrollbar. This hack continues further down.
653 // TODO! Can there be no more than one horizontal scrollbar? If so, the
654 // code can be simplified.
655 unsigned lowestLeftSbIdx = (unsigned)-1;
656 unsigned lowestRightSbIdx = (unsigned)-1;
657 unsigned rightmostSbIdx = (unsigned)-1;
658 unsigned rowMaxLeft = 0, rowMaxRight = 0, colMax = 0;
659 unsigned i, count = [scrollbars count];
660 for (i = 0; i < count; ++i) {
661 MMScroller *scroller = [scrollbars objectAtIndex:i];
662 if (![scroller isHidden]) {
663 NSRange range = [scroller range];
664 if ([scroller type] == MMScrollerTypeLeft
665 && range.location >= rowMaxLeft) {
666 rowMaxLeft = range.location;
668 } else if ([scroller type] == MMScrollerTypeRight
669 && range.location >= rowMaxRight) {
670 rowMaxRight = range.location;
671 lowestRightSbIdx = i;
672 } else if ([scroller type] == MMScrollerTypeBottom
673 && range.location >= colMax) {
674 colMax = range.location;
680 // Place the scrollbars.
681 for (i = 0; i < count; ++i) {
682 MMScroller *scroller = [scrollbars objectAtIndex:i];
683 if ([scroller isHidden])
687 if ([scroller type] == MMScrollerTypeBottom) {
688 rect = [textView rectForColumnsInRange:[scroller range]];
689 rect.size.height = [NSScroller scrollerWidth];
691 rect.origin.x += [NSScroller scrollerWidth];
693 // HACK! Make sure the rightmost horizontal scrollbar covers the
694 // text view all the way to the right, otherwise it looks ugly when
695 // the user drags the window to resize.
696 if (i == rightmostSbIdx) {
697 float w = NSMaxX(textViewFrame) - NSMaxX(rect);
699 rect.size.width += w;
702 // Make sure scrollbar rect is bounded by the text view frame.
703 if (rect.origin.x < textViewFrame.origin.x)
704 rect.origin.x = textViewFrame.origin.x;
705 else if (rect.origin.x > NSMaxX(textViewFrame))
706 rect.origin.x = NSMaxX(textViewFrame);
707 if (NSMaxX(rect) > NSMaxX(textViewFrame))
708 rect.size.width -= NSMaxX(rect) - NSMaxX(textViewFrame);
709 if (rect.size.width < 0)
712 rect = [textView rectForRowsInRange:[scroller range]];
713 // Adjust for the fact that text layout is flipped.
714 rect.origin.y = NSMaxY(textViewFrame) - rect.origin.y
716 rect.size.width = [NSScroller scrollerWidth];
717 if ([scroller type] == MMScrollerTypeRight)
718 rect.origin.x = NSMaxX(textViewFrame);
720 // HACK! Make sure the lowest vertical scrollbar covers the text
721 // view all the way to the bottom. This is done because Vim only
722 // makes the scrollbar cover the (vim-)window it is associated with
723 // and this means there is always an empty gap in the scrollbar
724 // region next to the command line.
725 // TODO! Find a nicer way to do this.
726 if (i == lowestLeftSbIdx || i == lowestRightSbIdx) {
727 float h = rect.origin.y + rect.size.height
728 - textViewFrame.origin.y;
729 if (rect.size.height < h) {
730 rect.origin.y = textViewFrame.origin.y;
731 rect.size.height = h;
735 // Vertical scrollers must not cover the resize box in the
736 // bottom-right corner of the window.
737 if ([[self window] showsResizeIndicator] // XXX: make this a flag
738 && rect.origin.y < [NSScroller scrollerWidth]) {
739 rect.size.height -= [NSScroller scrollerWidth] - rect.origin.y;
740 rect.origin.y = [NSScroller scrollerWidth];
743 // Make sure scrollbar rect is bounded by the text view frame.
744 if (rect.origin.y < textViewFrame.origin.y) {
745 rect.size.height -= textViewFrame.origin.y - rect.origin.y;
746 rect.origin.y = textViewFrame.origin.y;
747 } else if (rect.origin.y > NSMaxY(textViewFrame))
748 rect.origin.y = NSMaxY(textViewFrame);
749 if (NSMaxY(rect) > NSMaxY(textViewFrame))
750 rect.size.height -= NSMaxY(rect) - NSMaxY(textViewFrame);
751 if (rect.size.height < 0)
752 rect.size.height = 0;
755 NSRect oldRect = [scroller frame];
756 if (!NSEqualRects(oldRect, rect)) {
757 [scroller setFrame:rect];
758 // Clear behind the old scroller frame, or parts of the old
759 // scroller might still be visible after setFrame:.
760 [[[self window] contentView] setNeedsDisplayInRect:oldRect];
761 [scroller setNeedsDisplay:YES];
766 - (NSUInteger)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
768 NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
769 return [tabViewItems indexOfObject:tvi];
772 - (MMScroller *)scrollbarForIdentifier:(int32_t)ident index:(unsigned *)idx
774 unsigned i, count = [scrollbars count];
775 for (i = 0; i < count; ++i) {
776 MMScroller *scroller = [scrollbars objectAtIndex:i];
777 if ([scroller scrollerId] == ident) {
786 - (NSSize)vimViewSizeForTextViewSize:(NSSize)textViewSize
788 NSSize size = textViewSize;
790 if (![[self tabBarControl] isHidden])
791 size.height += [[self tabBarControl] frame].size.height;
793 if ([self bottomScrollbarVisible])
794 size.height += [NSScroller scrollerWidth];
795 if ([self leftScrollbarVisible])
796 size.width += [NSScroller scrollerWidth];
797 if ([self rightScrollbarVisible])
798 size.width += [NSScroller scrollerWidth];
803 - (NSRect)textViewRectForVimViewSize:(NSSize)contentSize
805 NSRect rect = { {0, 0}, {contentSize.width, contentSize.height} };
807 if (![[self tabBarControl] isHidden])
808 rect.size.height -= [[self tabBarControl] frame].size.height;
810 if ([self bottomScrollbarVisible]) {
811 rect.size.height -= [NSScroller scrollerWidth];
812 rect.origin.y += [NSScroller scrollerWidth];
814 if ([self leftScrollbarVisible]) {
815 rect.size.width -= [NSScroller scrollerWidth];
816 rect.origin.x += [NSScroller scrollerWidth];
818 if ([self rightScrollbarVisible])
819 rect.size.width -= [NSScroller scrollerWidth];
824 - (NSTabView *)tabView
829 - (void)frameSizeMayHaveChanged
831 // NOTE: Whenever a call is made that may have changed the frame size we
832 // take the opportunity to make sure all subviews are in place and that the
833 // (rows,columns) are constrained to lie inside the new frame. We not only
834 // do this when the frame really has changed since it is possible to modify
835 // the number of (rows,columns) without changing the frame size.
837 // Give all superfluous space to the text view. It might be smaller or
838 // larger than it wants to be, but this is needed during live resizing.
839 NSRect textViewRect = [self textViewRectForVimViewSize:[self frame].size];
840 [textView setFrame:textViewRect];
842 [self placeScrollbars];
844 // It is possible that the current number of (rows,columns) is too big or
845 // too small to fit the new frame. If so, notify Vim that the text
846 // dimensions should change, but don't actually change the number of
847 // (rows,columns). These numbers may only change when Vim initiates the
848 // change (as opposed to the user dragging the window resizer, for
851 // Note that the message sent to Vim depends on whether we're in
852 // a live resize or not -- this is necessary to avoid the window jittering
853 // when the user drags to resize.
855 NSSize textViewSize = [textView frame].size;
856 [textView constrainRows:&constrained[0] columns:&constrained[1]
857 toSize:textViewSize];
860 [textView getMaxRows:&rows columns:&cols];
862 if (constrained[0] != rows || constrained[1] != cols) {
863 NSData *data = [NSData dataWithBytes:constrained length:2*sizeof(int)];
864 int msgid = [self inLiveResize] ? LiveResizeMsgID
865 : SetTextDimensionsMsgID;
867 ASLogDebug(@"Notify Vim that text dimensions changed from %dx%d to "
868 "%dx%d (%s)", cols, rows, constrained[1], constrained[0],
869 MessageStrings[msgid]);
871 [vimController sendMessage:msgid data:data];
873 // We only want to set the window title if this resize came from
874 // a live-resize, not (for example) setting 'columns' or 'lines'.
875 if ([self inLiveResize]) {
876 [[self window] setTitle:[NSString stringWithFormat:@"%dx%d",
877 constrained[1], constrained[0]]];
882 @end // MMVimView (Private)
887 @implementation MMScroller
889 - (id)initWithIdentifier:(int32_t)ident type:(int)theType
891 // HACK! NSScroller creates a horizontal scroller if it is init'ed with a
892 // frame whose with exceeds its height; so create a bogus rect and pass it
894 NSRect frame = theType == MMScrollerTypeBottom
895 ? NSMakeRect(0, 0, 1, 0)
896 : NSMakeRect(0, 0, 0, 1);
898 self = [super initWithFrame:frame];
899 if (!self) return nil;
903 [self setHidden:YES];
904 [self setEnabled:YES];
905 [self setAutoresizingMask:NSViewNotSizable];
910 - (int32_t)scrollerId
925 - (void)setRange:(NSRange)newRange
930 - (void)scrollWheel:(NSEvent *)event
932 // HACK! Pass message on to the text view.
933 NSView *vimView = [self superview];
934 if ([vimView isKindOfClass:[MMVimView class]])
935 [[(MMVimView*)vimView textView] scrollWheel:event];
938 - (void)mouseDown:(NSEvent *)event
940 // TODO: This is an ugly way of getting the connection to the backend.
941 NSConnection *connection = nil;
942 id wc = [[self window] windowController];
943 if ([wc isKindOfClass:[MMWindowController class]]) {
944 MMVimController *vc = [(MMWindowController*)wc vimController];
945 id proxy = [vc backendProxy];
946 connection = [(NSDistantObject*)proxy connectionForProxy];
949 // NOTE: The scroller goes into "event tracking mode" when the user clicks
950 // (and holds) the mouse button. We have to manually add the backend
951 // connection to this mode while the mouse button is held, else DO messages
952 // from Vim will not be processed until the mouse button is released.
953 [connection addRequestMode:NSEventTrackingRunLoopMode];
954 [super mouseDown:event];
955 [connection removeRequestMode:NSEventTrackingRunLoopMode];