Add help on how to remap Caps Lock to Esc
[MacVim.git] / src / MacVim / MMVimView.m
blob0ebc075a1a15f0caf66fb5a6f3c2084b419f7d1f
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 "Miscellaneous.h"
26 #import <PSMTabBarControl.h>
30 // Scroller type; these must match SBAR_* in gui.h
31 enum {
32     MMScrollerTypeLeft = 0,
33     MMScrollerTypeRight,
34     MMScrollerTypeBottom
38 // TODO:  Move!
39 @interface MMScroller : NSScroller {
40     long identifier;
41     int type;
42     NSRange range;
44 - (id)initWithIdentifier:(long)ident type:(int)type;
45 - (long)identifier;
46 - (int)type;
47 - (NSRange)range;
48 - (void)setRange:(NSRange)newRange;
49 @end
52 @interface MMVimView (Private)
53 - (BOOL)bottomScrollbarVisible;
54 - (BOOL)leftScrollbarVisible;
55 - (BOOL)rightScrollbarVisible;
56 - (void)placeScrollbars;
57 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi;
58 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx;
59 - (NSSize)vimViewSizeForTextViewSize:(NSSize)textViewSize;
60 - (NSRect)textViewRectForVimViewSize:(NSSize)contentSize;
61 - (NSTabView *)tabView;
62 - (void)frameSizeMayHaveChanged;
63 @end
66 // This is an informal protocol implemented by MMWindowController (maybe it
67 // shold be a formal protocol, but ...).
68 @interface NSWindowController (MMVimViewDelegate)
69 - (void)liveResizeWillStart;
70 - (void)liveResizeDidEnd;
71 @end
75 @implementation MMVimView
77 - (MMVimView *)initWithFrame:(NSRect)frame
78                vimController:(MMVimController *)controller
80     if (![super initWithFrame:frame])
81         return nil;
82     
83     vimController = controller;
84     scrollbars = [[NSMutableArray alloc] init];
86     // Only the tabline is autoresized, all other subview placement is done in
87     // frameSizeMayHaveChanged.
88     [self setAutoresizesSubviews:YES];
90     if ([[NSUserDefaults standardUserDefaults] boolForKey:MMAtsuiRendererKey]) {
91         // Use ATSUI for text rendering.
92         //
93         // HACK! 'textView' has type MMTextView, but MMAtsuiTextView is not
94         // derived from MMTextView.
95         textView = [[MMAtsuiTextView alloc] initWithFrame:frame];
96     } else {
97         // Use Cocoa text system for text rendering.
98         textView = [[MMTextView alloc] initWithFrame:frame];
99     }
101     // Allow control of text view inset via MMTextInset* user defaults.
102     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
103     int left = [ud integerForKey:MMTextInsetLeftKey];
104     int top = [ud integerForKey:MMTextInsetTopKey];
105     [textView setTextContainerInset:NSMakeSize(left, top)];
107     [textView setAutoresizingMask:NSViewNotSizable];
108     [self addSubview:textView];
109     
110     // Create the tab view (which is never visible, but the tab bar control
111     // needs it to function).
112     tabView = [[NSTabView alloc] initWithFrame:NSZeroRect];
114     // Create the tab bar control (which is responsible for actually
115     // drawing the tabline and tabs).
116     NSRect tabFrame = { { 0, frame.size.height - 22 },
117                         { frame.size.width, 22 } };
118     tabBarControl = [[PSMTabBarControl alloc] initWithFrame:tabFrame];
120     [tabView setDelegate:tabBarControl];
122     [tabBarControl setTabView:tabView];
123     [tabBarControl setDelegate:self];
124     [tabBarControl setHidden:YES];
126     [tabBarControl setCellMinWidth:[ud integerForKey:MMTabMinWidthKey]];
127     [tabBarControl setCellMaxWidth:[ud integerForKey:MMTabMaxWidthKey]];
128     [tabBarControl setCellOptimumWidth:
129                                      [ud integerForKey:MMTabOptimumWidthKey]];
131     [tabBarControl setShowAddTabButton:YES];
132     [[tabBarControl addTabButton] setTarget:self];
133     [[tabBarControl addTabButton] setAction:@selector(addNewTab:)];
134     [tabBarControl setAllowsDragBetweenWindows:NO];
135     [tabBarControl registerForDraggedTypes:
136                             [NSArray arrayWithObject:NSFilenamesPboardType]];
138     [tabBarControl setAutoresizingMask:NSViewWidthSizable|NSViewMinYMargin];
139     
140     //[tabBarControl setPartnerView:textView];
141     
142     // tab bar resizing only works if awakeFromNib is called (that's where
143     // the NSViewFrameDidChangeNotification callback is installed). Sounds like
144     // a PSMTabBarControl bug, let's live with it for now.
145     [tabBarControl awakeFromNib];
147     [self addSubview:tabBarControl];
149     return self;
152 - (void)dealloc
154     LOG_DEALLOC
156     [tabBarControl release];  tabBarControl = nil;
157     [tabView release];  tabView = nil;
158     [scrollbars release];  scrollbars = nil;
160     // HACK! The text storage is the principal owner of the text system, but we
161     // keep only a reference to the text view, so release the text storage
162     // first (unless we are using the ATSUI renderer).
163     if ([textView isKindOfClass:[MMTextView class]])
164         [[textView textStorage] release];
166     [textView release];  textView = nil;
168     [super dealloc];
171 - (void)drawRect:(NSRect)rect
173     // On Leopard, we want to have a textured window background for nice
174     // looking tabs. However, the textured window background looks really
175     // weird behind the window resize throbber, so emulate the look of an
176     // NSScrollView in the bottom right corner.
177     if (![[self window] showsResizeIndicator]  // XXX: make this a flag
178             || !([[self window] styleMask] & NSTexturedBackgroundWindowMask))
179         return;
181     int sw = [NSScroller scrollerWidth];
183     // add .5 to the pixel locations to put the lines on a pixel boundary.
184     // the top and right edges of the rect will be outside of the bounds rect
185     // and clipped away.
186     NSRect sizerRect = NSMakeRect([self bounds].size.width - sw + .5, -.5,
187             sw, sw);
188     //NSBezierPath* path = [NSBezierPath bezierPath];
189     NSBezierPath* path = [NSBezierPath bezierPathWithRect:sizerRect];
191     // On Tiger, we have color #E8E8E8 behind the resize throbber
192     // (which is windowBackgroundColor on untextured windows or controlColor in
193     // general). Terminal.app on Leopard has #FFFFFF background and #D9D9D9 as
194     // stroke. The colors below are #FFFFFF and #D4D4D4, which is close enough
195     // for me.
196     [[NSColor controlBackgroundColor] set];
197     [path fill];
199     [[NSColor secondarySelectedControlColor] set];
200     [path stroke];
203 - (MMTextView *)textView
205     return textView;
208 - (PSMTabBarControl *)tabBarControl
210     return tabBarControl;
213 - (void)cleanup
215     vimController = nil;
216     
217     // NOTE! There is a bug in PSMTabBarControl in that it retains the delegate
218     // so reset the delegate here, otherwise the delegate may never get
219     // released.
220     [tabView setDelegate:nil];
221     [tabBarControl setDelegate:nil];
222     [tabBarControl setTabView:nil];
223     [[self window] setDelegate:nil];
225     // NOTE! There is another bug in PSMTabBarControl where the control is not
226     // removed as an observer, so remove it here (failing to remove an observer
227     // may lead to very strange bugs).
228     [[NSNotificationCenter defaultCenter] removeObserver:tabBarControl];
230     [tabBarControl removeFromSuperviewWithoutNeedingDisplay];
231     [textView removeFromSuperviewWithoutNeedingDisplay];
233     unsigned i, count = [scrollbars count];
234     for (i = 0; i < count; ++i) {
235         MMScroller *sb = [scrollbars objectAtIndex:i];
236         [sb removeFromSuperviewWithoutNeedingDisplay];
237     }
239     [tabView removeAllTabViewItems];
242 - (NSSize)desiredSize
244     return [self vimViewSizeForTextViewSize:[textView desiredSize]];
247 - (NSSize)minSize
249     return [self vimViewSizeForTextViewSize:[textView minSize]];
252 - (NSSize)constrainRows:(int *)r columns:(int *)c toSize:(NSSize)size
254     NSSize textViewSize = [self textViewRectForVimViewSize:size].size;
255     textViewSize = [textView constrainRows:r columns:c toSize:textViewSize];
256     return [self vimViewSizeForTextViewSize:textViewSize];
259 - (void)setDesiredRows:(int)r columns:(int)c
261     [textView setMaxRows:r columns:c];
264 - (IBAction)addNewTab:(id)sender
266     [vimController sendMessage:AddNewTabMsgID data:nil];
269 - (void)updateTabsWithData:(NSData *)data
271     const void *p = [data bytes];
272     const void *end = p + [data length];
273     int tabIdx = 0;
275     // HACK!  Current tab is first in the message.  This way it is not
276     // necessary to guess which tab should be the selected one (this can be
277     // problematic for instance when new tabs are created).
278     int curtabIdx = *((int*)p);  p += sizeof(int);
280     NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
282     while (p < end) {
283         //int wincount = *((int*)p);  p += sizeof(int);
284         int length = *((int*)p);  p += sizeof(int);
286         NSString *label = [[NSString alloc]
287                 initWithBytes:(void*)p length:length
288                      encoding:NSUTF8StringEncoding];
289         p += length;
291         // Set the label of the tab;  add a new tab when needed.
292         NSTabViewItem *tvi = [[self tabView] numberOfTabViewItems] <= tabIdx
293                 ? [self addNewTabViewItem]
294                 : [tabViewItems objectAtIndex:tabIdx];
296         [tvi setLabel:label];
298         [label release];
300         ++tabIdx;
301     }
303     // Remove unused tabs from the NSTabView.  Note that when a tab is closed
304     // the NSTabView will automatically select another tab, but we want Vim to
305     // take care of which tab to select so set the vimTaskSelectedTab flag to
306     // prevent the tab selection message to be passed on to the VimTask.
307     vimTaskSelectedTab = YES;
308     int i, count = [[self tabView] numberOfTabViewItems];
309     for (i = count-1; i >= tabIdx; --i) {
310         id tvi = [tabViewItems objectAtIndex:i];
311         //NSLog(@"Removing tab with index %d", i);
312         [[self tabView] removeTabViewItem:tvi];
313     }
314     vimTaskSelectedTab = NO;
316     [self selectTabWithIndex:curtabIdx];
319 - (void)selectTabWithIndex:(int)idx
321     //NSLog(@"%s%d", _cmd, idx);
323     NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
324     if (idx < 0 || idx >= [tabViewItems count]) {
325         NSLog(@"WARNING: No tab with index %d exists.", idx);
326         return;
327     }
329     // Do not try to select a tab if already selected.
330     NSTabViewItem *tvi = [tabViewItems objectAtIndex:idx];
331     if (tvi != [[self tabView] selectedTabViewItem]) {
332         vimTaskSelectedTab = YES;
333         [[self tabView] selectTabViewItem:tvi];
334         vimTaskSelectedTab = NO;
336         // We might need to change the scrollbars that are visible.
337         [self placeScrollbars];
338     }
341 - (NSTabViewItem *)addNewTabViewItem
343     // NOTE!  A newly created tab is not by selected by default; Vim decides
344     // which tab should be selected at all times.  However, the AppKit will
345     // automatically select the first tab added to a tab view.
347     NSTabViewItem *tvi = [[NSTabViewItem alloc] initWithIdentifier:nil];
349     // NOTE: If this is the first tab it will be automatically selected.
350     vimTaskSelectedTab = YES;
351     [[self tabView] addTabViewItem:tvi];
352     vimTaskSelectedTab = NO;
354     [tvi autorelease];
356     return tvi;
359 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
361     //NSLog(@"Create scroller %d of type %d", ident, type);
363     MMScroller *scroller = [[MMScroller alloc] initWithIdentifier:ident
364                                                              type:type];
365     [scroller setTarget:self];
366     [scroller setAction:@selector(scroll:)];
368     [self addSubview:scroller];
369     [scrollbars addObject:scroller];
370     [scroller release];
373 - (BOOL)destroyScrollbarWithIdentifier:(long)ident
375     //NSLog(@"Destroy scroller %d", ident);
377     unsigned idx = 0;
378     MMScroller *scroller = [self scrollbarForIdentifier:ident index:&idx];
379     if (!scroller) return NO;
381     [scroller removeFromSuperview];
382     [scrollbars removeObjectAtIndex:idx];
384     // If a visible scroller was removed then the vim view must resize.  This
385     // is handled by the window controller (the vim view never resizes itself).
386     return ![scroller isHidden];
389 - (BOOL)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
391     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
392     if (!scroller) return NO;
394     BOOL wasVisible = ![scroller isHidden];
395     //NSLog(@"%s scroller %d (was %svisible)", visible ? "Show" : "Hide",
396     //      ident, wasVisible ? "" : "in");
397     [scroller setHidden:!visible];
399     // If a scroller was hidden or shown then the vim view must resize.  This
400     // is handled by the window controller (the vim view never resizes itself).
401     return wasVisible != visible;
404 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
405                     identifier:(long)ident
407     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
408     //NSLog(@"Set thumb value %.2f proportion %.2f for scroller %d",
409     //        val, prop, ident);
410     [scroller setFloatValue:val knobProportion:prop];
411     [scroller setEnabled:prop != 1.f];
415 - (void)scroll:(id)sender
417     NSMutableData *data = [NSMutableData data];
418     long ident = [(MMScroller*)sender identifier];
419     int hitPart = [sender hitPart];
420     float value = [sender floatValue];
422     [data appendBytes:&ident length:sizeof(long)];
423     [data appendBytes:&hitPart length:sizeof(int)];
424     [data appendBytes:&value length:sizeof(float)];
426     [vimController sendMessage:ScrollbarEventMsgID data:data];
429 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
431     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
432     NSRange range = NSMakeRange(pos, len);
433     if (!NSEqualRanges(range, [scroller range])) {
434         //NSLog(@"Set range %@ for scroller %d",
435         //        NSStringFromRange(range), ident);
436         [scroller setRange:range];
437         // TODO!  Should only do this once per update.
439         // This could be sent because a text window was created or closed, so
440         // we might need to update which scrollbars are visible.
441         [self placeScrollbars];
442     }
445 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
447     [textView setDefaultColorsBackground:back foreground:fore];
451 // -- PSMTabBarControl delegate ----------------------------------------------
454 - (BOOL)tabView:(NSTabView *)theTabView shouldSelectTabViewItem:
455     (NSTabViewItem *)tabViewItem
457     // NOTE: It would be reasonable to think that 'shouldSelect...' implies
458     // that this message only gets sent when the user clicks the tab.
459     // Unfortunately it is not so, which is why we need the
460     // 'vimTaskSelectedTab' flag.
461     //
462     // HACK!  The selection message should not be propagated to Vim if Vim
463     // selected the tab (e.g. as opposed the user clicking the tab).  The
464     // delegate method has no way of knowing who initiated the selection so a
465     // flag is set when Vim initiated the selection.
466     if (!vimTaskSelectedTab) {
467         // Propagate the selection message to Vim.
468         int idx = [self representedIndexOfTabViewItem:tabViewItem];
469         if (NSNotFound != idx) {
470             NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
471             [vimController sendMessage:SelectTabMsgID data:data];
472         }
473     }
475     // Unless Vim selected the tab, return NO, and let Vim decide if the tab
476     // should get selected or not.
477     return vimTaskSelectedTab;
480 - (BOOL)tabView:(NSTabView *)theTabView shouldCloseTabViewItem:
481         (NSTabViewItem *)tabViewItem
483     // HACK!  This method is only called when the user clicks the close button
484     // on the tab.  Instead of letting the tab bar close the tab, we return NO
485     // and pass a message on to Vim to let it handle the closing.
486     int idx = [self representedIndexOfTabViewItem:tabViewItem];
487     //NSLog(@"Closing tab with index %d", idx);
488     NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
489     [vimController sendMessage:CloseTabMsgID data:data];
491     return NO;
494 - (void)tabView:(NSTabView *)theTabView didDragTabViewItem:
495         (NSTabViewItem *)tabViewItem toIndex:(int)idx
497     NSMutableData *data = [NSMutableData data];
498     [data appendBytes:&idx length:sizeof(int)];
500     [vimController sendMessage:DraggedTabMsgID data:data];
503 - (NSDragOperation)tabBarControl:(PSMTabBarControl *)theTabBarControl
504         draggingEntered:(id <NSDraggingInfo>)sender
505         forTabAtIndex:(unsigned)tabIndex
507     NSPasteboard *pb = [sender draggingPasteboard];
508     return [[pb types] containsObject:NSFilenamesPboardType]
509             ? NSDragOperationCopy
510             : NSDragOperationNone;
513 - (BOOL)tabBarControl:(PSMTabBarControl *)theTabBarControl
514         performDragOperation:(id <NSDraggingInfo>)sender
515         forTabAtIndex:(unsigned)tabIndex
517     NSPasteboard *pb = [sender draggingPasteboard];
518     if ([[pb types] containsObject:NSFilenamesPboardType]) {
519         NSArray *filenames = [pb propertyListForType:NSFilenamesPboardType];
520         if ([filenames count] == 0)
521             return NO;
522         if (tabIndex != NSNotFound) {
523             // If dropping on a specific tab, only open one file
524             [vimController file:[filenames objectAtIndex:0]
525                 draggedToTabAtIndex:tabIndex];
526         } else {
527             // Files were dropped on empty part of tab bar; open them all
528             [vimController filesDraggedToTabBar:filenames];
529         }
530         return YES;
531     } else {
532         return NO;
533     }
538 // -- NSView customization ---------------------------------------------------
541 - (void)viewWillStartLiveResize
543     id windowController = [[self window] windowController];
544     [windowController liveResizeWillStart];
546     [super viewWillStartLiveResize];
549 - (void)viewDidEndLiveResize
551     id windowController = [[self window] windowController];
552     [windowController liveResizeDidEnd];
554     [super viewDidEndLiveResize];
557 - (void)setFrameSize:(NSSize)size
559     // NOTE: Instead of only acting when a frame was resized, we do some
560     // updating each time a frame may be resized.  (At the moment, if we only
561     // respond to actual frame changes then typing ":set lines=1000" twice in a
562     // row will result in the vim view holding more rows than the can fit
563     // inside the window.)
564     [super setFrameSize:size];
565     [self frameSizeMayHaveChanged];
568 - (void)setFrame:(NSRect)frame
570     // See comment in setFrameSize: above.
571     [super setFrame:frame];
572     [self frameSizeMayHaveChanged];
575 @end // MMVimView
580 @implementation MMVimView (Private)
582 - (BOOL)bottomScrollbarVisible
584     unsigned i, count = [scrollbars count];
585     for (i = 0; i < count; ++i) {
586         MMScroller *scroller = [scrollbars objectAtIndex:i];
587         if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden])
588             return YES;
589     }
591     return NO;
594 - (BOOL)leftScrollbarVisible
596     unsigned i, count = [scrollbars count];
597     for (i = 0; i < count; ++i) {
598         MMScroller *scroller = [scrollbars objectAtIndex:i];
599         if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden])
600             return YES;
601     }
603     return NO;
606 - (BOOL)rightScrollbarVisible
608     unsigned i, count = [scrollbars count];
609     for (i = 0; i < count; ++i) {
610         MMScroller *scroller = [scrollbars objectAtIndex:i];
611         if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden])
612             return YES;
613     }
615     return NO;
618 - (void)placeScrollbars
620     NSRect textViewFrame = [textView frame];
621     BOOL lsbVisible = [self leftScrollbarVisible];
623     // HACK!  Find the lowest left&right vertical scrollbars, as well as the
624     // rightmost horizontal scrollbar.  This hack continues further down.
625     //
626     // TODO!  Can there be no more than one horizontal scrollbar?  If so, the
627     // code can be simplified.
628     unsigned lowestLeftSbIdx = (unsigned)-1;
629     unsigned lowestRightSbIdx = (unsigned)-1;
630     unsigned rightmostSbIdx = (unsigned)-1;
631     unsigned rowMaxLeft = 0, rowMaxRight = 0, colMax = 0;
632     unsigned i, count = [scrollbars count];
633     for (i = 0; i < count; ++i) {
634         MMScroller *scroller = [scrollbars objectAtIndex:i];
635         if (![scroller isHidden]) {
636             NSRange range = [scroller range];
637             if ([scroller type] == MMScrollerTypeLeft
638                     && range.location >= rowMaxLeft) {
639                 rowMaxLeft = range.location;
640                 lowestLeftSbIdx = i;
641             } else if ([scroller type] == MMScrollerTypeRight
642                     && range.location >= rowMaxRight) {
643                 rowMaxRight = range.location;
644                 lowestRightSbIdx = i;
645             } else if ([scroller type] == MMScrollerTypeBottom
646                     && range.location >= colMax) {
647                 colMax = range.location;
648                 rightmostSbIdx = i;
649             }
650         }
651     }
653     // Place the scrollbars.
654     for (i = 0; i < count; ++i) {
655         MMScroller *scroller = [scrollbars objectAtIndex:i];
656         if ([scroller isHidden])
657             continue;
659         NSRect rect;
660         if ([scroller type] == MMScrollerTypeBottom) {
661             rect = [textView rectForColumnsInRange:[scroller range]];
662             rect.size.height = [NSScroller scrollerWidth];
663             if (lsbVisible)
664                 rect.origin.x += [NSScroller scrollerWidth];
666             // HACK!  Make sure the rightmost horizontal scrollbar covers the
667             // text view all the way to the right, otherwise it looks ugly when
668             // the user drags the window to resize.
669             if (i == rightmostSbIdx) {
670                 float w = NSMaxX(textViewFrame) - NSMaxX(rect);
671                 if (w > 0)
672                     rect.size.width += w;
673             }
675             // Make sure scrollbar rect is bounded by the text view frame.
676             if (rect.origin.x < textViewFrame.origin.x)
677                 rect.origin.x = textViewFrame.origin.x;
678             else if (rect.origin.x > NSMaxX(textViewFrame))
679                 rect.origin.x = NSMaxX(textViewFrame);
680             if (NSMaxX(rect) > NSMaxX(textViewFrame))
681                 rect.size.width -= NSMaxX(rect) - NSMaxX(textViewFrame);
682             if (rect.size.width < 0)
683                 rect.size.width = 0;
684         } else {
685             rect = [textView rectForRowsInRange:[scroller range]];
686             // Adjust for the fact that text layout is flipped.
687             rect.origin.y = NSMaxY(textViewFrame) - rect.origin.y
688                     - rect.size.height;
689             rect.size.width = [NSScroller scrollerWidth];
690             if ([scroller type] == MMScrollerTypeRight)
691                 rect.origin.x = NSMaxX(textViewFrame);
693             // HACK!  Make sure the lowest vertical scrollbar covers the text
694             // view all the way to the bottom.  This is done because Vim only
695             // makes the scrollbar cover the (vim-)window it is associated with
696             // and this means there is always an empty gap in the scrollbar
697             // region next to the command line.
698             // TODO!  Find a nicer way to do this.
699             if (i == lowestLeftSbIdx || i == lowestRightSbIdx) {
700                 float h = rect.origin.y + rect.size.height
701                           - textViewFrame.origin.y;
702                 if (rect.size.height < h) {
703                     rect.origin.y = textViewFrame.origin.y;
704                     rect.size.height = h;
705                 }
706             }
708             // Vertical scrollers must not cover the resize box in the
709             // bottom-right corner of the window.
710             if ([[self window] showsResizeIndicator]  // XXX: make this a flag
711                 && rect.origin.y < [NSScroller scrollerWidth]) {
712                 rect.size.height -= [NSScroller scrollerWidth] - rect.origin.y;
713                 rect.origin.y = [NSScroller scrollerWidth];
714             }
716             // Make sure scrollbar rect is bounded by the text view frame.
717             if (rect.origin.y < textViewFrame.origin.y) {
718                 rect.size.height -= textViewFrame.origin.y - rect.origin.y;
719                 rect.origin.y = textViewFrame.origin.y;
720             } else if (rect.origin.y > NSMaxY(textViewFrame))
721                 rect.origin.y = NSMaxY(textViewFrame);
722             if (NSMaxY(rect) > NSMaxY(textViewFrame))
723                 rect.size.height -= NSMaxY(rect) - NSMaxY(textViewFrame);
724             if (rect.size.height < 0)
725                 rect.size.height = 0;
726         }
728         //NSLog(@"set scroller #%d frame = %@", i, NSStringFromRect(rect));
729         NSRect oldRect = [scroller frame];
730         if (!NSEqualRects(oldRect, rect)) {
731             [scroller setFrame:rect];
732             // Clear behind the old scroller frame, or parts of the old
733             // scroller might still be visible after setFrame:.
734             [[[self window] contentView] setNeedsDisplayInRect:oldRect];
735             [scroller setNeedsDisplay:YES];
736         }
737     }
740 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
742     NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
743     return [tabViewItems indexOfObject:tvi];
746 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx
748     unsigned i, count = [scrollbars count];
749     for (i = 0; i < count; ++i) {
750         MMScroller *scroller = [scrollbars objectAtIndex:i];
751         if ([scroller identifier] == ident) {
752             if (idx) *idx = i;
753             return scroller;
754         }
755     }
757     return nil;
760 - (NSSize)vimViewSizeForTextViewSize:(NSSize)textViewSize
762     NSSize size = textViewSize;
764     if (![[self tabBarControl] isHidden])
765         size.height += [[self tabBarControl] frame].size.height;
767     if ([self bottomScrollbarVisible])
768         size.height += [NSScroller scrollerWidth];
769     if ([self leftScrollbarVisible])
770         size.width += [NSScroller scrollerWidth];
771     if ([self rightScrollbarVisible])
772         size.width += [NSScroller scrollerWidth];
774     return size;
777 - (NSRect)textViewRectForVimViewSize:(NSSize)contentSize
779     NSRect rect = { 0, 0, contentSize.width, contentSize.height };
781     if (![[self tabBarControl] isHidden])
782         rect.size.height -= [[self tabBarControl] frame].size.height;
784     if ([self bottomScrollbarVisible]) {
785         rect.size.height -= [NSScroller scrollerWidth];
786         rect.origin.y += [NSScroller scrollerWidth];
787     }
788     if ([self leftScrollbarVisible]) {
789         rect.size.width -= [NSScroller scrollerWidth];
790         rect.origin.x += [NSScroller scrollerWidth];
791     }
792     if ([self rightScrollbarVisible])
793         rect.size.width -= [NSScroller scrollerWidth];
795     return rect;
798 - (NSTabView *)tabView
800     return tabView;
803 - (void)frameSizeMayHaveChanged
805     // NOTE: Whenever a call is made that may have changed the frame size we
806     // take the opportunity to make sure all subviews are in place and that the
807     // (rows,columns) are constrained to lie inside the new frame.  We not only
808     // do this when the frame really has changed since it is possible to modify
809     // the number of (rows,columns) without changing the frame size.
811     // Give all superfluous space to the text view. It might be smaller or
812     // larger than it wants to be, but this is needed during live resizing.
813     NSRect textViewRect = [self textViewRectForVimViewSize:[self frame].size];
814     [textView setFrame:textViewRect];
816     [self placeScrollbars];
818     // It is possible that the current number of (rows,columns) is too big or
819     // too small to fit the new frame.  If so, notify Vim that the text
820     // dimensions should change, but don't actually change the number of
821     // (rows,columns).  These numbers may only change when Vim initiates the
822     // change (as opposed to the user dragging the window resizer, for
823     // example).
824     //
825     // Note that the message sent to Vim depends on whether we're in
826     // a live resize or not -- this is necessary to avoid the window jittering
827     // when the user drags to resize.
828     int constrained[2];
829     NSSize textViewSize = [textView frame].size;
830     [textView constrainRows:&constrained[0] columns:&constrained[1]
831                      toSize:textViewSize];
833     int rows, cols;
834     [textView getMaxRows:&rows columns:&cols];
836     if (constrained[0] != rows || constrained[1] != cols) {
837         NSData *data = [NSData dataWithBytes:constrained length:2*sizeof(int)];
838         int msgid = [self inLiveResize] ? LiveResizeMsgID
839                                         : SetTextDimensionsMsgID;
841         //NSLog(@"Notify Vim that text dimensions changed from %dx%d to %dx%d"
842         //       " (%s)", cols, rows, constrained[1], constrained[0],
843         //       MessageStrings[msgid]);
845         [vimController sendMessage:msgid data:data];
847         // We only want to set the window title if this resize came from
848         // a live-resize, not (for example) setting 'columns' or 'lines'.
849         if ([self inLiveResize]) {
850             [[self window] setTitle:[NSString stringWithFormat:@"%dx%d",
851                     constrained[1], constrained[0]]];
852         }
853     }
856 @end // MMVimView (Private)
861 @implementation MMScroller
863 - (id)initWithIdentifier:(long)ident type:(int)theType
865     // HACK! NSScroller creates a horizontal scroller if it is init'ed with a
866     // frame whose with exceeds its height; so create a bogus rect and pass it
867     // to initWithFrame.
868     NSRect frame = theType == MMScrollerTypeBottom
869             ? NSMakeRect(0, 0, 1, 0)
870             : NSMakeRect(0, 0, 0, 1);
872     self = [super initWithFrame:frame];
873     if (!self) return nil;
875     identifier = ident;
876     type = theType;
877     [self setHidden:YES];
878     [self setEnabled:YES];
879     [self setAutoresizingMask:NSViewNotSizable];
881     return self;
884 - (long)identifier
886     return identifier;
889 - (int)type
891     return type;
894 - (NSRange)range
896     return range;
899 - (void)setRange:(NSRange)newRange
901     range = newRange;
904 - (void)scrollWheel:(NSEvent *)event
906     // HACK! Pass message on to the text view.
907     NSView *vimView = [self superview];
908     if ([vimView isKindOfClass:[MMVimView class]])
909         [[(MMVimView*)vimView textView] scrollWheel:event];
912 @end // MMScroller