Add MMRenderer user default
[MacVim.git] / src / MacVim / MMVimView.m
blob429240a5b3554494d07deb6ffb177c213132578b
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 #if MM_ENABLE_ATSUI
22 # import "MMAtsuiTextView.h"
23 #else
24 # import "MMCoreTextView.h"
25 #endif
26 #import "MMTextView.h"
27 #import "MMVimController.h"
28 #import "MMVimView.h"
29 #import "MMWindowController.h"
30 #import "Miscellaneous.h"
31 #import <PSMTabBarControl/PSMTabBarControl.h>
35 // Scroller type; these must match SBAR_* in gui.h
36 enum {
37     MMScrollerTypeLeft = 0,
38     MMScrollerTypeRight,
39     MMScrollerTypeBottom
43 // TODO:  Move!
44 @interface MMScroller : NSScroller {
45     int32_t identifier;
46     int type;
47     NSRange range;
49 - (id)initWithIdentifier:(int32_t)ident type:(int)type;
50 - (int32_t)scrollerId;
51 - (int)type;
52 - (NSRange)range;
53 - (void)setRange:(NSRange)newRange;
54 @end
57 @interface MMVimView (Private)
58 - (BOOL)bottomScrollbarVisible;
59 - (BOOL)leftScrollbarVisible;
60 - (BOOL)rightScrollbarVisible;
61 - (void)placeScrollbars;
62 - (NSUInteger)representedIndexOfTabViewItem:(NSTabViewItem *)tvi;
63 - (MMScroller *)scrollbarForIdentifier:(int32_t)ident index:(unsigned *)idx;
64 - (NSSize)vimViewSizeForTextViewSize:(NSSize)textViewSize;
65 - (NSRect)textViewRectForVimViewSize:(NSSize)contentSize;
66 - (NSTabView *)tabView;
67 - (void)frameSizeMayHaveChanged;
68 @end
71 // This is an informal protocol implemented by MMWindowController (maybe it
72 // shold be a formal protocol, but ...).
73 @interface NSWindowController (MMVimViewDelegate)
74 - (void)liveResizeWillStart;
75 - (void)liveResizeDidEnd;
76 @end
80 @implementation MMVimView
82 - (MMVimView *)initWithFrame:(NSRect)frame
83                vimController:(MMVimController *)controller
85     if (![super initWithFrame:frame])
86         return nil;
87     
88     vimController = controller;
89     scrollbars = [[NSMutableArray alloc] init];
91     // Only the tabline is autoresized, all other subview placement is done in
92     // frameSizeMayHaveChanged.
93     [self setAutoresizesSubviews:YES];
95     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
96     NSInteger renderer = [ud integerForKey:MMRendererKey];
97     ASLogInfo(@"Use renderer=%d", renderer);
99     if (MMRendererCoreText == renderer) {
100         // HACK! 'textView' has type MMTextView, but MMCoreTextView is not
101         // derived from MMTextView.
102         textView = [[MMCoreTextView alloc] initWithFrame:frame];
103 #if MM_ENABLE_ATSUI
104     } else if (MMRendererATSUI == renderer) {
105         // HACK! 'textView' has type MMTextView, but MMAtsuiTextView is not
106         // derived from MMTextView.
107         textView = [[MMAtsuiTextView alloc] initWithFrame:frame];
108 #endif
109     } else {
110         // Use Cocoa text system for text rendering.
111         textView = [[MMTextView alloc] initWithFrame:frame];
112     }
114     // Allow control of text view inset via MMTextInset* user defaults.
115     int left = [ud integerForKey:MMTextInsetLeftKey];
116     int top = [ud integerForKey:MMTextInsetTopKey];
117     [textView setTextContainerInset:NSMakeSize(left, top)];
119     [textView setAutoresizingMask:NSViewNotSizable];
120     [self addSubview:textView];
121     
122     // Create the tab view (which is never visible, but the tab bar control
123     // needs it to function).
124     tabView = [[NSTabView alloc] initWithFrame:NSZeroRect];
126     // Create the tab bar control (which is responsible for actually
127     // drawing the tabline and tabs).
128     NSRect tabFrame = { { 0, frame.size.height - 22 },
129                         { frame.size.width, 22 } };
130     tabBarControl = [[PSMTabBarControl alloc] initWithFrame:tabFrame];
132     [tabView setDelegate:tabBarControl];
134     [tabBarControl setTabView:tabView];
135     [tabBarControl setDelegate:self];
136     [tabBarControl setHidden:YES];
138     [tabBarControl setCellMinWidth:[ud integerForKey:MMTabMinWidthKey]];
139     [tabBarControl setCellMaxWidth:[ud integerForKey:MMTabMaxWidthKey]];
140     [tabBarControl setCellOptimumWidth:
141                                      [ud integerForKey:MMTabOptimumWidthKey]];
143     [tabBarControl setShowAddTabButton:[ud boolForKey:MMShowAddTabButtonKey]];
144     [[tabBarControl addTabButton] setTarget:self];
145     [[tabBarControl addTabButton] setAction:@selector(addNewTab:)];
146     [tabBarControl setAllowsDragBetweenWindows:NO];
147     [tabBarControl registerForDraggedTypes:
148                             [NSArray arrayWithObject:NSFilenamesPboardType]];
150     [tabBarControl setAutoresizingMask:NSViewWidthSizable|NSViewMinYMargin];
151     
152     //[tabBarControl setPartnerView:textView];
153     
154     // tab bar resizing only works if awakeFromNib is called (that's where
155     // the NSViewFrameDidChangeNotification callback is installed). Sounds like
156     // a PSMTabBarControl bug, let's live with it for now.
157     [tabBarControl awakeFromNib];
159     [self addSubview:tabBarControl];
161     return self;
164 - (void)dealloc
166     ASLogDebug(@"");
168     [tabBarControl release];  tabBarControl = nil;
169     [tabView release];  tabView = nil;
170     [scrollbars release];  scrollbars = nil;
172     // HACK! The text storage is the principal owner of the text system, but we
173     // keep only a reference to the text view, so release the text storage
174     // first (unless we are using the ATSUI renderer).
175     if ([textView isKindOfClass:[MMTextView class]])
176         [[textView textStorage] release];
178     [textView release];  textView = nil;
180     [super dealloc];
183 - (BOOL)isOpaque
185     return YES;
188 - (void)drawRect:(NSRect)rect
190     // On Leopard, we want to have a textured window background for nice
191     // looking tabs. However, the textured window background looks really
192     // weird behind the window resize throbber, so emulate the look of an
193     // NSScrollView in the bottom right corner.
194     if (![[self window] showsResizeIndicator]  // XXX: make this a flag
195             || !([[self window] styleMask] & NSTexturedBackgroundWindowMask))
196         return;
198     int sw = [NSScroller scrollerWidth];
200     // add .5 to the pixel locations to put the lines on a pixel boundary.
201     // the top and right edges of the rect will be outside of the bounds rect
202     // and clipped away.
203     NSRect sizerRect = NSMakeRect([self bounds].size.width - sw + .5, -.5,
204             sw, sw);
205     //NSBezierPath* path = [NSBezierPath bezierPath];
206     NSBezierPath* path = [NSBezierPath bezierPathWithRect:sizerRect];
208     // On Tiger, we have color #E8E8E8 behind the resize throbber
209     // (which is windowBackgroundColor on untextured windows or controlColor in
210     // general). Terminal.app on Leopard has #FFFFFF background and #D9D9D9 as
211     // stroke. The colors below are #FFFFFF and #D4D4D4, which is close enough
212     // for me.
213     [[NSColor controlBackgroundColor] set];
214     [path fill];
216     [[NSColor secondarySelectedControlColor] set];
217     [path stroke];
220 - (MMTextView *)textView
222     return textView;
225 - (PSMTabBarControl *)tabBarControl
227     return tabBarControl;
230 - (void)cleanup
232     vimController = nil;
233     
234     // NOTE! There is a bug in PSMTabBarControl in that it retains the delegate
235     // so reset the delegate here, otherwise the delegate may never get
236     // released.
237     [tabView setDelegate:nil];
238     [tabBarControl setDelegate:nil];
239     [tabBarControl setTabView:nil];
240     [[self window] setDelegate:nil];
242     // NOTE! There is another bug in PSMTabBarControl where the control is not
243     // removed as an observer, so remove it here (failing to remove an observer
244     // may lead to very strange bugs).
245     [[NSNotificationCenter defaultCenter] removeObserver:tabBarControl];
247     [tabBarControl removeFromSuperviewWithoutNeedingDisplay];
248     [textView removeFromSuperviewWithoutNeedingDisplay];
250     unsigned i, count = [scrollbars count];
251     for (i = 0; i < count; ++i) {
252         MMScroller *sb = [scrollbars objectAtIndex:i];
253         [sb removeFromSuperviewWithoutNeedingDisplay];
254     }
256     [tabView removeAllTabViewItems];
259 - (NSSize)desiredSize
261     return [self vimViewSizeForTextViewSize:[textView desiredSize]];
264 - (NSSize)minSize
266     return [self vimViewSizeForTextViewSize:[textView minSize]];
269 - (NSSize)constrainRows:(int *)r columns:(int *)c toSize:(NSSize)size
271     NSSize textViewSize = [self textViewRectForVimViewSize:size].size;
272     textViewSize = [textView constrainRows:r columns:c toSize:textViewSize];
273     return [self vimViewSizeForTextViewSize:textViewSize];
276 - (void)setDesiredRows:(int)r columns:(int)c
278     [textView setMaxRows:r columns:c];
281 - (IBAction)addNewTab:(id)sender
283     [vimController sendMessage:AddNewTabMsgID data:nil];
286 - (void)updateTabsWithData:(NSData *)data
288     const void *p = [data bytes];
289     const void *end = p + [data length];
290     int tabIdx = 0;
292     // HACK!  Current tab is first in the message.  This way it is not
293     // necessary to guess which tab should be the selected one (this can be
294     // problematic for instance when new tabs are created).
295     int curtabIdx = *((int*)p);  p += sizeof(int);
297     NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
299     while (p < end) {
300         NSTabViewItem *tvi = nil;
302         //int wincount = *((int*)p);  p += sizeof(int);
303         int infoCount = *((int*)p); p += sizeof(int);
304         unsigned i;
305         for (i = 0; i < infoCount; ++i) {
306             int length = *((int*)p);  p += sizeof(int);
307             if (length <= 0)
308                 continue;
310             NSString *val = [[NSString alloc]
311                     initWithBytes:(void*)p length:length
312                          encoding:NSUTF8StringEncoding];
313             p += length;
315             switch (i) {
316                 case MMTabLabel:
317                     // Set the label of the tab, adding a new tab when needed.
318                     tvi = [[self tabView] numberOfTabViewItems] <= tabIdx
319                             ? [self addNewTabViewItem]
320                             : [tabViewItems objectAtIndex:tabIdx];
321                     [tvi setLabel:val];
322                     ++tabIdx;
323                     break;
324                 case MMTabToolTip:
325                     if (tvi)
326                         [[self tabBarControl] setToolTip:val
327                                           forTabViewItem:tvi];
328                     break;
329                 default:
330                     ASLogWarn(@"Unknown tab info for index: %d", i);
331             }
333             [val release];
334         }
335     }
337     // Remove unused tabs from the NSTabView.  Note that when a tab is closed
338     // the NSTabView will automatically select another tab, but we want Vim to
339     // take care of which tab to select so set the vimTaskSelectedTab flag to
340     // prevent the tab selection message to be passed on to the VimTask.
341     vimTaskSelectedTab = YES;
342     int i, count = [[self tabView] numberOfTabViewItems];
343     for (i = count-1; i >= tabIdx; --i) {
344         id tvi = [tabViewItems objectAtIndex:i];
345         [[self tabView] removeTabViewItem:tvi];
346     }
347     vimTaskSelectedTab = NO;
349     [self selectTabWithIndex:curtabIdx];
352 - (void)selectTabWithIndex:(int)idx
354     NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
355     if (idx < 0 || idx >= [tabViewItems count]) {
356         ASLogWarn(@"No tab with index %d exists.", idx);
357         return;
358     }
360     // Do not try to select a tab if already selected.
361     NSTabViewItem *tvi = [tabViewItems objectAtIndex:idx];
362     if (tvi != [[self tabView] selectedTabViewItem]) {
363         vimTaskSelectedTab = YES;
364         [[self tabView] selectTabViewItem:tvi];
365         vimTaskSelectedTab = NO;
367         // We might need to change the scrollbars that are visible.
368         [self placeScrollbars];
369     }
372 - (NSTabViewItem *)addNewTabViewItem
374     // NOTE!  A newly created tab is not by selected by default; Vim decides
375     // which tab should be selected at all times.  However, the AppKit will
376     // automatically select the first tab added to a tab view.
378     NSTabViewItem *tvi = [[NSTabViewItem alloc] initWithIdentifier:nil];
380     // NOTE: If this is the first tab it will be automatically selected.
381     vimTaskSelectedTab = YES;
382     [[self tabView] addTabViewItem:tvi];
383     vimTaskSelectedTab = NO;
385     [tvi autorelease];
387     return tvi;
390 - (void)createScrollbarWithIdentifier:(int32_t)ident type:(int)type
392     MMScroller *scroller = [[MMScroller alloc] initWithIdentifier:ident
393                                                              type:type];
394     [scroller setTarget:self];
395     [scroller setAction:@selector(scroll:)];
397     [self addSubview:scroller];
398     [scrollbars addObject:scroller];
399     [scroller release];
402 - (BOOL)destroyScrollbarWithIdentifier:(int32_t)ident
404     unsigned idx = 0;
405     MMScroller *scroller = [self scrollbarForIdentifier:ident index:&idx];
406     if (!scroller) return NO;
408     [scroller removeFromSuperview];
409     [scrollbars removeObjectAtIndex:idx];
411     // If a visible scroller was removed then the vim view must resize.  This
412     // is handled by the window controller (the vim view never resizes itself).
413     return ![scroller isHidden];
416 - (BOOL)showScrollbarWithIdentifier:(int32_t)ident state:(BOOL)visible
418     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
419     if (!scroller) return NO;
421     BOOL wasVisible = ![scroller isHidden];
422     [scroller setHidden:!visible];
424     // If a scroller was hidden or shown then the vim view must resize.  This
425     // is handled by the window controller (the vim view never resizes itself).
426     return wasVisible != visible;
429 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
430                     identifier:(int32_t)ident
432     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
433 #if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)
434     [scroller setDoubleValue:val];
435     [scroller setKnobProportion:prop];
436 #else
437     [scroller setFloatValue:val knobProportion:prop];
438 #endif
439     [scroller setEnabled:prop != 1.f];
443 - (void)scroll:(id)sender
445     NSMutableData *data = [NSMutableData data];
446     int32_t ident = [(MMScroller*)sender scrollerId];
447     int hitPart = [sender hitPart];
448     float value = [sender floatValue];
450     [data appendBytes:&ident length:sizeof(int32_t)];
451     [data appendBytes:&hitPart length:sizeof(int)];
452     [data appendBytes:&value length:sizeof(float)];
454     [vimController sendMessage:ScrollbarEventMsgID data:data];
457 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(int32_t)ident
459     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
460     NSRange range = NSMakeRange(pos, len);
461     if (!NSEqualRanges(range, [scroller range])) {
462         [scroller setRange:range];
463         // TODO!  Should only do this once per update.
465         // This could be sent because a text window was created or closed, so
466         // we might need to update which scrollbars are visible.
467         [self placeScrollbars];
468     }
471 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
473     [textView setDefaultColorsBackground:back foreground:fore];
477 // -- PSMTabBarControl delegate ----------------------------------------------
480 - (BOOL)tabView:(NSTabView *)theTabView shouldSelectTabViewItem:
481     (NSTabViewItem *)tabViewItem
483     // NOTE: It would be reasonable to think that 'shouldSelect...' implies
484     // that this message only gets sent when the user clicks the tab.
485     // Unfortunately it is not so, which is why we need the
486     // 'vimTaskSelectedTab' flag.
487     //
488     // HACK!  The selection message should not be propagated to Vim if Vim
489     // selected the tab (e.g. as opposed the user clicking the tab).  The
490     // delegate method has no way of knowing who initiated the selection so a
491     // flag is set when Vim initiated the selection.
492     if (!vimTaskSelectedTab) {
493         // Propagate the selection message to Vim.
494         NSUInteger idx = [self representedIndexOfTabViewItem:tabViewItem];
495         if (NSNotFound != idx) {
496             int i = (int)idx;   // HACK! Never more than MAXINT tabs?!
497             NSData *data = [NSData dataWithBytes:&i length:sizeof(int)];
498             [vimController sendMessage:SelectTabMsgID data:data];
499         }
500     }
502     // Unless Vim selected the tab, return NO, and let Vim decide if the tab
503     // should get selected or not.
504     return vimTaskSelectedTab;
507 - (BOOL)tabView:(NSTabView *)theTabView shouldCloseTabViewItem:
508         (NSTabViewItem *)tabViewItem
510     // HACK!  This method is only called when the user clicks the close button
511     // on the tab.  Instead of letting the tab bar close the tab, we return NO
512     // and pass a message on to Vim to let it handle the closing.
513     NSUInteger idx = [self representedIndexOfTabViewItem:tabViewItem];
514     int i = (int)idx;   // HACK! Never more than MAXINT tabs?!
515     NSData *data = [NSData dataWithBytes:&i length:sizeof(int)];
516     [vimController sendMessage:CloseTabMsgID data:data];
518     return NO;
521 - (void)tabView:(NSTabView *)theTabView didDragTabViewItem:
522         (NSTabViewItem *)tabViewItem toIndex:(int)idx
524     NSMutableData *data = [NSMutableData data];
525     [data appendBytes:&idx length:sizeof(int)];
527     [vimController sendMessage:DraggedTabMsgID data:data];
530 - (NSDragOperation)tabBarControl:(PSMTabBarControl *)theTabBarControl
531         draggingEntered:(id <NSDraggingInfo>)sender
532         forTabAtIndex:(NSUInteger)tabIndex
534     NSPasteboard *pb = [sender draggingPasteboard];
535     return [[pb types] containsObject:NSFilenamesPboardType]
536             ? NSDragOperationCopy
537             : NSDragOperationNone;
540 - (BOOL)tabBarControl:(PSMTabBarControl *)theTabBarControl
541         performDragOperation:(id <NSDraggingInfo>)sender
542         forTabAtIndex:(NSUInteger)tabIndex
544     NSPasteboard *pb = [sender draggingPasteboard];
545     if ([[pb types] containsObject:NSFilenamesPboardType]) {
546         NSArray *filenames = [pb propertyListForType:NSFilenamesPboardType];
547         if ([filenames count] == 0)
548             return NO;
549         if (tabIndex != NSNotFound) {
550             // If dropping on a specific tab, only open one file
551             [vimController file:[filenames objectAtIndex:0]
552                 draggedToTabAtIndex:tabIndex];
553         } else {
554             // Files were dropped on empty part of tab bar; open them all
555             [vimController filesDraggedToTabBar:filenames];
556         }
557         return YES;
558     } else {
559         return NO;
560     }
565 // -- NSView customization ---------------------------------------------------
568 - (void)viewWillStartLiveResize
570     id windowController = [[self window] windowController];
571     [windowController liveResizeWillStart];
573     [super viewWillStartLiveResize];
576 - (void)viewDidEndLiveResize
578     id windowController = [[self window] windowController];
579     [windowController liveResizeDidEnd];
581     [super viewDidEndLiveResize];
584 - (void)setFrameSize:(NSSize)size
586     // NOTE: Instead of only acting when a frame was resized, we do some
587     // updating each time a frame may be resized.  (At the moment, if we only
588     // respond to actual frame changes then typing ":set lines=1000" twice in a
589     // row will result in the vim view holding more rows than the can fit
590     // inside the window.)
591     [super setFrameSize:size];
592     [self frameSizeMayHaveChanged];
595 - (void)setFrame:(NSRect)frame
597     // See comment in setFrameSize: above.
598     [super setFrame:frame];
599     [self frameSizeMayHaveChanged];
602 @end // MMVimView
607 @implementation MMVimView (Private)
609 - (BOOL)bottomScrollbarVisible
611     unsigned i, count = [scrollbars count];
612     for (i = 0; i < count; ++i) {
613         MMScroller *scroller = [scrollbars objectAtIndex:i];
614         if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden])
615             return YES;
616     }
618     return NO;
621 - (BOOL)leftScrollbarVisible
623     unsigned i, count = [scrollbars count];
624     for (i = 0; i < count; ++i) {
625         MMScroller *scroller = [scrollbars objectAtIndex:i];
626         if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden])
627             return YES;
628     }
630     return NO;
633 - (BOOL)rightScrollbarVisible
635     unsigned i, count = [scrollbars count];
636     for (i = 0; i < count; ++i) {
637         MMScroller *scroller = [scrollbars objectAtIndex:i];
638         if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden])
639             return YES;
640     }
642     return NO;
645 - (void)placeScrollbars
647     NSRect textViewFrame = [textView frame];
648     BOOL lsbVisible = [self leftScrollbarVisible];
650     // HACK!  Find the lowest left&right vertical scrollbars, as well as the
651     // rightmost horizontal scrollbar.  This hack continues further down.
652     //
653     // TODO!  Can there be no more than one horizontal scrollbar?  If so, the
654     // code can be simplified.
655     unsigned lowestLeftSbIdx = (unsigned)-1;
656     unsigned lowestRightSbIdx = (unsigned)-1;
657     unsigned rightmostSbIdx = (unsigned)-1;
658     unsigned rowMaxLeft = 0, rowMaxRight = 0, colMax = 0;
659     unsigned i, count = [scrollbars count];
660     for (i = 0; i < count; ++i) {
661         MMScroller *scroller = [scrollbars objectAtIndex:i];
662         if (![scroller isHidden]) {
663             NSRange range = [scroller range];
664             if ([scroller type] == MMScrollerTypeLeft
665                     && range.location >= rowMaxLeft) {
666                 rowMaxLeft = range.location;
667                 lowestLeftSbIdx = i;
668             } else if ([scroller type] == MMScrollerTypeRight
669                     && range.location >= rowMaxRight) {
670                 rowMaxRight = range.location;
671                 lowestRightSbIdx = i;
672             } else if ([scroller type] == MMScrollerTypeBottom
673                     && range.location >= colMax) {
674                 colMax = range.location;
675                 rightmostSbIdx = i;
676             }
677         }
678     }
680     // Place the scrollbars.
681     for (i = 0; i < count; ++i) {
682         MMScroller *scroller = [scrollbars objectAtIndex:i];
683         if ([scroller isHidden])
684             continue;
686         NSRect rect;
687         if ([scroller type] == MMScrollerTypeBottom) {
688             rect = [textView rectForColumnsInRange:[scroller range]];
689             rect.size.height = [NSScroller scrollerWidth];
690             if (lsbVisible)
691                 rect.origin.x += [NSScroller scrollerWidth];
693             // HACK!  Make sure the rightmost horizontal scrollbar covers the
694             // text view all the way to the right, otherwise it looks ugly when
695             // the user drags the window to resize.
696             if (i == rightmostSbIdx) {
697                 float w = NSMaxX(textViewFrame) - NSMaxX(rect);
698                 if (w > 0)
699                     rect.size.width += w;
700             }
702             // Make sure scrollbar rect is bounded by the text view frame.
703             if (rect.origin.x < textViewFrame.origin.x)
704                 rect.origin.x = textViewFrame.origin.x;
705             else if (rect.origin.x > NSMaxX(textViewFrame))
706                 rect.origin.x = NSMaxX(textViewFrame);
707             if (NSMaxX(rect) > NSMaxX(textViewFrame))
708                 rect.size.width -= NSMaxX(rect) - NSMaxX(textViewFrame);
709             if (rect.size.width < 0)
710                 rect.size.width = 0;
711         } else {
712             rect = [textView rectForRowsInRange:[scroller range]];
713             // Adjust for the fact that text layout is flipped.
714             rect.origin.y = NSMaxY(textViewFrame) - rect.origin.y
715                     - rect.size.height;
716             rect.size.width = [NSScroller scrollerWidth];
717             if ([scroller type] == MMScrollerTypeRight)
718                 rect.origin.x = NSMaxX(textViewFrame);
720             // HACK!  Make sure the lowest vertical scrollbar covers the text
721             // view all the way to the bottom.  This is done because Vim only
722             // makes the scrollbar cover the (vim-)window it is associated with
723             // and this means there is always an empty gap in the scrollbar
724             // region next to the command line.
725             // TODO!  Find a nicer way to do this.
726             if (i == lowestLeftSbIdx || i == lowestRightSbIdx) {
727                 float h = rect.origin.y + rect.size.height
728                           - textViewFrame.origin.y;
729                 if (rect.size.height < h) {
730                     rect.origin.y = textViewFrame.origin.y;
731                     rect.size.height = h;
732                 }
733             }
735             // Vertical scrollers must not cover the resize box in the
736             // bottom-right corner of the window.
737             if ([[self window] showsResizeIndicator]  // XXX: make this a flag
738                 && rect.origin.y < [NSScroller scrollerWidth]) {
739                 rect.size.height -= [NSScroller scrollerWidth] - rect.origin.y;
740                 rect.origin.y = [NSScroller scrollerWidth];
741             }
743             // Make sure scrollbar rect is bounded by the text view frame.
744             if (rect.origin.y < textViewFrame.origin.y) {
745                 rect.size.height -= textViewFrame.origin.y - rect.origin.y;
746                 rect.origin.y = textViewFrame.origin.y;
747             } else if (rect.origin.y > NSMaxY(textViewFrame))
748                 rect.origin.y = NSMaxY(textViewFrame);
749             if (NSMaxY(rect) > NSMaxY(textViewFrame))
750                 rect.size.height -= NSMaxY(rect) - NSMaxY(textViewFrame);
751             if (rect.size.height < 0)
752                 rect.size.height = 0;
753         }
755         NSRect oldRect = [scroller frame];
756         if (!NSEqualRects(oldRect, rect)) {
757             [scroller setFrame:rect];
758             // Clear behind the old scroller frame, or parts of the old
759             // scroller might still be visible after setFrame:.
760             [[[self window] contentView] setNeedsDisplayInRect:oldRect];
761             [scroller setNeedsDisplay:YES];
762         }
763     }
766 - (NSUInteger)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
768     NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
769     return [tabViewItems indexOfObject:tvi];
772 - (MMScroller *)scrollbarForIdentifier:(int32_t)ident index:(unsigned *)idx
774     unsigned i, count = [scrollbars count];
775     for (i = 0; i < count; ++i) {
776         MMScroller *scroller = [scrollbars objectAtIndex:i];
777         if ([scroller scrollerId] == ident) {
778             if (idx) *idx = i;
779             return scroller;
780         }
781     }
783     return nil;
786 - (NSSize)vimViewSizeForTextViewSize:(NSSize)textViewSize
788     NSSize size = textViewSize;
790     if (![[self tabBarControl] isHidden])
791         size.height += [[self tabBarControl] frame].size.height;
793     if ([self bottomScrollbarVisible])
794         size.height += [NSScroller scrollerWidth];
795     if ([self leftScrollbarVisible])
796         size.width += [NSScroller scrollerWidth];
797     if ([self rightScrollbarVisible])
798         size.width += [NSScroller scrollerWidth];
800     return size;
803 - (NSRect)textViewRectForVimViewSize:(NSSize)contentSize
805     NSRect rect = { {0, 0}, {contentSize.width, contentSize.height} };
807     if (![[self tabBarControl] isHidden])
808         rect.size.height -= [[self tabBarControl] frame].size.height;
810     if ([self bottomScrollbarVisible]) {
811         rect.size.height -= [NSScroller scrollerWidth];
812         rect.origin.y += [NSScroller scrollerWidth];
813     }
814     if ([self leftScrollbarVisible]) {
815         rect.size.width -= [NSScroller scrollerWidth];
816         rect.origin.x += [NSScroller scrollerWidth];
817     }
818     if ([self rightScrollbarVisible])
819         rect.size.width -= [NSScroller scrollerWidth];
821     return rect;
824 - (NSTabView *)tabView
826     return tabView;
829 - (void)frameSizeMayHaveChanged
831     // NOTE: Whenever a call is made that may have changed the frame size we
832     // take the opportunity to make sure all subviews are in place and that the
833     // (rows,columns) are constrained to lie inside the new frame.  We not only
834     // do this when the frame really has changed since it is possible to modify
835     // the number of (rows,columns) without changing the frame size.
837     // Give all superfluous space to the text view. It might be smaller or
838     // larger than it wants to be, but this is needed during live resizing.
839     NSRect textViewRect = [self textViewRectForVimViewSize:[self frame].size];
840     [textView setFrame:textViewRect];
842     [self placeScrollbars];
844     // It is possible that the current number of (rows,columns) is too big or
845     // too small to fit the new frame.  If so, notify Vim that the text
846     // dimensions should change, but don't actually change the number of
847     // (rows,columns).  These numbers may only change when Vim initiates the
848     // change (as opposed to the user dragging the window resizer, for
849     // example).
850     //
851     // Note that the message sent to Vim depends on whether we're in
852     // a live resize or not -- this is necessary to avoid the window jittering
853     // when the user drags to resize.
854     int constrained[2];
855     NSSize textViewSize = [textView frame].size;
856     [textView constrainRows:&constrained[0] columns:&constrained[1]
857                      toSize:textViewSize];
859     int rows, cols;
860     [textView getMaxRows:&rows columns:&cols];
862     if (constrained[0] != rows || constrained[1] != cols) {
863         NSData *data = [NSData dataWithBytes:constrained length:2*sizeof(int)];
864         int msgid = [self inLiveResize] ? LiveResizeMsgID
865                                         : SetTextDimensionsMsgID;
867         ASLogDebug(@"Notify Vim that text dimensions changed from %dx%d to "
868                    "%dx%d (%s)", cols, rows, constrained[1], constrained[0],
869                    MessageStrings[msgid]);
871         [vimController sendMessage:msgid data:data];
873         // We only want to set the window title if this resize came from
874         // a live-resize, not (for example) setting 'columns' or 'lines'.
875         if ([self inLiveResize]) {
876             [[self window] setTitle:[NSString stringWithFormat:@"%dx%d",
877                     constrained[1], constrained[0]]];
878         }
879     }
882 @end // MMVimView (Private)
887 @implementation MMScroller
889 - (id)initWithIdentifier:(int32_t)ident type:(int)theType
891     // HACK! NSScroller creates a horizontal scroller if it is init'ed with a
892     // frame whose with exceeds its height; so create a bogus rect and pass it
893     // to initWithFrame.
894     NSRect frame = theType == MMScrollerTypeBottom
895             ? NSMakeRect(0, 0, 1, 0)
896             : NSMakeRect(0, 0, 0, 1);
898     self = [super initWithFrame:frame];
899     if (!self) return nil;
901     identifier = ident;
902     type = theType;
903     [self setHidden:YES];
904     [self setEnabled:YES];
905     [self setAutoresizingMask:NSViewNotSizable];
907     return self;
910 - (int32_t)scrollerId
912     return identifier;
915 - (int)type
917     return type;
920 - (NSRange)range
922     return range;
925 - (void)setRange:(NSRange)newRange
927     range = newRange;
930 - (void)scrollWheel:(NSEvent *)event
932     // HACK! Pass message on to the text view.
933     NSView *vimView = [self superview];
934     if ([vimView isKindOfClass:[MMVimView class]])
935         [[(MMVimView*)vimView textView] scrollWheel:event];
938 - (void)mouseDown:(NSEvent *)event
940     // TODO: This is an ugly way of getting the connection to the backend.
941     NSConnection *connection = nil;
942     id wc = [[self window] windowController];
943     if ([wc isKindOfClass:[MMWindowController class]]) {
944         MMVimController *vc = [(MMWindowController*)wc vimController];
945         id proxy = [vc backendProxy];
946         connection = [(NSDistantObject*)proxy connectionForProxy];
947     }
949     // NOTE: The scroller goes into "event tracking mode" when the user clicks
950     // (and holds) the mouse button.  We have to manually add the backend
951     // connection to this mode while the mouse button is held, else DO messages
952     // from Vim will not be processed until the mouse button is released.
953     [connection addRequestMode:NSEventTrackingRunLoopMode];
954     [super mouseDown:event];
955     [connection removeRequestMode:NSEventTrackingRunLoopMode];
958 @end // MMScroller