Don't pass 'long' vars over process boundary
[MacVim.git] / src / MacVim / MMVimView.m
blobdeeab7d865caa97b4267bc2538b1b2d14a74622a
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
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.
9  */
11  * MMVimView
12  *
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.
19  */
21 #import "MMAtsuiTextView.h"
22 #import "MMTextView.h"
23 #import "MMVimController.h"
24 #import "MMVimView.h"
25 #import "MMWindowController.h"
26 #import "Miscellaneous.h"
27 #import <PSMTabBarControl/PSMTabBarControl.h>
31 // Scroller type; these must match SBAR_* in gui.h
32 enum {
33     MMScrollerTypeLeft = 0,
34     MMScrollerTypeRight,
35     MMScrollerTypeBottom
39 // TODO:  Move!
40 @interface MMScroller : NSScroller {
41     int32_t identifier;
42     int type;
43     NSRange range;
45 - (id)initWithIdentifier:(int32_t)ident type:(int)type;
46 - (int32_t)identifier;
47 - (int)type;
48 - (NSRange)range;
49 - (void)setRange:(NSRange)newRange;
50 @end
53 @interface MMVimView (Private)
54 - (BOOL)bottomScrollbarVisible;
55 - (BOOL)leftScrollbarVisible;
56 - (BOOL)rightScrollbarVisible;
57 - (void)placeScrollbars;
58 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi;
59 - (MMScroller *)scrollbarForIdentifier:(int32_t)ident index:(unsigned *)idx;
60 - (NSSize)vimViewSizeForTextViewSize:(NSSize)textViewSize;
61 - (NSRect)textViewRectForVimViewSize:(NSSize)contentSize;
62 - (NSTabView *)tabView;
63 - (void)frameSizeMayHaveChanged;
64 @end
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;
72 @end
76 @implementation MMVimView
78 - (MMVimView *)initWithFrame:(NSRect)frame
79                vimController:(MMVimController *)controller
81     if (![super initWithFrame:frame])
82         return nil;
83     
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 ENABLE_ATSUI
93     if ([ud boolForKey:MMAtsuiRendererKey]) {
94         // Use ATSUI for text rendering.
95         //
96         // HACK! 'textView' has type MMTextView, but MMAtsuiTextView is not
97         // derived from MMTextView.
98         textView = [[MMAtsuiTextView alloc] initWithFrame:frame];
99     } else
100 #endif // ENABLE_ATSUI
101     {
102         // Use Cocoa text system for text rendering.
103         textView = [[MMTextView alloc] initWithFrame:frame];
104     }
106     // Allow control of text view inset via MMTextInset* user defaults.
107     int left = [ud integerForKey:MMTextInsetLeftKey];
108     int top = [ud integerForKey:MMTextInsetTopKey];
109     [textView setTextContainerInset:NSMakeSize(left, top)];
111     [textView setAutoresizingMask:NSViewNotSizable];
112     [self addSubview:textView];
113     
114     // Create the tab view (which is never visible, but the tab bar control
115     // needs it to function).
116     tabView = [[NSTabView alloc] initWithFrame:NSZeroRect];
118     // Create the tab bar control (which is responsible for actually
119     // drawing the tabline and tabs).
120     NSRect tabFrame = { { 0, frame.size.height - 22 },
121                         { frame.size.width, 22 } };
122     tabBarControl = [[PSMTabBarControl alloc] initWithFrame:tabFrame];
124     [tabView setDelegate:tabBarControl];
126     [tabBarControl setTabView:tabView];
127     [tabBarControl setDelegate:self];
128     [tabBarControl setHidden:YES];
130     [tabBarControl setCellMinWidth:[ud integerForKey:MMTabMinWidthKey]];
131     [tabBarControl setCellMaxWidth:[ud integerForKey:MMTabMaxWidthKey]];
132     [tabBarControl setCellOptimumWidth:
133                                      [ud integerForKey:MMTabOptimumWidthKey]];
135     [tabBarControl setShowAddTabButton:[ud boolForKey:MMShowAddTabButtonKey]];
136     [[tabBarControl addTabButton] setTarget:self];
137     [[tabBarControl addTabButton] setAction:@selector(addNewTab:)];
138     [tabBarControl setAllowsDragBetweenWindows:NO];
139     [tabBarControl registerForDraggedTypes:
140                             [NSArray arrayWithObject:NSFilenamesPboardType]];
142     [tabBarControl setAutoresizingMask:NSViewWidthSizable|NSViewMinYMargin];
143     
144     //[tabBarControl setPartnerView:textView];
145     
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];
153     return self;
156 - (void)dealloc
158     ASLogDebug(@"");
160     [tabBarControl release];  tabBarControl = nil;
161     [tabView release];  tabView = nil;
162     [scrollbars release];  scrollbars = nil;
164     // HACK! The text storage is the principal owner of the text system, but we
165     // keep only a reference to the text view, so release the text storage
166     // first (unless we are using the ATSUI renderer).
167     if ([textView isKindOfClass:[MMTextView class]])
168         [[textView textStorage] release];
170     [textView release];  textView = nil;
172     [super dealloc];
175 - (void)drawRect:(NSRect)rect
177     // On Leopard, we want to have a textured window background for nice
178     // looking tabs. However, the textured window background looks really
179     // weird behind the window resize throbber, so emulate the look of an
180     // NSScrollView in the bottom right corner.
181     if (![[self window] showsResizeIndicator]  // XXX: make this a flag
182             || !([[self window] styleMask] & NSTexturedBackgroundWindowMask))
183         return;
185     int sw = [NSScroller scrollerWidth];
187     // add .5 to the pixel locations to put the lines on a pixel boundary.
188     // the top and right edges of the rect will be outside of the bounds rect
189     // and clipped away.
190     NSRect sizerRect = NSMakeRect([self bounds].size.width - sw + .5, -.5,
191             sw, sw);
192     //NSBezierPath* path = [NSBezierPath bezierPath];
193     NSBezierPath* path = [NSBezierPath bezierPathWithRect:sizerRect];
195     // On Tiger, we have color #E8E8E8 behind the resize throbber
196     // (which is windowBackgroundColor on untextured windows or controlColor in
197     // general). Terminal.app on Leopard has #FFFFFF background and #D9D9D9 as
198     // stroke. The colors below are #FFFFFF and #D4D4D4, which is close enough
199     // for me.
200     [[NSColor controlBackgroundColor] set];
201     [path fill];
203     [[NSColor secondarySelectedControlColor] set];
204     [path stroke];
207 - (MMTextView *)textView
209     return textView;
212 - (PSMTabBarControl *)tabBarControl
214     return tabBarControl;
217 - (void)cleanup
219     vimController = nil;
220     
221     // NOTE! There is a bug in PSMTabBarControl in that it retains the delegate
222     // so reset the delegate here, otherwise the delegate may never get
223     // released.
224     [tabView setDelegate:nil];
225     [tabBarControl setDelegate:nil];
226     [tabBarControl setTabView:nil];
227     [[self window] setDelegate:nil];
229     // NOTE! There is another bug in PSMTabBarControl where the control is not
230     // removed as an observer, so remove it here (failing to remove an observer
231     // may lead to very strange bugs).
232     [[NSNotificationCenter defaultCenter] removeObserver:tabBarControl];
234     [tabBarControl removeFromSuperviewWithoutNeedingDisplay];
235     [textView removeFromSuperviewWithoutNeedingDisplay];
237     unsigned i, count = [scrollbars count];
238     for (i = 0; i < count; ++i) {
239         MMScroller *sb = [scrollbars objectAtIndex:i];
240         [sb removeFromSuperviewWithoutNeedingDisplay];
241     }
243     [tabView removeAllTabViewItems];
246 - (NSSize)desiredSize
248     return [self vimViewSizeForTextViewSize:[textView desiredSize]];
251 - (NSSize)minSize
253     return [self vimViewSizeForTextViewSize:[textView minSize]];
256 - (NSSize)constrainRows:(int *)r columns:(int *)c toSize:(NSSize)size
258     NSSize textViewSize = [self textViewRectForVimViewSize:size].size;
259     textViewSize = [textView constrainRows:r columns:c toSize:textViewSize];
260     return [self vimViewSizeForTextViewSize:textViewSize];
263 - (void)setDesiredRows:(int)r columns:(int)c
265     [textView setMaxRows:r columns:c];
268 - (IBAction)addNewTab:(id)sender
270     [vimController sendMessage:AddNewTabMsgID data:nil];
273 - (void)updateTabsWithData:(NSData *)data
275     const void *p = [data bytes];
276     const void *end = p + [data length];
277     int tabIdx = 0;
279     // HACK!  Current tab is first in the message.  This way it is not
280     // necessary to guess which tab should be the selected one (this can be
281     // problematic for instance when new tabs are created).
282     int curtabIdx = *((int*)p);  p += sizeof(int);
284     NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
286     while (p < end) {
287         NSTabViewItem *tvi = nil;
289         //int wincount = *((int*)p);  p += sizeof(int);
290         int infoCount = *((int*)p); p += sizeof(int);
291         unsigned i;
292         for (i = 0; i < infoCount; ++i) {
293             int length = *((int*)p);  p += sizeof(int);
294             if (length <= 0)
295                 continue;
297             NSString *val = [[NSString alloc]
298                     initWithBytes:(void*)p length:length
299                          encoding:NSUTF8StringEncoding];
300             p += length;
302             switch (i) {
303                 case MMTabLabel:
304                     // Set the label of the tab, adding a new tab when needed.
305                     tvi = [[self tabView] numberOfTabViewItems] <= tabIdx
306                             ? [self addNewTabViewItem]
307                             : [tabViewItems objectAtIndex:tabIdx];
308                     [tvi setLabel:val];
309                     ++tabIdx;
310                     break;
311                 case MMTabToolTip:
312                     if (tvi)
313                         [[self tabBarControl] setToolTip:val
314                                           forTabViewItem:tvi];
315                     break;
316                 default:
317                     ASLogWarn(@"Unknown tab info for index: %d", i);
318             }
320             [val release];
321         }
322     }
324     // Remove unused tabs from the NSTabView.  Note that when a tab is closed
325     // the NSTabView will automatically select another tab, but we want Vim to
326     // take care of which tab to select so set the vimTaskSelectedTab flag to
327     // prevent the tab selection message to be passed on to the VimTask.
328     vimTaskSelectedTab = YES;
329     int i, count = [[self tabView] numberOfTabViewItems];
330     for (i = count-1; i >= tabIdx; --i) {
331         id tvi = [tabViewItems objectAtIndex:i];
332         [[self tabView] removeTabViewItem:tvi];
333     }
334     vimTaskSelectedTab = NO;
336     [self selectTabWithIndex:curtabIdx];
339 - (void)selectTabWithIndex:(int)idx
341     NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
342     if (idx < 0 || idx >= [tabViewItems count]) {
343         ASLogWarn(@"No tab with index %d exists.", idx);
344         return;
345     }
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];
356     }
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;
372     [tvi autorelease];
374     return tvi;
377 - (void)createScrollbarWithIdentifier:(int32_t)ident type:(int)type
379     MMScroller *scroller = [[MMScroller alloc] initWithIdentifier:ident
380                                                              type:type];
381     [scroller setTarget:self];
382     [scroller setAction:@selector(scroll:)];
384     [self addSubview:scroller];
385     [scrollbars addObject:scroller];
386     [scroller release];
389 - (BOOL)destroyScrollbarWithIdentifier:(int32_t)ident
391     unsigned idx = 0;
392     MMScroller *scroller = [self scrollbarForIdentifier:ident index:&idx];
393     if (!scroller) return NO;
395     [scroller removeFromSuperview];
396     [scrollbars removeObjectAtIndex:idx];
398     // If a visible scroller was removed then the vim view must resize.  This
399     // is handled by the window controller (the vim view never resizes itself).
400     return ![scroller isHidden];
403 - (BOOL)showScrollbarWithIdentifier:(int32_t)ident state:(BOOL)visible
405     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
406     if (!scroller) return NO;
408     BOOL wasVisible = ![scroller isHidden];
409     [scroller setHidden:!visible];
411     // If a scroller was hidden or shown then the vim view must resize.  This
412     // is handled by the window controller (the vim view never resizes itself).
413     return wasVisible != visible;
416 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
417                     identifier:(int32_t)ident
419     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
420 #if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)
421     [scroller setDoubleValue:val];
422     [scroller setKnobProportion:prop];
423 #else
424     [scroller setFloatValue:val knobProportion:prop];
425 #endif
426     [scroller setEnabled:prop != 1.f];
430 - (void)scroll:(id)sender
432     NSMutableData *data = [NSMutableData data];
433     int32_t ident = [(MMScroller*)sender identifier];
434     int hitPart = [sender hitPart];
435     float value = [sender floatValue];
437     [data appendBytes:&ident length:sizeof(int32_t)];
438     [data appendBytes:&hitPart length:sizeof(int)];
439     [data appendBytes:&value length:sizeof(float)];
441     [vimController sendMessage:ScrollbarEventMsgID data:data];
444 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(int32_t)ident
446     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
447     NSRange range = NSMakeRange(pos, len);
448     if (!NSEqualRanges(range, [scroller range])) {
449         [scroller setRange:range];
450         // TODO!  Should only do this once per update.
452         // This could be sent because a text window was created or closed, so
453         // we might need to update which scrollbars are visible.
454         [self placeScrollbars];
455     }
458 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
460     [textView setDefaultColorsBackground:back foreground:fore];
464 // -- PSMTabBarControl delegate ----------------------------------------------
467 - (BOOL)tabView:(NSTabView *)theTabView shouldSelectTabViewItem:
468     (NSTabViewItem *)tabViewItem
470     // NOTE: It would be reasonable to think that 'shouldSelect...' implies
471     // that this message only gets sent when the user clicks the tab.
472     // Unfortunately it is not so, which is why we need the
473     // 'vimTaskSelectedTab' flag.
474     //
475     // HACK!  The selection message should not be propagated to Vim if Vim
476     // selected the tab (e.g. as opposed the user clicking the tab).  The
477     // delegate method has no way of knowing who initiated the selection so a
478     // flag is set when Vim initiated the selection.
479     if (!vimTaskSelectedTab) {
480         // Propagate the selection message to Vim.
481         int idx = [self representedIndexOfTabViewItem:tabViewItem];
482         if (NSNotFound != idx) {
483             NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
484             [vimController sendMessage:SelectTabMsgID data:data];
485         }
486     }
488     // Unless Vim selected the tab, return NO, and let Vim decide if the tab
489     // should get selected or not.
490     return vimTaskSelectedTab;
493 - (BOOL)tabView:(NSTabView *)theTabView shouldCloseTabViewItem:
494         (NSTabViewItem *)tabViewItem
496     // HACK!  This method is only called when the user clicks the close button
497     // on the tab.  Instead of letting the tab bar close the tab, we return NO
498     // and pass a message on to Vim to let it handle the closing.
499     int idx = [self representedIndexOfTabViewItem:tabViewItem];
500     NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
501     [vimController sendMessage:CloseTabMsgID data:data];
503     return NO;
506 - (void)tabView:(NSTabView *)theTabView didDragTabViewItem:
507         (NSTabViewItem *)tabViewItem toIndex:(int)idx
509     NSMutableData *data = [NSMutableData data];
510     [data appendBytes:&idx length:sizeof(int)];
512     [vimController sendMessage:DraggedTabMsgID data:data];
515 - (NSDragOperation)tabBarControl:(PSMTabBarControl *)theTabBarControl
516         draggingEntered:(id <NSDraggingInfo>)sender
517         forTabAtIndex:(unsigned)tabIndex
519     NSPasteboard *pb = [sender draggingPasteboard];
520     return [[pb types] containsObject:NSFilenamesPboardType]
521             ? NSDragOperationCopy
522             : NSDragOperationNone;
525 - (BOOL)tabBarControl:(PSMTabBarControl *)theTabBarControl
526         performDragOperation:(id <NSDraggingInfo>)sender
527         forTabAtIndex:(unsigned)tabIndex
529     NSPasteboard *pb = [sender draggingPasteboard];
530     if ([[pb types] containsObject:NSFilenamesPboardType]) {
531         NSArray *filenames = [pb propertyListForType:NSFilenamesPboardType];
532         if ([filenames count] == 0)
533             return NO;
534         if (tabIndex != NSNotFound) {
535             // If dropping on a specific tab, only open one file
536             [vimController file:[filenames objectAtIndex:0]
537                 draggedToTabAtIndex:tabIndex];
538         } else {
539             // Files were dropped on empty part of tab bar; open them all
540             [vimController filesDraggedToTabBar:filenames];
541         }
542         return YES;
543     } else {
544         return NO;
545     }
550 // -- NSView customization ---------------------------------------------------
553 - (void)viewWillStartLiveResize
555     id windowController = [[self window] windowController];
556     [windowController liveResizeWillStart];
558     [super viewWillStartLiveResize];
561 - (void)viewDidEndLiveResize
563     id windowController = [[self window] windowController];
564     [windowController liveResizeDidEnd];
566     [super viewDidEndLiveResize];
569 - (void)setFrameSize:(NSSize)size
571     // NOTE: Instead of only acting when a frame was resized, we do some
572     // updating each time a frame may be resized.  (At the moment, if we only
573     // respond to actual frame changes then typing ":set lines=1000" twice in a
574     // row will result in the vim view holding more rows than the can fit
575     // inside the window.)
576     [super setFrameSize:size];
577     [self frameSizeMayHaveChanged];
580 - (void)setFrame:(NSRect)frame
582     // See comment in setFrameSize: above.
583     [super setFrame:frame];
584     [self frameSizeMayHaveChanged];
587 @end // MMVimView
592 @implementation MMVimView (Private)
594 - (BOOL)bottomScrollbarVisible
596     unsigned i, count = [scrollbars count];
597     for (i = 0; i < count; ++i) {
598         MMScroller *scroller = [scrollbars objectAtIndex:i];
599         if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden])
600             return YES;
601     }
603     return NO;
606 - (BOOL)leftScrollbarVisible
608     unsigned i, count = [scrollbars count];
609     for (i = 0; i < count; ++i) {
610         MMScroller *scroller = [scrollbars objectAtIndex:i];
611         if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden])
612             return YES;
613     }
615     return NO;
618 - (BOOL)rightScrollbarVisible
620     unsigned i, count = [scrollbars count];
621     for (i = 0; i < count; ++i) {
622         MMScroller *scroller = [scrollbars objectAtIndex:i];
623         if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden])
624             return YES;
625     }
627     return NO;
630 - (void)placeScrollbars
632     NSRect textViewFrame = [textView frame];
633     BOOL lsbVisible = [self leftScrollbarVisible];
635     // HACK!  Find the lowest left&right vertical scrollbars, as well as the
636     // rightmost horizontal scrollbar.  This hack continues further down.
637     //
638     // TODO!  Can there be no more than one horizontal scrollbar?  If so, the
639     // code can be simplified.
640     unsigned lowestLeftSbIdx = (unsigned)-1;
641     unsigned lowestRightSbIdx = (unsigned)-1;
642     unsigned rightmostSbIdx = (unsigned)-1;
643     unsigned rowMaxLeft = 0, rowMaxRight = 0, colMax = 0;
644     unsigned i, count = [scrollbars count];
645     for (i = 0; i < count; ++i) {
646         MMScroller *scroller = [scrollbars objectAtIndex:i];
647         if (![scroller isHidden]) {
648             NSRange range = [scroller range];
649             if ([scroller type] == MMScrollerTypeLeft
650                     && range.location >= rowMaxLeft) {
651                 rowMaxLeft = range.location;
652                 lowestLeftSbIdx = i;
653             } else if ([scroller type] == MMScrollerTypeRight
654                     && range.location >= rowMaxRight) {
655                 rowMaxRight = range.location;
656                 lowestRightSbIdx = i;
657             } else if ([scroller type] == MMScrollerTypeBottom
658                     && range.location >= colMax) {
659                 colMax = range.location;
660                 rightmostSbIdx = i;
661             }
662         }
663     }
665     // Place the scrollbars.
666     for (i = 0; i < count; ++i) {
667         MMScroller *scroller = [scrollbars objectAtIndex:i];
668         if ([scroller isHidden])
669             continue;
671         NSRect rect;
672         if ([scroller type] == MMScrollerTypeBottom) {
673             rect = [textView rectForColumnsInRange:[scroller range]];
674             rect.size.height = [NSScroller scrollerWidth];
675             if (lsbVisible)
676                 rect.origin.x += [NSScroller scrollerWidth];
678             // HACK!  Make sure the rightmost horizontal scrollbar covers the
679             // text view all the way to the right, otherwise it looks ugly when
680             // the user drags the window to resize.
681             if (i == rightmostSbIdx) {
682                 float w = NSMaxX(textViewFrame) - NSMaxX(rect);
683                 if (w > 0)
684                     rect.size.width += w;
685             }
687             // Make sure scrollbar rect is bounded by the text view frame.
688             if (rect.origin.x < textViewFrame.origin.x)
689                 rect.origin.x = textViewFrame.origin.x;
690             else if (rect.origin.x > NSMaxX(textViewFrame))
691                 rect.origin.x = NSMaxX(textViewFrame);
692             if (NSMaxX(rect) > NSMaxX(textViewFrame))
693                 rect.size.width -= NSMaxX(rect) - NSMaxX(textViewFrame);
694             if (rect.size.width < 0)
695                 rect.size.width = 0;
696         } else {
697             rect = [textView rectForRowsInRange:[scroller range]];
698             // Adjust for the fact that text layout is flipped.
699             rect.origin.y = NSMaxY(textViewFrame) - rect.origin.y
700                     - rect.size.height;
701             rect.size.width = [NSScroller scrollerWidth];
702             if ([scroller type] == MMScrollerTypeRight)
703                 rect.origin.x = NSMaxX(textViewFrame);
705             // HACK!  Make sure the lowest vertical scrollbar covers the text
706             // view all the way to the bottom.  This is done because Vim only
707             // makes the scrollbar cover the (vim-)window it is associated with
708             // and this means there is always an empty gap in the scrollbar
709             // region next to the command line.
710             // TODO!  Find a nicer way to do this.
711             if (i == lowestLeftSbIdx || i == lowestRightSbIdx) {
712                 float h = rect.origin.y + rect.size.height
713                           - textViewFrame.origin.y;
714                 if (rect.size.height < h) {
715                     rect.origin.y = textViewFrame.origin.y;
716                     rect.size.height = h;
717                 }
718             }
720             // Vertical scrollers must not cover the resize box in the
721             // bottom-right corner of the window.
722             if ([[self window] showsResizeIndicator]  // XXX: make this a flag
723                 && rect.origin.y < [NSScroller scrollerWidth]) {
724                 rect.size.height -= [NSScroller scrollerWidth] - rect.origin.y;
725                 rect.origin.y = [NSScroller scrollerWidth];
726             }
728             // Make sure scrollbar rect is bounded by the text view frame.
729             if (rect.origin.y < textViewFrame.origin.y) {
730                 rect.size.height -= textViewFrame.origin.y - rect.origin.y;
731                 rect.origin.y = textViewFrame.origin.y;
732             } else if (rect.origin.y > NSMaxY(textViewFrame))
733                 rect.origin.y = NSMaxY(textViewFrame);
734             if (NSMaxY(rect) > NSMaxY(textViewFrame))
735                 rect.size.height -= NSMaxY(rect) - NSMaxY(textViewFrame);
736             if (rect.size.height < 0)
737                 rect.size.height = 0;
738         }
740         NSRect oldRect = [scroller frame];
741         if (!NSEqualRects(oldRect, rect)) {
742             [scroller setFrame:rect];
743             // Clear behind the old scroller frame, or parts of the old
744             // scroller might still be visible after setFrame:.
745             [[[self window] contentView] setNeedsDisplayInRect:oldRect];
746             [scroller setNeedsDisplay:YES];
747         }
748     }
751 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
753     NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
754     return [tabViewItems indexOfObject:tvi];
757 - (MMScroller *)scrollbarForIdentifier:(int32_t)ident index:(unsigned *)idx
759     unsigned i, count = [scrollbars count];
760     for (i = 0; i < count; ++i) {
761         MMScroller *scroller = [scrollbars objectAtIndex:i];
762         if ([scroller identifier] == ident) {
763             if (idx) *idx = i;
764             return scroller;
765         }
766     }
768     return nil;
771 - (NSSize)vimViewSizeForTextViewSize:(NSSize)textViewSize
773     NSSize size = textViewSize;
775     if (![[self tabBarControl] isHidden])
776         size.height += [[self tabBarControl] frame].size.height;
778     if ([self bottomScrollbarVisible])
779         size.height += [NSScroller scrollerWidth];
780     if ([self leftScrollbarVisible])
781         size.width += [NSScroller scrollerWidth];
782     if ([self rightScrollbarVisible])
783         size.width += [NSScroller scrollerWidth];
785     return size;
788 - (NSRect)textViewRectForVimViewSize:(NSSize)contentSize
790     NSRect rect = { 0, 0, contentSize.width, contentSize.height };
792     if (![[self tabBarControl] isHidden])
793         rect.size.height -= [[self tabBarControl] frame].size.height;
795     if ([self bottomScrollbarVisible]) {
796         rect.size.height -= [NSScroller scrollerWidth];
797         rect.origin.y += [NSScroller scrollerWidth];
798     }
799     if ([self leftScrollbarVisible]) {
800         rect.size.width -= [NSScroller scrollerWidth];
801         rect.origin.x += [NSScroller scrollerWidth];
802     }
803     if ([self rightScrollbarVisible])
804         rect.size.width -= [NSScroller scrollerWidth];
806     return rect;
809 - (NSTabView *)tabView
811     return tabView;
814 - (void)frameSizeMayHaveChanged
816     // NOTE: Whenever a call is made that may have changed the frame size we
817     // take the opportunity to make sure all subviews are in place and that the
818     // (rows,columns) are constrained to lie inside the new frame.  We not only
819     // do this when the frame really has changed since it is possible to modify
820     // the number of (rows,columns) without changing the frame size.
822     // Give all superfluous space to the text view. It might be smaller or
823     // larger than it wants to be, but this is needed during live resizing.
824     NSRect textViewRect = [self textViewRectForVimViewSize:[self frame].size];
825     [textView setFrame:textViewRect];
827     [self placeScrollbars];
829     // It is possible that the current number of (rows,columns) is too big or
830     // too small to fit the new frame.  If so, notify Vim that the text
831     // dimensions should change, but don't actually change the number of
832     // (rows,columns).  These numbers may only change when Vim initiates the
833     // change (as opposed to the user dragging the window resizer, for
834     // example).
835     //
836     // Note that the message sent to Vim depends on whether we're in
837     // a live resize or not -- this is necessary to avoid the window jittering
838     // when the user drags to resize.
839     int constrained[2];
840     NSSize textViewSize = [textView frame].size;
841     [textView constrainRows:&constrained[0] columns:&constrained[1]
842                      toSize:textViewSize];
844     int rows, cols;
845     [textView getMaxRows:&rows columns:&cols];
847     if (constrained[0] != rows || constrained[1] != cols) {
848         NSData *data = [NSData dataWithBytes:constrained length:2*sizeof(int)];
849         int msgid = [self inLiveResize] ? LiveResizeMsgID
850                                         : SetTextDimensionsMsgID;
852         ASLogDebug(@"Notify Vim that text dimensions changed from %dx%d to "
853                    "%dx%d (%s)", cols, rows, constrained[1], constrained[0],
854                    MessageStrings[msgid]);
856         [vimController sendMessage:msgid data:data];
858         // We only want to set the window title if this resize came from
859         // a live-resize, not (for example) setting 'columns' or 'lines'.
860         if ([self inLiveResize]) {
861             [[self window] setTitle:[NSString stringWithFormat:@"%dx%d",
862                     constrained[1], constrained[0]]];
863         }
864     }
867 @end // MMVimView (Private)
872 @implementation MMScroller
874 - (id)initWithIdentifier:(int32_t)ident type:(int)theType
876     // HACK! NSScroller creates a horizontal scroller if it is init'ed with a
877     // frame whose with exceeds its height; so create a bogus rect and pass it
878     // to initWithFrame.
879     NSRect frame = theType == MMScrollerTypeBottom
880             ? NSMakeRect(0, 0, 1, 0)
881             : NSMakeRect(0, 0, 0, 1);
883     self = [super initWithFrame:frame];
884     if (!self) return nil;
886     identifier = ident;
887     type = theType;
888     [self setHidden:YES];
889     [self setEnabled:YES];
890     [self setAutoresizingMask:NSViewNotSizable];
892     return self;
895 - (int32_t)identifier
897     return identifier;
900 - (int)type
902     return type;
905 - (NSRange)range
907     return range;
910 - (void)setRange:(NSRange)newRange
912     range = newRange;
915 - (void)scrollWheel:(NSEvent *)event
917     // HACK! Pass message on to the text view.
918     NSView *vimView = [self superview];
919     if ([vimView isKindOfClass:[MMVimView class]])
920         [[(MMVimView*)vimView textView] scrollWheel:event];
923 - (void)mouseDown:(NSEvent *)event
925     // TODO: This is an ugly way of getting the connection to the backend.
926     NSConnection *connection = nil;
927     id wc = [[self window] windowController];
928     if ([wc isKindOfClass:[MMWindowController class]]) {
929         MMVimController *vc = [(MMWindowController*)wc vimController];
930         id proxy = [vc backendProxy];
931         connection = [(NSDistantObject*)proxy connectionForProxy];
932     }
934     // NOTE: The scroller goes into "event tracking mode" when the user clicks
935     // (and holds) the mouse button.  We have to manually add the backend
936     // connection to this mode while the mouse button is held, else DO messages
937     // from Vim will not be processed until the mouse button is released.
938     [connection addRequestMode:NSEventTrackingRunLoopMode];
939     [super mouseDown:event];
940     [connection removeRequestMode:NSEventTrackingRunLoopMode];
943 @end // MMScroller