Prepare for 64 bit
[MacVim.git] / src / MacVim / MMVimView.m
blobc9979ffe4a33e6a1960661216e1a67961ee60eb6
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)scrollerId;
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 - (NSUInteger)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 scrollerId];
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         NSUInteger idx = [self representedIndexOfTabViewItem:tabViewItem];
482         if (NSNotFound != idx) {
483             int i = (int)idx;   // HACK! Never more than MAXINT tabs?!
484             NSData *data = [NSData dataWithBytes:&i length:sizeof(int)];
485             [vimController sendMessage:SelectTabMsgID data:data];
486         }
487     }
489     // Unless Vim selected the tab, return NO, and let Vim decide if the tab
490     // should get selected or not.
491     return vimTaskSelectedTab;
494 - (BOOL)tabView:(NSTabView *)theTabView shouldCloseTabViewItem:
495         (NSTabViewItem *)tabViewItem
497     // HACK!  This method is only called when the user clicks the close button
498     // on the tab.  Instead of letting the tab bar close the tab, we return NO
499     // and pass a message on to Vim to let it handle the closing.
500     NSUInteger idx = [self representedIndexOfTabViewItem:tabViewItem];
501     int i = (int)idx;   // HACK! Never more than MAXINT tabs?!
502     NSData *data = [NSData dataWithBytes:&i length:sizeof(int)];
503     [vimController sendMessage:CloseTabMsgID data:data];
505     return NO;
508 - (void)tabView:(NSTabView *)theTabView didDragTabViewItem:
509         (NSTabViewItem *)tabViewItem toIndex:(int)idx
511     NSMutableData *data = [NSMutableData data];
512     [data appendBytes:&idx length:sizeof(int)];
514     [vimController sendMessage:DraggedTabMsgID data:data];
517 - (NSDragOperation)tabBarControl:(PSMTabBarControl *)theTabBarControl
518         draggingEntered:(id <NSDraggingInfo>)sender
519         forTabAtIndex:(NSUInteger)tabIndex
521     NSPasteboard *pb = [sender draggingPasteboard];
522     return [[pb types] containsObject:NSFilenamesPboardType]
523             ? NSDragOperationCopy
524             : NSDragOperationNone;
527 - (BOOL)tabBarControl:(PSMTabBarControl *)theTabBarControl
528         performDragOperation:(id <NSDraggingInfo>)sender
529         forTabAtIndex:(NSUInteger)tabIndex
531     NSPasteboard *pb = [sender draggingPasteboard];
532     if ([[pb types] containsObject:NSFilenamesPboardType]) {
533         NSArray *filenames = [pb propertyListForType:NSFilenamesPboardType];
534         if ([filenames count] == 0)
535             return NO;
536         if (tabIndex != NSNotFound) {
537             // If dropping on a specific tab, only open one file
538             [vimController file:[filenames objectAtIndex:0]
539                 draggedToTabAtIndex:tabIndex];
540         } else {
541             // Files were dropped on empty part of tab bar; open them all
542             [vimController filesDraggedToTabBar:filenames];
543         }
544         return YES;
545     } else {
546         return NO;
547     }
552 // -- NSView customization ---------------------------------------------------
555 - (void)viewWillStartLiveResize
557     id windowController = [[self window] windowController];
558     [windowController liveResizeWillStart];
560     [super viewWillStartLiveResize];
563 - (void)viewDidEndLiveResize
565     id windowController = [[self window] windowController];
566     [windowController liveResizeDidEnd];
568     [super viewDidEndLiveResize];
571 - (void)setFrameSize:(NSSize)size
573     // NOTE: Instead of only acting when a frame was resized, we do some
574     // updating each time a frame may be resized.  (At the moment, if we only
575     // respond to actual frame changes then typing ":set lines=1000" twice in a
576     // row will result in the vim view holding more rows than the can fit
577     // inside the window.)
578     [super setFrameSize:size];
579     [self frameSizeMayHaveChanged];
582 - (void)setFrame:(NSRect)frame
584     // See comment in setFrameSize: above.
585     [super setFrame:frame];
586     [self frameSizeMayHaveChanged];
589 @end // MMVimView
594 @implementation MMVimView (Private)
596 - (BOOL)bottomScrollbarVisible
598     unsigned i, count = [scrollbars count];
599     for (i = 0; i < count; ++i) {
600         MMScroller *scroller = [scrollbars objectAtIndex:i];
601         if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden])
602             return YES;
603     }
605     return NO;
608 - (BOOL)leftScrollbarVisible
610     unsigned i, count = [scrollbars count];
611     for (i = 0; i < count; ++i) {
612         MMScroller *scroller = [scrollbars objectAtIndex:i];
613         if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden])
614             return YES;
615     }
617     return NO;
620 - (BOOL)rightScrollbarVisible
622     unsigned i, count = [scrollbars count];
623     for (i = 0; i < count; ++i) {
624         MMScroller *scroller = [scrollbars objectAtIndex:i];
625         if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden])
626             return YES;
627     }
629     return NO;
632 - (void)placeScrollbars
634     NSRect textViewFrame = [textView frame];
635     BOOL lsbVisible = [self leftScrollbarVisible];
637     // HACK!  Find the lowest left&right vertical scrollbars, as well as the
638     // rightmost horizontal scrollbar.  This hack continues further down.
639     //
640     // TODO!  Can there be no more than one horizontal scrollbar?  If so, the
641     // code can be simplified.
642     unsigned lowestLeftSbIdx = (unsigned)-1;
643     unsigned lowestRightSbIdx = (unsigned)-1;
644     unsigned rightmostSbIdx = (unsigned)-1;
645     unsigned rowMaxLeft = 0, rowMaxRight = 0, colMax = 0;
646     unsigned i, count = [scrollbars count];
647     for (i = 0; i < count; ++i) {
648         MMScroller *scroller = [scrollbars objectAtIndex:i];
649         if (![scroller isHidden]) {
650             NSRange range = [scroller range];
651             if ([scroller type] == MMScrollerTypeLeft
652                     && range.location >= rowMaxLeft) {
653                 rowMaxLeft = range.location;
654                 lowestLeftSbIdx = i;
655             } else if ([scroller type] == MMScrollerTypeRight
656                     && range.location >= rowMaxRight) {
657                 rowMaxRight = range.location;
658                 lowestRightSbIdx = i;
659             } else if ([scroller type] == MMScrollerTypeBottom
660                     && range.location >= colMax) {
661                 colMax = range.location;
662                 rightmostSbIdx = i;
663             }
664         }
665     }
667     // Place the scrollbars.
668     for (i = 0; i < count; ++i) {
669         MMScroller *scroller = [scrollbars objectAtIndex:i];
670         if ([scroller isHidden])
671             continue;
673         NSRect rect;
674         if ([scroller type] == MMScrollerTypeBottom) {
675             rect = [textView rectForColumnsInRange:[scroller range]];
676             rect.size.height = [NSScroller scrollerWidth];
677             if (lsbVisible)
678                 rect.origin.x += [NSScroller scrollerWidth];
680             // HACK!  Make sure the rightmost horizontal scrollbar covers the
681             // text view all the way to the right, otherwise it looks ugly when
682             // the user drags the window to resize.
683             if (i == rightmostSbIdx) {
684                 float w = NSMaxX(textViewFrame) - NSMaxX(rect);
685                 if (w > 0)
686                     rect.size.width += w;
687             }
689             // Make sure scrollbar rect is bounded by the text view frame.
690             if (rect.origin.x < textViewFrame.origin.x)
691                 rect.origin.x = textViewFrame.origin.x;
692             else if (rect.origin.x > NSMaxX(textViewFrame))
693                 rect.origin.x = NSMaxX(textViewFrame);
694             if (NSMaxX(rect) > NSMaxX(textViewFrame))
695                 rect.size.width -= NSMaxX(rect) - NSMaxX(textViewFrame);
696             if (rect.size.width < 0)
697                 rect.size.width = 0;
698         } else {
699             rect = [textView rectForRowsInRange:[scroller range]];
700             // Adjust for the fact that text layout is flipped.
701             rect.origin.y = NSMaxY(textViewFrame) - rect.origin.y
702                     - rect.size.height;
703             rect.size.width = [NSScroller scrollerWidth];
704             if ([scroller type] == MMScrollerTypeRight)
705                 rect.origin.x = NSMaxX(textViewFrame);
707             // HACK!  Make sure the lowest vertical scrollbar covers the text
708             // view all the way to the bottom.  This is done because Vim only
709             // makes the scrollbar cover the (vim-)window it is associated with
710             // and this means there is always an empty gap in the scrollbar
711             // region next to the command line.
712             // TODO!  Find a nicer way to do this.
713             if (i == lowestLeftSbIdx || i == lowestRightSbIdx) {
714                 float h = rect.origin.y + rect.size.height
715                           - textViewFrame.origin.y;
716                 if (rect.size.height < h) {
717                     rect.origin.y = textViewFrame.origin.y;
718                     rect.size.height = h;
719                 }
720             }
722             // Vertical scrollers must not cover the resize box in the
723             // bottom-right corner of the window.
724             if ([[self window] showsResizeIndicator]  // XXX: make this a flag
725                 && rect.origin.y < [NSScroller scrollerWidth]) {
726                 rect.size.height -= [NSScroller scrollerWidth] - rect.origin.y;
727                 rect.origin.y = [NSScroller scrollerWidth];
728             }
730             // Make sure scrollbar rect is bounded by the text view frame.
731             if (rect.origin.y < textViewFrame.origin.y) {
732                 rect.size.height -= textViewFrame.origin.y - rect.origin.y;
733                 rect.origin.y = textViewFrame.origin.y;
734             } else if (rect.origin.y > NSMaxY(textViewFrame))
735                 rect.origin.y = NSMaxY(textViewFrame);
736             if (NSMaxY(rect) > NSMaxY(textViewFrame))
737                 rect.size.height -= NSMaxY(rect) - NSMaxY(textViewFrame);
738             if (rect.size.height < 0)
739                 rect.size.height = 0;
740         }
742         NSRect oldRect = [scroller frame];
743         if (!NSEqualRects(oldRect, rect)) {
744             [scroller setFrame:rect];
745             // Clear behind the old scroller frame, or parts of the old
746             // scroller might still be visible after setFrame:.
747             [[[self window] contentView] setNeedsDisplayInRect:oldRect];
748             [scroller setNeedsDisplay:YES];
749         }
750     }
753 - (NSUInteger)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
755     NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
756     return [tabViewItems indexOfObject:tvi];
759 - (MMScroller *)scrollbarForIdentifier:(int32_t)ident index:(unsigned *)idx
761     unsigned i, count = [scrollbars count];
762     for (i = 0; i < count; ++i) {
763         MMScroller *scroller = [scrollbars objectAtIndex:i];
764         if ([scroller scrollerId] == ident) {
765             if (idx) *idx = i;
766             return scroller;
767         }
768     }
770     return nil;
773 - (NSSize)vimViewSizeForTextViewSize:(NSSize)textViewSize
775     NSSize size = textViewSize;
777     if (![[self tabBarControl] isHidden])
778         size.height += [[self tabBarControl] frame].size.height;
780     if ([self bottomScrollbarVisible])
781         size.height += [NSScroller scrollerWidth];
782     if ([self leftScrollbarVisible])
783         size.width += [NSScroller scrollerWidth];
784     if ([self rightScrollbarVisible])
785         size.width += [NSScroller scrollerWidth];
787     return size;
790 - (NSRect)textViewRectForVimViewSize:(NSSize)contentSize
792     NSRect rect = { {0, 0}, {contentSize.width, contentSize.height} };
794     if (![[self tabBarControl] isHidden])
795         rect.size.height -= [[self tabBarControl] frame].size.height;
797     if ([self bottomScrollbarVisible]) {
798         rect.size.height -= [NSScroller scrollerWidth];
799         rect.origin.y += [NSScroller scrollerWidth];
800     }
801     if ([self leftScrollbarVisible]) {
802         rect.size.width -= [NSScroller scrollerWidth];
803         rect.origin.x += [NSScroller scrollerWidth];
804     }
805     if ([self rightScrollbarVisible])
806         rect.size.width -= [NSScroller scrollerWidth];
808     return rect;
811 - (NSTabView *)tabView
813     return tabView;
816 - (void)frameSizeMayHaveChanged
818     // NOTE: Whenever a call is made that may have changed the frame size we
819     // take the opportunity to make sure all subviews are in place and that the
820     // (rows,columns) are constrained to lie inside the new frame.  We not only
821     // do this when the frame really has changed since it is possible to modify
822     // the number of (rows,columns) without changing the frame size.
824     // Give all superfluous space to the text view. It might be smaller or
825     // larger than it wants to be, but this is needed during live resizing.
826     NSRect textViewRect = [self textViewRectForVimViewSize:[self frame].size];
827     [textView setFrame:textViewRect];
829     [self placeScrollbars];
831     // It is possible that the current number of (rows,columns) is too big or
832     // too small to fit the new frame.  If so, notify Vim that the text
833     // dimensions should change, but don't actually change the number of
834     // (rows,columns).  These numbers may only change when Vim initiates the
835     // change (as opposed to the user dragging the window resizer, for
836     // example).
837     //
838     // Note that the message sent to Vim depends on whether we're in
839     // a live resize or not -- this is necessary to avoid the window jittering
840     // when the user drags to resize.
841     int constrained[2];
842     NSSize textViewSize = [textView frame].size;
843     [textView constrainRows:&constrained[0] columns:&constrained[1]
844                      toSize:textViewSize];
846     int rows, cols;
847     [textView getMaxRows:&rows columns:&cols];
849     if (constrained[0] != rows || constrained[1] != cols) {
850         NSData *data = [NSData dataWithBytes:constrained length:2*sizeof(int)];
851         int msgid = [self inLiveResize] ? LiveResizeMsgID
852                                         : SetTextDimensionsMsgID;
854         ASLogDebug(@"Notify Vim that text dimensions changed from %dx%d to "
855                    "%dx%d (%s)", cols, rows, constrained[1], constrained[0],
856                    MessageStrings[msgid]);
858         [vimController sendMessage:msgid data:data];
860         // We only want to set the window title if this resize came from
861         // a live-resize, not (for example) setting 'columns' or 'lines'.
862         if ([self inLiveResize]) {
863             [[self window] setTitle:[NSString stringWithFormat:@"%dx%d",
864                     constrained[1], constrained[0]]];
865         }
866     }
869 @end // MMVimView (Private)
874 @implementation MMScroller
876 - (id)initWithIdentifier:(int32_t)ident type:(int)theType
878     // HACK! NSScroller creates a horizontal scroller if it is init'ed with a
879     // frame whose with exceeds its height; so create a bogus rect and pass it
880     // to initWithFrame.
881     NSRect frame = theType == MMScrollerTypeBottom
882             ? NSMakeRect(0, 0, 1, 0)
883             : NSMakeRect(0, 0, 0, 1);
885     self = [super initWithFrame:frame];
886     if (!self) return nil;
888     identifier = ident;
889     type = theType;
890     [self setHidden:YES];
891     [self setEnabled:YES];
892     [self setAutoresizingMask:NSViewNotSizable];
894     return self;
897 - (int32_t)scrollerId
899     return identifier;
902 - (int)type
904     return type;
907 - (NSRange)range
909     return range;
912 - (void)setRange:(NSRange)newRange
914     range = newRange;
917 - (void)scrollWheel:(NSEvent *)event
919     // HACK! Pass message on to the text view.
920     NSView *vimView = [self superview];
921     if ([vimView isKindOfClass:[MMVimView class]])
922         [[(MMVimView*)vimView textView] scrollWheel:event];
925 - (void)mouseDown:(NSEvent *)event
927     // TODO: This is an ugly way of getting the connection to the backend.
928     NSConnection *connection = nil;
929     id wc = [[self window] windowController];
930     if ([wc isKindOfClass:[MMWindowController class]]) {
931         MMVimController *vc = [(MMWindowController*)wc vimController];
932         id proxy = [vc backendProxy];
933         connection = [(NSDistantObject*)proxy connectionForProxy];
934     }
936     // NOTE: The scroller goes into "event tracking mode" when the user clicks
937     // (and holds) the mouse button.  We have to manually add the backend
938     // connection to this mode while the mouse button is held, else DO messages
939     // from Vim will not be processed until the mouse button is released.
940     [connection addRequestMode:NSEventTrackingRunLoopMode];
941     [super mouseDown:event];
942     [connection removeRequestMode:NSEventTrackingRunLoopMode];
945 @end // MMScroller