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.
21 #import "MMAtsuiTextView.h"
22 #import "MMTextView.h"
23 #import "MMVimController.h"
25 #import "MMWindowController.h"
26 #import "Miscellaneous.h"
27 #import <PSMTabBarControl.h>
31 // Scroller type; these must match SBAR_* in gui.h
33 MMScrollerTypeLeft = 0,
40 @interface MMScroller : NSScroller {
45 - (id)initWithIdentifier:(long)ident type:(int)type;
49 - (void)setRange:(NSRange)newRange;
53 @interface MMVimView (Private)
54 - (BOOL)bottomScrollbarVisible;
55 - (BOOL)leftScrollbarVisible;
56 - (BOOL)rightScrollbarVisible;
57 - (void)placeScrollbars;
58 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi;
59 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx;
60 - (NSSize)vimViewSizeForTextViewSize:(NSSize)textViewSize;
61 - (NSRect)textViewRectForVimViewSize:(NSSize)contentSize;
62 - (NSTabView *)tabView;
63 - (void)frameSizeMayHaveChanged;
67 // This is an informal protocol implemented by MMWindowController (maybe it
68 // shold be a formal protocol, but ...).
69 @interface NSWindowController (MMVimViewDelegate)
70 - (void)liveResizeWillStart;
71 - (void)liveResizeDidEnd;
76 @implementation MMVimView
78 - (MMVimView *)initWithFrame:(NSRect)frame
79 vimController:(MMVimController *)controller
81 if (![super initWithFrame:frame])
84 vimController = controller;
85 scrollbars = [[NSMutableArray alloc] init];
87 // Only the tabline is autoresized, all other subview placement is done in
88 // frameSizeMayHaveChanged.
89 [self setAutoresizesSubviews:YES];
91 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
92 if ([ud boolForKey:MMAtsuiRendererKey]) {
93 // Use ATSUI for text rendering.
95 // HACK! 'textView' has type MMTextView, but MMAtsuiTextView is not
96 // derived from MMTextView.
97 textView = [[MMAtsuiTextView alloc] initWithFrame:frame];
99 // Use Cocoa text system for text rendering.
100 textView = [[MMTextView alloc] initWithFrame:frame];
103 // Allow control of text view inset via MMTextInset* user defaults.
104 int left = [ud integerForKey:MMTextInsetLeftKey];
105 int top = [ud integerForKey:MMTextInsetTopKey];
106 [textView setTextContainerInset:NSMakeSize(left, top)];
108 [textView setAutoresizingMask:NSViewNotSizable];
109 [self addSubview:textView];
111 // Create the tab view (which is never visible, but the tab bar control
112 // needs it to function).
113 tabView = [[NSTabView alloc] initWithFrame:NSZeroRect];
115 // Create the tab bar control (which is responsible for actually
116 // drawing the tabline and tabs).
117 NSRect tabFrame = { { 0, frame.size.height - 22 },
118 { frame.size.width, 22 } };
119 tabBarControl = [[PSMTabBarControl alloc] initWithFrame:tabFrame];
121 [tabView setDelegate:tabBarControl];
123 [tabBarControl setTabView:tabView];
124 [tabBarControl setDelegate:self];
125 [tabBarControl setHidden:YES];
127 [tabBarControl setCellMinWidth:[ud integerForKey:MMTabMinWidthKey]];
128 [tabBarControl setCellMaxWidth:[ud integerForKey:MMTabMaxWidthKey]];
129 [tabBarControl setCellOptimumWidth:
130 [ud integerForKey:MMTabOptimumWidthKey]];
132 [tabBarControl setShowAddTabButton:[ud boolForKey:MMShowAddTabButtonKey]];
133 [[tabBarControl addTabButton] setTarget:self];
134 [[tabBarControl addTabButton] setAction:@selector(addNewTab:)];
135 [tabBarControl setAllowsDragBetweenWindows:NO];
136 [tabBarControl registerForDraggedTypes:
137 [NSArray arrayWithObject:NSFilenamesPboardType]];
139 [tabBarControl setAutoresizingMask:NSViewWidthSizable|NSViewMinYMargin];
141 //[tabBarControl setPartnerView:textView];
143 // tab bar resizing only works if awakeFromNib is called (that's where
144 // the NSViewFrameDidChangeNotification callback is installed). Sounds like
145 // a PSMTabBarControl bug, let's live with it for now.
146 [tabBarControl awakeFromNib];
148 [self addSubview:tabBarControl];
157 [tabBarControl release]; tabBarControl = nil;
158 [tabView release]; tabView = nil;
159 [scrollbars release]; scrollbars = nil;
161 // HACK! The text storage is the principal owner of the text system, but we
162 // keep only a reference to the text view, so release the text storage
163 // first (unless we are using the ATSUI renderer).
164 if ([textView isKindOfClass:[MMTextView class]])
165 [[textView textStorage] release];
167 [textView release]; textView = nil;
172 - (void)drawRect:(NSRect)rect
174 // On Leopard, we want to have a textured window background for nice
175 // looking tabs. However, the textured window background looks really
176 // weird behind the window resize throbber, so emulate the look of an
177 // NSScrollView in the bottom right corner.
178 if (![[self window] showsResizeIndicator] // XXX: make this a flag
179 || !([[self window] styleMask] & NSTexturedBackgroundWindowMask))
182 int sw = [NSScroller scrollerWidth];
184 // add .5 to the pixel locations to put the lines on a pixel boundary.
185 // the top and right edges of the rect will be outside of the bounds rect
187 NSRect sizerRect = NSMakeRect([self bounds].size.width - sw + .5, -.5,
189 //NSBezierPath* path = [NSBezierPath bezierPath];
190 NSBezierPath* path = [NSBezierPath bezierPathWithRect:sizerRect];
192 // On Tiger, we have color #E8E8E8 behind the resize throbber
193 // (which is windowBackgroundColor on untextured windows or controlColor in
194 // general). Terminal.app on Leopard has #FFFFFF background and #D9D9D9 as
195 // stroke. The colors below are #FFFFFF and #D4D4D4, which is close enough
197 [[NSColor controlBackgroundColor] set];
200 [[NSColor secondarySelectedControlColor] set];
204 - (MMTextView *)textView
209 - (PSMTabBarControl *)tabBarControl
211 return tabBarControl;
218 // NOTE! There is a bug in PSMTabBarControl in that it retains the delegate
219 // so reset the delegate here, otherwise the delegate may never get
221 [tabView setDelegate:nil];
222 [tabBarControl setDelegate:nil];
223 [tabBarControl setTabView:nil];
224 [[self window] setDelegate:nil];
226 // NOTE! There is another bug in PSMTabBarControl where the control is not
227 // removed as an observer, so remove it here (failing to remove an observer
228 // may lead to very strange bugs).
229 [[NSNotificationCenter defaultCenter] removeObserver:tabBarControl];
231 [tabBarControl removeFromSuperviewWithoutNeedingDisplay];
232 [textView removeFromSuperviewWithoutNeedingDisplay];
234 unsigned i, count = [scrollbars count];
235 for (i = 0; i < count; ++i) {
236 MMScroller *sb = [scrollbars objectAtIndex:i];
237 [sb removeFromSuperviewWithoutNeedingDisplay];
240 [tabView removeAllTabViewItems];
243 - (NSSize)desiredSize
245 return [self vimViewSizeForTextViewSize:[textView desiredSize]];
250 return [self vimViewSizeForTextViewSize:[textView minSize]];
253 - (NSSize)constrainRows:(int *)r columns:(int *)c toSize:(NSSize)size
255 NSSize textViewSize = [self textViewRectForVimViewSize:size].size;
256 textViewSize = [textView constrainRows:r columns:c toSize:textViewSize];
257 return [self vimViewSizeForTextViewSize:textViewSize];
260 - (void)setDesiredRows:(int)r columns:(int)c
262 [textView setMaxRows:r columns:c];
265 - (IBAction)addNewTab:(id)sender
267 [vimController sendMessage:AddNewTabMsgID data:nil];
270 - (void)updateTabsWithData:(NSData *)data
272 const void *p = [data bytes];
273 const void *end = p + [data length];
276 // HACK! Current tab is first in the message. This way it is not
277 // necessary to guess which tab should be the selected one (this can be
278 // problematic for instance when new tabs are created).
279 int curtabIdx = *((int*)p); p += sizeof(int);
281 NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
284 NSTabViewItem *tvi = nil;
286 //int wincount = *((int*)p); p += sizeof(int);
287 int infoCount = *((int*)p); p += sizeof(int);
289 for (i = 0; i < infoCount; ++i) {
290 int length = *((int*)p); p += sizeof(int);
294 NSString *val = [[NSString alloc]
295 initWithBytes:(void*)p length:length
296 encoding:NSUTF8StringEncoding];
301 // Set the label of the tab, adding a new tab when needed.
302 tvi = [[self tabView] numberOfTabViewItems] <= tabIdx
303 ? [self addNewTabViewItem]
304 : [tabViewItems objectAtIndex:tabIdx];
310 [[self tabBarControl] setToolTip:val
314 NSLog(@"WARNING: Unknown tab info for index: %d", i);
321 // Remove unused tabs from the NSTabView. Note that when a tab is closed
322 // the NSTabView will automatically select another tab, but we want Vim to
323 // take care of which tab to select so set the vimTaskSelectedTab flag to
324 // prevent the tab selection message to be passed on to the VimTask.
325 vimTaskSelectedTab = YES;
326 int i, count = [[self tabView] numberOfTabViewItems];
327 for (i = count-1; i >= tabIdx; --i) {
328 id tvi = [tabViewItems objectAtIndex:i];
329 //NSLog(@"Removing tab with index %d", i);
330 [[self tabView] removeTabViewItem:tvi];
332 vimTaskSelectedTab = NO;
334 [self selectTabWithIndex:curtabIdx];
337 - (void)selectTabWithIndex:(int)idx
339 //NSLog(@"%s%d", _cmd, idx);
341 NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
342 if (idx < 0 || idx >= [tabViewItems count]) {
343 NSLog(@"WARNING: No tab with index %d exists.", idx);
347 // Do not try to select a tab if already selected.
348 NSTabViewItem *tvi = [tabViewItems objectAtIndex:idx];
349 if (tvi != [[self tabView] selectedTabViewItem]) {
350 vimTaskSelectedTab = YES;
351 [[self tabView] selectTabViewItem:tvi];
352 vimTaskSelectedTab = NO;
354 // We might need to change the scrollbars that are visible.
355 [self placeScrollbars];
359 - (NSTabViewItem *)addNewTabViewItem
361 // NOTE! A newly created tab is not by selected by default; Vim decides
362 // which tab should be selected at all times. However, the AppKit will
363 // automatically select the first tab added to a tab view.
365 NSTabViewItem *tvi = [[NSTabViewItem alloc] initWithIdentifier:nil];
367 // NOTE: If this is the first tab it will be automatically selected.
368 vimTaskSelectedTab = YES;
369 [[self tabView] addTabViewItem:tvi];
370 vimTaskSelectedTab = NO;
377 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
379 //NSLog(@"Create scroller %d of type %d", ident, type);
381 MMScroller *scroller = [[MMScroller alloc] initWithIdentifier:ident
383 [scroller setTarget:self];
384 [scroller setAction:@selector(scroll:)];
386 [self addSubview:scroller];
387 [scrollbars addObject:scroller];
391 - (BOOL)destroyScrollbarWithIdentifier:(long)ident
393 //NSLog(@"Destroy scroller %d", ident);
396 MMScroller *scroller = [self scrollbarForIdentifier:ident index:&idx];
397 if (!scroller) return NO;
399 [scroller removeFromSuperview];
400 [scrollbars removeObjectAtIndex:idx];
402 // If a visible scroller was removed then the vim view must resize. This
403 // is handled by the window controller (the vim view never resizes itself).
404 return ![scroller isHidden];
407 - (BOOL)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
409 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
410 if (!scroller) return NO;
412 BOOL wasVisible = ![scroller isHidden];
413 //NSLog(@"%s scroller %d (was %svisible)", visible ? "Show" : "Hide",
414 // ident, wasVisible ? "" : "in");
415 [scroller setHidden:!visible];
417 // If a scroller was hidden or shown then the vim view must resize. This
418 // is handled by the window controller (the vim view never resizes itself).
419 return wasVisible != visible;
422 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
423 identifier:(long)ident
425 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
426 //NSLog(@"Set thumb value %.2f proportion %.2f for scroller %d",
427 // val, prop, ident);
428 [scroller setFloatValue:val knobProportion:prop];
429 [scroller setEnabled:prop != 1.f];
433 - (void)scroll:(id)sender
435 NSMutableData *data = [NSMutableData data];
436 long ident = [(MMScroller*)sender identifier];
437 int hitPart = [sender hitPart];
438 float value = [sender floatValue];
440 [data appendBytes:&ident length:sizeof(long)];
441 [data appendBytes:&hitPart length:sizeof(int)];
442 [data appendBytes:&value length:sizeof(float)];
444 [vimController sendMessage:ScrollbarEventMsgID data:data];
447 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
449 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
450 NSRange range = NSMakeRange(pos, len);
451 if (!NSEqualRanges(range, [scroller range])) {
452 //NSLog(@"Set range %@ for scroller %d",
453 // NSStringFromRange(range), ident);
454 [scroller setRange:range];
455 // TODO! Should only do this once per update.
457 // This could be sent because a text window was created or closed, so
458 // we might need to update which scrollbars are visible.
459 [self placeScrollbars];
463 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
465 [textView setDefaultColorsBackground:back foreground:fore];
469 // -- PSMTabBarControl delegate ----------------------------------------------
472 - (BOOL)tabView:(NSTabView *)theTabView shouldSelectTabViewItem:
473 (NSTabViewItem *)tabViewItem
475 // NOTE: It would be reasonable to think that 'shouldSelect...' implies
476 // that this message only gets sent when the user clicks the tab.
477 // Unfortunately it is not so, which is why we need the
478 // 'vimTaskSelectedTab' flag.
480 // HACK! The selection message should not be propagated to Vim if Vim
481 // selected the tab (e.g. as opposed the user clicking the tab). The
482 // delegate method has no way of knowing who initiated the selection so a
483 // flag is set when Vim initiated the selection.
484 if (!vimTaskSelectedTab) {
485 // Propagate the selection message to Vim.
486 int idx = [self representedIndexOfTabViewItem:tabViewItem];
487 if (NSNotFound != idx) {
488 NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
489 [vimController sendMessage:SelectTabMsgID data:data];
493 // Unless Vim selected the tab, return NO, and let Vim decide if the tab
494 // should get selected or not.
495 return vimTaskSelectedTab;
498 - (BOOL)tabView:(NSTabView *)theTabView shouldCloseTabViewItem:
499 (NSTabViewItem *)tabViewItem
501 // HACK! This method is only called when the user clicks the close button
502 // on the tab. Instead of letting the tab bar close the tab, we return NO
503 // and pass a message on to Vim to let it handle the closing.
504 int idx = [self representedIndexOfTabViewItem:tabViewItem];
505 //NSLog(@"Closing tab with index %d", idx);
506 NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
507 [vimController sendMessage:CloseTabMsgID data:data];
512 - (void)tabView:(NSTabView *)theTabView didDragTabViewItem:
513 (NSTabViewItem *)tabViewItem toIndex:(int)idx
515 NSMutableData *data = [NSMutableData data];
516 [data appendBytes:&idx length:sizeof(int)];
518 [vimController sendMessage:DraggedTabMsgID data:data];
521 - (NSDragOperation)tabBarControl:(PSMTabBarControl *)theTabBarControl
522 draggingEntered:(id <NSDraggingInfo>)sender
523 forTabAtIndex:(unsigned)tabIndex
525 NSPasteboard *pb = [sender draggingPasteboard];
526 return [[pb types] containsObject:NSFilenamesPboardType]
527 ? NSDragOperationCopy
528 : NSDragOperationNone;
531 - (BOOL)tabBarControl:(PSMTabBarControl *)theTabBarControl
532 performDragOperation:(id <NSDraggingInfo>)sender
533 forTabAtIndex:(unsigned)tabIndex
535 NSPasteboard *pb = [sender draggingPasteboard];
536 if ([[pb types] containsObject:NSFilenamesPboardType]) {
537 NSArray *filenames = [pb propertyListForType:NSFilenamesPboardType];
538 if ([filenames count] == 0)
540 if (tabIndex != NSNotFound) {
541 // If dropping on a specific tab, only open one file
542 [vimController file:[filenames objectAtIndex:0]
543 draggedToTabAtIndex:tabIndex];
545 // Files were dropped on empty part of tab bar; open them all
546 [vimController filesDraggedToTabBar:filenames];
556 // -- NSView customization ---------------------------------------------------
559 - (void)viewWillStartLiveResize
561 id windowController = [[self window] windowController];
562 [windowController liveResizeWillStart];
564 [super viewWillStartLiveResize];
567 - (void)viewDidEndLiveResize
569 id windowController = [[self window] windowController];
570 [windowController liveResizeDidEnd];
572 [super viewDidEndLiveResize];
575 - (void)setFrameSize:(NSSize)size
577 // NOTE: Instead of only acting when a frame was resized, we do some
578 // updating each time a frame may be resized. (At the moment, if we only
579 // respond to actual frame changes then typing ":set lines=1000" twice in a
580 // row will result in the vim view holding more rows than the can fit
581 // inside the window.)
582 [super setFrameSize:size];
583 [self frameSizeMayHaveChanged];
586 - (void)setFrame:(NSRect)frame
588 // See comment in setFrameSize: above.
589 [super setFrame:frame];
590 [self frameSizeMayHaveChanged];
598 @implementation MMVimView (Private)
600 - (BOOL)bottomScrollbarVisible
602 unsigned i, count = [scrollbars count];
603 for (i = 0; i < count; ++i) {
604 MMScroller *scroller = [scrollbars objectAtIndex:i];
605 if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden])
612 - (BOOL)leftScrollbarVisible
614 unsigned i, count = [scrollbars count];
615 for (i = 0; i < count; ++i) {
616 MMScroller *scroller = [scrollbars objectAtIndex:i];
617 if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden])
624 - (BOOL)rightScrollbarVisible
626 unsigned i, count = [scrollbars count];
627 for (i = 0; i < count; ++i) {
628 MMScroller *scroller = [scrollbars objectAtIndex:i];
629 if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden])
636 - (void)placeScrollbars
638 NSRect textViewFrame = [textView frame];
639 BOOL lsbVisible = [self leftScrollbarVisible];
641 // HACK! Find the lowest left&right vertical scrollbars, as well as the
642 // rightmost horizontal scrollbar. This hack continues further down.
644 // TODO! Can there be no more than one horizontal scrollbar? If so, the
645 // code can be simplified.
646 unsigned lowestLeftSbIdx = (unsigned)-1;
647 unsigned lowestRightSbIdx = (unsigned)-1;
648 unsigned rightmostSbIdx = (unsigned)-1;
649 unsigned rowMaxLeft = 0, rowMaxRight = 0, colMax = 0;
650 unsigned i, count = [scrollbars count];
651 for (i = 0; i < count; ++i) {
652 MMScroller *scroller = [scrollbars objectAtIndex:i];
653 if (![scroller isHidden]) {
654 NSRange range = [scroller range];
655 if ([scroller type] == MMScrollerTypeLeft
656 && range.location >= rowMaxLeft) {
657 rowMaxLeft = range.location;
659 } else if ([scroller type] == MMScrollerTypeRight
660 && range.location >= rowMaxRight) {
661 rowMaxRight = range.location;
662 lowestRightSbIdx = i;
663 } else if ([scroller type] == MMScrollerTypeBottom
664 && range.location >= colMax) {
665 colMax = range.location;
671 // Place the scrollbars.
672 for (i = 0; i < count; ++i) {
673 MMScroller *scroller = [scrollbars objectAtIndex:i];
674 if ([scroller isHidden])
678 if ([scroller type] == MMScrollerTypeBottom) {
679 rect = [textView rectForColumnsInRange:[scroller range]];
680 rect.size.height = [NSScroller scrollerWidth];
682 rect.origin.x += [NSScroller scrollerWidth];
684 // HACK! Make sure the rightmost horizontal scrollbar covers the
685 // text view all the way to the right, otherwise it looks ugly when
686 // the user drags the window to resize.
687 if (i == rightmostSbIdx) {
688 float w = NSMaxX(textViewFrame) - NSMaxX(rect);
690 rect.size.width += w;
693 // Make sure scrollbar rect is bounded by the text view frame.
694 if (rect.origin.x < textViewFrame.origin.x)
695 rect.origin.x = textViewFrame.origin.x;
696 else if (rect.origin.x > NSMaxX(textViewFrame))
697 rect.origin.x = NSMaxX(textViewFrame);
698 if (NSMaxX(rect) > NSMaxX(textViewFrame))
699 rect.size.width -= NSMaxX(rect) - NSMaxX(textViewFrame);
700 if (rect.size.width < 0)
703 rect = [textView rectForRowsInRange:[scroller range]];
704 // Adjust for the fact that text layout is flipped.
705 rect.origin.y = NSMaxY(textViewFrame) - rect.origin.y
707 rect.size.width = [NSScroller scrollerWidth];
708 if ([scroller type] == MMScrollerTypeRight)
709 rect.origin.x = NSMaxX(textViewFrame);
711 // HACK! Make sure the lowest vertical scrollbar covers the text
712 // view all the way to the bottom. This is done because Vim only
713 // makes the scrollbar cover the (vim-)window it is associated with
714 // and this means there is always an empty gap in the scrollbar
715 // region next to the command line.
716 // TODO! Find a nicer way to do this.
717 if (i == lowestLeftSbIdx || i == lowestRightSbIdx) {
718 float h = rect.origin.y + rect.size.height
719 - textViewFrame.origin.y;
720 if (rect.size.height < h) {
721 rect.origin.y = textViewFrame.origin.y;
722 rect.size.height = h;
726 // Vertical scrollers must not cover the resize box in the
727 // bottom-right corner of the window.
728 if ([[self window] showsResizeIndicator] // XXX: make this a flag
729 && rect.origin.y < [NSScroller scrollerWidth]) {
730 rect.size.height -= [NSScroller scrollerWidth] - rect.origin.y;
731 rect.origin.y = [NSScroller scrollerWidth];
734 // Make sure scrollbar rect is bounded by the text view frame.
735 if (rect.origin.y < textViewFrame.origin.y) {
736 rect.size.height -= textViewFrame.origin.y - rect.origin.y;
737 rect.origin.y = textViewFrame.origin.y;
738 } else if (rect.origin.y > NSMaxY(textViewFrame))
739 rect.origin.y = NSMaxY(textViewFrame);
740 if (NSMaxY(rect) > NSMaxY(textViewFrame))
741 rect.size.height -= NSMaxY(rect) - NSMaxY(textViewFrame);
742 if (rect.size.height < 0)
743 rect.size.height = 0;
746 //NSLog(@"set scroller #%d frame = %@", i, NSStringFromRect(rect));
747 NSRect oldRect = [scroller frame];
748 if (!NSEqualRects(oldRect, rect)) {
749 [scroller setFrame:rect];
750 // Clear behind the old scroller frame, or parts of the old
751 // scroller might still be visible after setFrame:.
752 [[[self window] contentView] setNeedsDisplayInRect:oldRect];
753 [scroller setNeedsDisplay:YES];
758 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
760 NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
761 return [tabViewItems indexOfObject:tvi];
764 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx
766 unsigned i, count = [scrollbars count];
767 for (i = 0; i < count; ++i) {
768 MMScroller *scroller = [scrollbars objectAtIndex:i];
769 if ([scroller identifier] == ident) {
778 - (NSSize)vimViewSizeForTextViewSize:(NSSize)textViewSize
780 NSSize size = textViewSize;
782 if (![[self tabBarControl] isHidden])
783 size.height += [[self tabBarControl] frame].size.height;
785 if ([self bottomScrollbarVisible])
786 size.height += [NSScroller scrollerWidth];
787 if ([self leftScrollbarVisible])
788 size.width += [NSScroller scrollerWidth];
789 if ([self rightScrollbarVisible])
790 size.width += [NSScroller scrollerWidth];
795 - (NSRect)textViewRectForVimViewSize:(NSSize)contentSize
797 NSRect rect = { 0, 0, contentSize.width, contentSize.height };
799 if (![[self tabBarControl] isHidden])
800 rect.size.height -= [[self tabBarControl] frame].size.height;
802 if ([self bottomScrollbarVisible]) {
803 rect.size.height -= [NSScroller scrollerWidth];
804 rect.origin.y += [NSScroller scrollerWidth];
806 if ([self leftScrollbarVisible]) {
807 rect.size.width -= [NSScroller scrollerWidth];
808 rect.origin.x += [NSScroller scrollerWidth];
810 if ([self rightScrollbarVisible])
811 rect.size.width -= [NSScroller scrollerWidth];
816 - (NSTabView *)tabView
821 - (void)frameSizeMayHaveChanged
823 // NOTE: Whenever a call is made that may have changed the frame size we
824 // take the opportunity to make sure all subviews are in place and that the
825 // (rows,columns) are constrained to lie inside the new frame. We not only
826 // do this when the frame really has changed since it is possible to modify
827 // the number of (rows,columns) without changing the frame size.
829 // Give all superfluous space to the text view. It might be smaller or
830 // larger than it wants to be, but this is needed during live resizing.
831 NSRect textViewRect = [self textViewRectForVimViewSize:[self frame].size];
832 [textView setFrame:textViewRect];
834 [self placeScrollbars];
836 // It is possible that the current number of (rows,columns) is too big or
837 // too small to fit the new frame. If so, notify Vim that the text
838 // dimensions should change, but don't actually change the number of
839 // (rows,columns). These numbers may only change when Vim initiates the
840 // change (as opposed to the user dragging the window resizer, for
843 // Note that the message sent to Vim depends on whether we're in
844 // a live resize or not -- this is necessary to avoid the window jittering
845 // when the user drags to resize.
847 NSSize textViewSize = [textView frame].size;
848 [textView constrainRows:&constrained[0] columns:&constrained[1]
849 toSize:textViewSize];
852 [textView getMaxRows:&rows columns:&cols];
854 if (constrained[0] != rows || constrained[1] != cols) {
855 NSData *data = [NSData dataWithBytes:constrained length:2*sizeof(int)];
856 int msgid = [self inLiveResize] ? LiveResizeMsgID
857 : SetTextDimensionsMsgID;
859 //NSLog(@"Notify Vim that text dimensions changed from %dx%d to %dx%d"
860 // " (%s)", cols, rows, constrained[1], constrained[0],
861 // MessageStrings[msgid]);
863 [vimController sendMessage:msgid data:data];
865 // We only want to set the window title if this resize came from
866 // a live-resize, not (for example) setting 'columns' or 'lines'.
867 if ([self inLiveResize]) {
868 [[self window] setTitle:[NSString stringWithFormat:@"%dx%d",
869 constrained[1], constrained[0]]];
874 @end // MMVimView (Private)
879 @implementation MMScroller
881 - (id)initWithIdentifier:(long)ident type:(int)theType
883 // HACK! NSScroller creates a horizontal scroller if it is init'ed with a
884 // frame whose with exceeds its height; so create a bogus rect and pass it
886 NSRect frame = theType == MMScrollerTypeBottom
887 ? NSMakeRect(0, 0, 1, 0)
888 : NSMakeRect(0, 0, 0, 1);
890 self = [super initWithFrame:frame];
891 if (!self) return nil;
895 [self setHidden:YES];
896 [self setEnabled:YES];
897 [self setAutoresizingMask:NSViewNotSizable];
917 - (void)setRange:(NSRange)newRange
922 - (void)scrollWheel:(NSEvent *)event
924 // HACK! Pass message on to the text view.
925 NSView *vimView = [self superview];
926 if ([vimView isKindOfClass:[MMVimView class]])
927 [[(MMVimView*)vimView textView] scrollWheel:event];
930 - (void)mouseDown:(NSEvent *)event
932 // TODO: This is an ugly way of getting the connection to the backend.
933 NSConnection *connection = nil;
934 id wc = [[self window] windowController];
935 if ([wc isKindOfClass:[MMWindowController class]]) {
936 MMVimController *vc = [(MMWindowController*)wc vimController];
937 id proxy = [vc backendProxy];
938 connection = [(NSDistantObject*)proxy connectionForProxy];
941 // NOTE: The scroller goes into "event tracking mode" when the user clicks
942 // (and holds) the mouse button. We have to manually add the backend
943 // connection to this mode while the mouse button is held, else DO messages
944 // from Vim will not be processed until the mouse button is released.
945 [connection addRequestMode:NSEventTrackingRunLoopMode];
946 [super mouseDown:event];
947 [connection removeRequestMode:NSEventTrackingRunLoopMode];