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 "Miscellaneous.h" // Defines MM_ENABLE_ATSUI
24 # import "MMAtsuiTextView.h"
26 # import "MMCoreTextView.h"
28 #import "MMTextView.h"
29 #import "MMVimController.h"
31 #import "MMWindowController.h"
32 #import <PSMTabBarControl/PSMTabBarControl.h>
36 // Scroller type; these must match SBAR_* in gui.h
38 MMScrollerTypeLeft = 0,
45 @interface MMScroller : NSScroller {
50 - (id)initWithIdentifier:(int32_t)ident type:(int)type;
51 - (int32_t)scrollerId;
54 - (void)setRange:(NSRange)newRange;
58 @interface MMVimView (Private)
59 - (BOOL)bottomScrollbarVisible;
60 - (BOOL)leftScrollbarVisible;
61 - (BOOL)rightScrollbarVisible;
62 - (void)placeScrollbars;
63 - (NSUInteger)representedIndexOfTabViewItem:(NSTabViewItem *)tvi;
64 - (MMScroller *)scrollbarForIdentifier:(int32_t)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 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
97 NSInteger renderer = [ud integerForKey:MMRendererKey];
98 ASLogInfo(@"Use renderer=%d", renderer);
101 if (MMRendererATSUI == renderer) {
102 // HACK! 'textView' has type MMTextView, but MMAtsuiTextView is not
103 // derived from MMTextView.
104 textView = [[MMAtsuiTextView alloc] initWithFrame:frame];
107 if (MMRendererCoreText == renderer) {
108 // HACK! 'textView' has type MMTextView, but MMCoreTextView is not
109 // derived from MMTextView.
110 textView = [[MMCoreTextView alloc] initWithFrame:frame];
114 // Use Cocoa text system for text rendering.
115 textView = [[MMTextView alloc] initWithFrame:frame];
118 // Allow control of text view inset via MMTextInset* user defaults.
119 int left = [ud integerForKey:MMTextInsetLeftKey];
120 int top = [ud integerForKey:MMTextInsetTopKey];
121 [textView setTextContainerInset:NSMakeSize(left, top)];
123 [textView setAutoresizingMask:NSViewNotSizable];
124 [self addSubview:textView];
126 // Create the tab view (which is never visible, but the tab bar control
127 // needs it to function).
128 tabView = [[NSTabView alloc] initWithFrame:NSZeroRect];
130 // Create the tab bar control (which is responsible for actually
131 // drawing the tabline and tabs).
132 NSRect tabFrame = { { 0, frame.size.height - 22 },
133 { frame.size.width, 22 } };
134 tabBarControl = [[PSMTabBarControl alloc] initWithFrame:tabFrame];
136 [tabView setDelegate:tabBarControl];
138 [tabBarControl setTabView:tabView];
139 [tabBarControl setDelegate:self];
140 [tabBarControl setHidden:YES];
142 [tabBarControl setCellMinWidth:[ud integerForKey:MMTabMinWidthKey]];
143 [tabBarControl setCellMaxWidth:[ud integerForKey:MMTabMaxWidthKey]];
144 [tabBarControl setCellOptimumWidth:
145 [ud integerForKey:MMTabOptimumWidthKey]];
147 [tabBarControl setShowAddTabButton:[ud boolForKey:MMShowAddTabButtonKey]];
148 [[tabBarControl addTabButton] setTarget:self];
149 [[tabBarControl addTabButton] setAction:@selector(addNewTab:)];
150 [tabBarControl setAllowsDragBetweenWindows:NO];
151 [tabBarControl registerForDraggedTypes:
152 [NSArray arrayWithObject:NSFilenamesPboardType]];
154 [tabBarControl setAutoresizingMask:NSViewWidthSizable|NSViewMinYMargin];
156 //[tabBarControl setPartnerView:textView];
158 // tab bar resizing only works if awakeFromNib is called (that's where
159 // the NSViewFrameDidChangeNotification callback is installed). Sounds like
160 // a PSMTabBarControl bug, let's live with it for now.
161 [tabBarControl awakeFromNib];
163 [self addSubview:tabBarControl];
172 [tabBarControl release]; tabBarControl = nil;
173 [tabView release]; tabView = nil;
174 [scrollbars release]; scrollbars = nil;
176 // HACK! The text storage is the principal owner of the text system, but we
177 // keep only a reference to the text view, so release the text storage
178 // first (unless we are using the ATSUI renderer).
179 if ([textView isKindOfClass:[MMTextView class]])
180 [[textView textStorage] release];
182 [textView release]; textView = nil;
192 - (void)drawRect:(NSRect)rect
195 // Clear the entire view
196 [[textView defaultBackgroundColor] set];
197 NSRectFill([self bounds]);
201 NSRect textViewFrame = [textView frame];
202 if (!NSEqualRects(lastTextViewFrame, textViewFrame)) {
203 // If the text view's frame changes we copy the contents of the old
204 // frame to the origin of the new frame. The reason for this is that
205 // Vim expects the contents of its view not to change unless Vim
206 // changes it. (Omitting this code causes the view contents to get
207 // messed up e.g. when the left scrollbar is shown.)
208 NSPoint pt = textViewFrame.origin;
209 CGFloat d = textViewFrame.size.height - lastTextViewFrame.size.height;
210 NSRect r = lastTextViewFrame;
212 // Thew view became larger. Copy the old view to the top of the
215 NSCopyBits(0, r, pt);
217 // Clear the part of the view that has been exposed.
220 [[textView defaultBackgroundColor] set];
224 r.size.width -= lastTextViewFrame.size.width;
225 if (r.size.width > 0) {
226 // The width of the view has grown to the right (i.e. user
227 // clicked maximize button whilst holding Cmd).
228 r.origin.x += lastTextViewFrame.size.width;
232 // The view became smaller.
233 // TODO: Should copy the top of the old view into the new view, but
234 // this does not work since the view has already been resized and
235 // the top of the old view is lost. Could perhaps work to cache
236 // the view to an offscreen surface before it resizes and then draw
238 // As a temporary hack we just clear the view instead.
239 NSRectFill(textViewFrame);
241 // r.size.height = textViewFrame.size.height;
244 lastTextViewFrame = textViewFrame;
247 // On Leopard, we want to have a textured window background for nice
248 // looking tabs. However, the textured window background looks really
249 // weird behind the window resize throbber, so emulate the look of an
250 // NSScrollView in the bottom right corner.
251 if (![[self window] showsResizeIndicator] // XXX: make this a flag
252 || !([[self window] styleMask] & NSTexturedBackgroundWindowMask))
255 int sw = [NSScroller scrollerWidth];
257 // add .5 to the pixel locations to put the lines on a pixel boundary.
258 // the top and right edges of the rect will be outside of the bounds rect
260 NSRect sizerRect = NSMakeRect([self bounds].size.width - sw + .5, -.5,
262 //NSBezierPath* path = [NSBezierPath bezierPath];
263 NSBezierPath* path = [NSBezierPath bezierPathWithRect:sizerRect];
265 // On Tiger, we have color #E8E8E8 behind the resize throbber
266 // (which is windowBackgroundColor on untextured windows or controlColor in
267 // general). Terminal.app on Leopard has #FFFFFF background and #D9D9D9 as
268 // stroke. The colors below are #FFFFFF and #D4D4D4, which is close enough
270 [[NSColor controlBackgroundColor] set];
273 [[NSColor secondarySelectedControlColor] set];
276 if ([self leftScrollbarVisible]) {
277 // If the left scrollbar is visible there is an empty square under it.
278 // Fill it in just like on the right hand corner. The half pixel
279 // offset ensures the outline goes on the top and right side of the
280 // square; the left and bottom parts of the outline are clipped.
281 sizerRect = NSMakeRect(-.5,-.5,sw,sw);
282 path = [NSBezierPath bezierPathWithRect:sizerRect];
283 [[NSColor controlBackgroundColor] set];
285 [[NSColor secondarySelectedControlColor] set];
290 - (MMTextView *)textView
295 - (PSMTabBarControl *)tabBarControl
297 return tabBarControl;
304 // NOTE! There is a bug in PSMTabBarControl in that it retains the delegate
305 // so reset the delegate here, otherwise the delegate may never get
307 [tabView setDelegate:nil];
308 [tabBarControl setDelegate:nil];
309 [tabBarControl setTabView:nil];
310 [[self window] setDelegate:nil];
312 // NOTE! There is another bug in PSMTabBarControl where the control is not
313 // removed as an observer, so remove it here (failing to remove an observer
314 // may lead to very strange bugs).
315 [[NSNotificationCenter defaultCenter] removeObserver:tabBarControl];
317 [tabBarControl removeFromSuperviewWithoutNeedingDisplay];
318 [textView removeFromSuperviewWithoutNeedingDisplay];
320 unsigned i, count = [scrollbars count];
321 for (i = 0; i < count; ++i) {
322 MMScroller *sb = [scrollbars objectAtIndex:i];
323 [sb removeFromSuperviewWithoutNeedingDisplay];
326 [tabView removeAllTabViewItems];
329 - (NSSize)desiredSize
331 return [self vimViewSizeForTextViewSize:[textView desiredSize]];
336 return [self vimViewSizeForTextViewSize:[textView minSize]];
339 - (NSSize)constrainRows:(int *)r columns:(int *)c toSize:(NSSize)size
341 NSSize textViewSize = [self textViewRectForVimViewSize:size].size;
342 textViewSize = [textView constrainRows:r columns:c toSize:textViewSize];
343 return [self vimViewSizeForTextViewSize:textViewSize];
346 - (void)setDesiredRows:(int)r columns:(int)c
348 [textView setMaxRows:r columns:c];
351 - (IBAction)addNewTab:(id)sender
353 [vimController sendMessage:AddNewTabMsgID data:nil];
356 - (void)updateTabsWithData:(NSData *)data
358 const void *p = [data bytes];
359 const void *end = p + [data length];
362 // HACK! Current tab is first in the message. This way it is not
363 // necessary to guess which tab should be the selected one (this can be
364 // problematic for instance when new tabs are created).
365 int curtabIdx = *((int*)p); p += sizeof(int);
367 NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
370 NSTabViewItem *tvi = nil;
372 //int wincount = *((int*)p); p += sizeof(int);
373 int infoCount = *((int*)p); p += sizeof(int);
375 for (i = 0; i < infoCount; ++i) {
376 int length = *((int*)p); p += sizeof(int);
380 NSString *val = [[NSString alloc]
381 initWithBytes:(void*)p length:length
382 encoding:NSUTF8StringEncoding];
387 // Set the label of the tab, adding a new tab when needed.
388 tvi = [[self tabView] numberOfTabViewItems] <= tabIdx
389 ? [self addNewTabViewItem]
390 : [tabViewItems objectAtIndex:tabIdx];
396 [[self tabBarControl] setToolTip:val
400 ASLogWarn(@"Unknown tab info for index: %d", i);
407 // Remove unused tabs from the NSTabView. Note that when a tab is closed
408 // the NSTabView will automatically select another tab, but we want Vim to
409 // take care of which tab to select so set the vimTaskSelectedTab flag to
410 // prevent the tab selection message to be passed on to the VimTask.
411 vimTaskSelectedTab = YES;
412 int i, count = [[self tabView] numberOfTabViewItems];
413 for (i = count-1; i >= tabIdx; --i) {
414 id tvi = [tabViewItems objectAtIndex:i];
415 [[self tabView] removeTabViewItem:tvi];
417 vimTaskSelectedTab = NO;
419 [self selectTabWithIndex:curtabIdx];
422 - (void)selectTabWithIndex:(int)idx
424 NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
425 if (idx < 0 || idx >= [tabViewItems count]) {
426 ASLogWarn(@"No tab with index %d exists.", idx);
430 // Do not try to select a tab if already selected.
431 NSTabViewItem *tvi = [tabViewItems objectAtIndex:idx];
432 if (tvi != [[self tabView] selectedTabViewItem]) {
433 vimTaskSelectedTab = YES;
434 [[self tabView] selectTabViewItem:tvi];
435 vimTaskSelectedTab = NO;
437 // We might need to change the scrollbars that are visible.
438 [self placeScrollbars];
442 - (NSTabViewItem *)addNewTabViewItem
444 // NOTE! A newly created tab is not by selected by default; Vim decides
445 // which tab should be selected at all times. However, the AppKit will
446 // automatically select the first tab added to a tab view.
448 NSTabViewItem *tvi = [[NSTabViewItem alloc] initWithIdentifier:nil];
450 // NOTE: If this is the first tab it will be automatically selected.
451 vimTaskSelectedTab = YES;
452 [[self tabView] addTabViewItem:tvi];
453 vimTaskSelectedTab = NO;
460 - (void)createScrollbarWithIdentifier:(int32_t)ident type:(int)type
462 MMScroller *scroller = [[MMScroller alloc] initWithIdentifier:ident
464 [scroller setTarget:self];
465 [scroller setAction:@selector(scroll:)];
467 [self addSubview:scroller];
468 [scrollbars addObject:scroller];
472 - (BOOL)destroyScrollbarWithIdentifier:(int32_t)ident
475 MMScroller *scroller = [self scrollbarForIdentifier:ident index:&idx];
476 if (!scroller) return NO;
478 [scroller removeFromSuperview];
479 [scrollbars removeObjectAtIndex:idx];
481 // If a visible scroller was removed then the vim view must resize. This
482 // is handled by the window controller (the vim view never resizes itself).
483 return ![scroller isHidden];
486 - (BOOL)showScrollbarWithIdentifier:(int32_t)ident state:(BOOL)visible
488 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
489 if (!scroller) return NO;
491 BOOL wasVisible = ![scroller isHidden];
492 [scroller setHidden:!visible];
494 // If a scroller was hidden or shown then the vim view must resize. This
495 // is handled by the window controller (the vim view never resizes itself).
496 return wasVisible != visible;
499 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
500 identifier:(int32_t)ident
502 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
503 #if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)
504 [scroller setDoubleValue:val];
505 [scroller setKnobProportion:prop];
507 [scroller setFloatValue:val knobProportion:prop];
509 [scroller setEnabled:prop != 1.f];
513 - (void)scroll:(id)sender
515 NSMutableData *data = [NSMutableData data];
516 int32_t ident = [(MMScroller*)sender scrollerId];
517 int hitPart = [sender hitPart];
518 float value = [sender floatValue];
520 [data appendBytes:&ident length:sizeof(int32_t)];
521 [data appendBytes:&hitPart length:sizeof(int)];
522 [data appendBytes:&value length:sizeof(float)];
524 [vimController sendMessage:ScrollbarEventMsgID data:data];
527 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(int32_t)ident
529 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
530 NSRange range = NSMakeRange(pos, len);
531 if (!NSEqualRanges(range, [scroller range])) {
532 [scroller setRange:range];
533 // TODO! Should only do this once per update.
535 // This could be sent because a text window was created or closed, so
536 // we might need to update which scrollbars are visible.
537 [self placeScrollbars];
541 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
543 [textView setDefaultColorsBackground:back foreground:fore];
547 // -- PSMTabBarControl delegate ----------------------------------------------
550 - (BOOL)tabView:(NSTabView *)theTabView shouldSelectTabViewItem:
551 (NSTabViewItem *)tabViewItem
553 // NOTE: It would be reasonable to think that 'shouldSelect...' implies
554 // that this message only gets sent when the user clicks the tab.
555 // Unfortunately it is not so, which is why we need the
556 // 'vimTaskSelectedTab' flag.
558 // HACK! The selection message should not be propagated to Vim if Vim
559 // selected the tab (e.g. as opposed the user clicking the tab). The
560 // delegate method has no way of knowing who initiated the selection so a
561 // flag is set when Vim initiated the selection.
562 if (!vimTaskSelectedTab) {
563 // Propagate the selection message to Vim.
564 NSUInteger idx = [self representedIndexOfTabViewItem:tabViewItem];
565 if (NSNotFound != idx) {
566 int i = (int)idx; // HACK! Never more than MAXINT tabs?!
567 NSData *data = [NSData dataWithBytes:&i length:sizeof(int)];
568 [vimController sendMessage:SelectTabMsgID data:data];
572 // Unless Vim selected the tab, return NO, and let Vim decide if the tab
573 // should get selected or not.
574 return vimTaskSelectedTab;
577 - (BOOL)tabView:(NSTabView *)theTabView shouldCloseTabViewItem:
578 (NSTabViewItem *)tabViewItem
580 // HACK! This method is only called when the user clicks the close button
581 // on the tab. Instead of letting the tab bar close the tab, we return NO
582 // and pass a message on to Vim to let it handle the closing.
583 NSUInteger idx = [self representedIndexOfTabViewItem:tabViewItem];
584 int i = (int)idx; // HACK! Never more than MAXINT tabs?!
585 NSData *data = [NSData dataWithBytes:&i length:sizeof(int)];
586 [vimController sendMessage:CloseTabMsgID data:data];
591 - (void)tabView:(NSTabView *)theTabView didDragTabViewItem:
592 (NSTabViewItem *)tabViewItem toIndex:(int)idx
594 NSMutableData *data = [NSMutableData data];
595 [data appendBytes:&idx length:sizeof(int)];
597 [vimController sendMessage:DraggedTabMsgID data:data];
600 - (NSDragOperation)tabBarControl:(PSMTabBarControl *)theTabBarControl
601 draggingEntered:(id <NSDraggingInfo>)sender
602 forTabAtIndex:(NSUInteger)tabIndex
604 NSPasteboard *pb = [sender draggingPasteboard];
605 return [[pb types] containsObject:NSFilenamesPboardType]
606 ? NSDragOperationCopy
607 : NSDragOperationNone;
610 - (BOOL)tabBarControl:(PSMTabBarControl *)theTabBarControl
611 performDragOperation:(id <NSDraggingInfo>)sender
612 forTabAtIndex:(NSUInteger)tabIndex
614 NSPasteboard *pb = [sender draggingPasteboard];
615 if ([[pb types] containsObject:NSFilenamesPboardType]) {
616 NSArray *filenames = [pb propertyListForType:NSFilenamesPboardType];
617 if ([filenames count] == 0)
619 if (tabIndex != NSNotFound) {
620 // If dropping on a specific tab, only open one file
621 [vimController file:[filenames objectAtIndex:0]
622 draggedToTabAtIndex:tabIndex];
624 // Files were dropped on empty part of tab bar; open them all
625 [vimController filesDraggedToTabBar:filenames];
635 // -- NSView customization ---------------------------------------------------
638 - (void)viewWillStartLiveResize
640 id windowController = [[self window] windowController];
641 [windowController liveResizeWillStart];
643 [super viewWillStartLiveResize];
646 - (void)viewDidEndLiveResize
648 id windowController = [[self window] windowController];
649 [windowController liveResizeDidEnd];
651 [super viewDidEndLiveResize];
654 - (void)setFrameSize:(NSSize)size
656 // NOTE: Instead of only acting when a frame was resized, we do some
657 // updating each time a frame may be resized. (At the moment, if we only
658 // respond to actual frame changes then typing ":set lines=1000" twice in a
659 // row will result in the vim view holding more rows than the can fit
660 // inside the window.)
661 [super setFrameSize:size];
662 [self frameSizeMayHaveChanged];
665 - (void)setFrame:(NSRect)frame
667 // See comment in setFrameSize: above.
668 [super setFrame:frame];
669 [self frameSizeMayHaveChanged];
674 // When the view is marked as dirty, the entire background will be cleared
675 // the next time the view is redrawn.
684 @implementation MMVimView (Private)
686 - (BOOL)bottomScrollbarVisible
688 unsigned i, count = [scrollbars count];
689 for (i = 0; i < count; ++i) {
690 MMScroller *scroller = [scrollbars objectAtIndex:i];
691 if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden])
698 - (BOOL)leftScrollbarVisible
700 unsigned i, count = [scrollbars count];
701 for (i = 0; i < count; ++i) {
702 MMScroller *scroller = [scrollbars objectAtIndex:i];
703 if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden])
710 - (BOOL)rightScrollbarVisible
712 unsigned i, count = [scrollbars count];
713 for (i = 0; i < count; ++i) {
714 MMScroller *scroller = [scrollbars objectAtIndex:i];
715 if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden])
722 - (void)placeScrollbars
724 NSRect textViewFrame = [textView frame];
725 BOOL lsbVisible = [self leftScrollbarVisible];
727 // HACK! Find the lowest left&right vertical scrollbars, as well as the
728 // rightmost horizontal scrollbar. This hack continues further down.
730 // TODO! Can there be no more than one horizontal scrollbar? If so, the
731 // code can be simplified.
732 unsigned lowestLeftSbIdx = (unsigned)-1;
733 unsigned lowestRightSbIdx = (unsigned)-1;
734 unsigned rightmostSbIdx = (unsigned)-1;
735 unsigned rowMaxLeft = 0, rowMaxRight = 0, colMax = 0;
736 unsigned i, count = [scrollbars count];
737 for (i = 0; i < count; ++i) {
738 MMScroller *scroller = [scrollbars objectAtIndex:i];
739 if (![scroller isHidden]) {
740 NSRange range = [scroller range];
741 if ([scroller type] == MMScrollerTypeLeft
742 && range.location >= rowMaxLeft) {
743 rowMaxLeft = range.location;
745 } else if ([scroller type] == MMScrollerTypeRight
746 && range.location >= rowMaxRight) {
747 rowMaxRight = range.location;
748 lowestRightSbIdx = i;
749 } else if ([scroller type] == MMScrollerTypeBottom
750 && range.location >= colMax) {
751 colMax = range.location;
757 // Place the scrollbars.
758 for (i = 0; i < count; ++i) {
759 MMScroller *scroller = [scrollbars objectAtIndex:i];
760 if ([scroller isHidden])
764 if ([scroller type] == MMScrollerTypeBottom) {
765 rect = [textView rectForColumnsInRange:[scroller range]];
766 rect.size.height = [NSScroller scrollerWidth];
768 rect.origin.x += [NSScroller scrollerWidth];
770 // HACK! Make sure the rightmost horizontal scrollbar covers the
771 // text view all the way to the right, otherwise it looks ugly when
772 // the user drags the window to resize.
773 if (i == rightmostSbIdx) {
774 float w = NSMaxX(textViewFrame) - NSMaxX(rect);
776 rect.size.width += w;
779 // Make sure scrollbar rect is bounded by the text view frame.
780 if (rect.origin.x < textViewFrame.origin.x)
781 rect.origin.x = textViewFrame.origin.x;
782 else if (rect.origin.x > NSMaxX(textViewFrame))
783 rect.origin.x = NSMaxX(textViewFrame);
784 if (NSMaxX(rect) > NSMaxX(textViewFrame))
785 rect.size.width -= NSMaxX(rect) - NSMaxX(textViewFrame);
786 if (rect.size.width < 0)
789 rect = [textView rectForRowsInRange:[scroller range]];
790 // Adjust for the fact that text layout is flipped.
791 rect.origin.y = NSMaxY(textViewFrame) - rect.origin.y
793 rect.size.width = [NSScroller scrollerWidth];
794 if ([scroller type] == MMScrollerTypeRight)
795 rect.origin.x = NSMaxX(textViewFrame);
797 // HACK! Make sure the lowest vertical scrollbar covers the text
798 // view all the way to the bottom. This is done because Vim only
799 // makes the scrollbar cover the (vim-)window it is associated with
800 // and this means there is always an empty gap in the scrollbar
801 // region next to the command line.
802 // TODO! Find a nicer way to do this.
803 if (i == lowestLeftSbIdx || i == lowestRightSbIdx) {
804 float h = rect.origin.y + rect.size.height
805 - textViewFrame.origin.y;
806 if (rect.size.height < h) {
807 rect.origin.y = textViewFrame.origin.y;
808 rect.size.height = h;
812 // Vertical scrollers must not cover the resize box in the
813 // bottom-right corner of the window.
814 if ([[self window] showsResizeIndicator] // XXX: make this a flag
815 && rect.origin.y < [NSScroller scrollerWidth]) {
816 rect.size.height -= [NSScroller scrollerWidth] - rect.origin.y;
817 rect.origin.y = [NSScroller scrollerWidth];
820 // Make sure scrollbar rect is bounded by the text view frame.
821 if (rect.origin.y < textViewFrame.origin.y) {
822 rect.size.height -= textViewFrame.origin.y - rect.origin.y;
823 rect.origin.y = textViewFrame.origin.y;
824 } else if (rect.origin.y > NSMaxY(textViewFrame))
825 rect.origin.y = NSMaxY(textViewFrame);
826 if (NSMaxY(rect) > NSMaxY(textViewFrame))
827 rect.size.height -= NSMaxY(rect) - NSMaxY(textViewFrame);
828 if (rect.size.height < 0)
829 rect.size.height = 0;
832 NSRect oldRect = [scroller frame];
833 if (!NSEqualRects(oldRect, rect)) {
834 [scroller setFrame:rect];
835 // Clear behind the old scroller frame, or parts of the old
836 // scroller might still be visible after setFrame:.
837 [[[self window] contentView] setNeedsDisplayInRect:oldRect];
838 [scroller setNeedsDisplay:YES];
843 - (NSUInteger)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
845 NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
846 return [tabViewItems indexOfObject:tvi];
849 - (MMScroller *)scrollbarForIdentifier:(int32_t)ident index:(unsigned *)idx
851 unsigned i, count = [scrollbars count];
852 for (i = 0; i < count; ++i) {
853 MMScroller *scroller = [scrollbars objectAtIndex:i];
854 if ([scroller scrollerId] == ident) {
863 - (NSSize)vimViewSizeForTextViewSize:(NSSize)textViewSize
865 NSSize size = textViewSize;
867 if (![[self tabBarControl] isHidden])
868 size.height += [[self tabBarControl] frame].size.height;
870 if ([self bottomScrollbarVisible])
871 size.height += [NSScroller scrollerWidth];
872 if ([self leftScrollbarVisible])
873 size.width += [NSScroller scrollerWidth];
874 if ([self rightScrollbarVisible])
875 size.width += [NSScroller scrollerWidth];
880 - (NSRect)textViewRectForVimViewSize:(NSSize)contentSize
882 NSRect rect = { {0, 0}, {contentSize.width, contentSize.height} };
884 if (![[self tabBarControl] isHidden])
885 rect.size.height -= [[self tabBarControl] frame].size.height;
887 if ([self bottomScrollbarVisible]) {
888 rect.size.height -= [NSScroller scrollerWidth];
889 rect.origin.y += [NSScroller scrollerWidth];
891 if ([self leftScrollbarVisible]) {
892 rect.size.width -= [NSScroller scrollerWidth];
893 rect.origin.x += [NSScroller scrollerWidth];
895 if ([self rightScrollbarVisible])
896 rect.size.width -= [NSScroller scrollerWidth];
901 - (NSTabView *)tabView
906 - (void)frameSizeMayHaveChanged
908 // NOTE: Whenever a call is made that may have changed the frame size we
909 // take the opportunity to make sure all subviews are in place and that the
910 // (rows,columns) are constrained to lie inside the new frame. We not only
911 // do this when the frame really has changed since it is possible to modify
912 // the number of (rows,columns) without changing the frame size.
914 // Give all superfluous space to the text view. It might be smaller or
915 // larger than it wants to be, but this is needed during live resizing.
916 NSRect textViewRect = [self textViewRectForVimViewSize:[self frame].size];
917 [textView setFrame:textViewRect];
919 [self placeScrollbars];
921 // It is possible that the current number of (rows,columns) is too big or
922 // too small to fit the new frame. If so, notify Vim that the text
923 // dimensions should change, but don't actually change the number of
924 // (rows,columns). These numbers may only change when Vim initiates the
925 // change (as opposed to the user dragging the window resizer, for
928 // Note that the message sent to Vim depends on whether we're in
929 // a live resize or not -- this is necessary to avoid the window jittering
930 // when the user drags to resize.
932 NSSize textViewSize = [textView frame].size;
933 [textView constrainRows:&constrained[0] columns:&constrained[1]
934 toSize:textViewSize];
937 [textView getMaxRows:&rows columns:&cols];
939 if (constrained[0] != rows || constrained[1] != cols) {
940 NSData *data = [NSData dataWithBytes:constrained length:2*sizeof(int)];
941 int msgid = [self inLiveResize] ? LiveResizeMsgID
942 : SetTextDimensionsMsgID;
944 ASLogDebug(@"Notify Vim that text dimensions changed from %dx%d to "
945 "%dx%d (%s)", cols, rows, constrained[1], constrained[0],
946 MessageStrings[msgid]);
948 [vimController sendMessage:msgid data:data];
950 // We only want to set the window title if this resize came from
951 // a live-resize, not (for example) setting 'columns' or 'lines'.
952 if ([self inLiveResize]) {
953 [[self window] setTitle:[NSString stringWithFormat:@"%dx%d",
954 constrained[1], constrained[0]]];
959 @end // MMVimView (Private)
964 @implementation MMScroller
966 - (id)initWithIdentifier:(int32_t)ident type:(int)theType
968 // HACK! NSScroller creates a horizontal scroller if it is init'ed with a
969 // frame whose with exceeds its height; so create a bogus rect and pass it
971 NSRect frame = theType == MMScrollerTypeBottom
972 ? NSMakeRect(0, 0, 1, 0)
973 : NSMakeRect(0, 0, 0, 1);
975 self = [super initWithFrame:frame];
976 if (!self) return nil;
980 [self setHidden:YES];
981 [self setEnabled:YES];
982 [self setAutoresizingMask:NSViewNotSizable];
987 - (int32_t)scrollerId
1002 - (void)setRange:(NSRange)newRange
1007 - (void)scrollWheel:(NSEvent *)event
1009 // HACK! Pass message on to the text view.
1010 NSView *vimView = [self superview];
1011 if ([vimView isKindOfClass:[MMVimView class]])
1012 [[(MMVimView*)vimView textView] scrollWheel:event];
1015 - (void)mouseDown:(NSEvent *)event
1017 // TODO: This is an ugly way of getting the connection to the backend.
1018 NSConnection *connection = nil;
1019 id wc = [[self window] windowController];
1020 if ([wc isKindOfClass:[MMWindowController class]]) {
1021 MMVimController *vc = [(MMWindowController*)wc vimController];
1022 id proxy = [vc backendProxy];
1023 connection = [(NSDistantObject*)proxy connectionForProxy];
1026 // NOTE: The scroller goes into "event tracking mode" when the user clicks
1027 // (and holds) the mouse button. We have to manually add the backend
1028 // connection to this mode while the mouse button is held, else DO messages
1029 // from Vim will not be processed until the mouse button is released.
1030 [connection addRequestMode:NSEventTrackingRunLoopMode];
1031 [super mouseDown:event];
1032 [connection removeRequestMode:NSEventTrackingRunLoopMode];