Change name of window menu items
[MacVim.git] / src / MacVim / MMVimView.m
blobd371f5e2fcd29f9a184ac0f185a12caca4a5906d
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.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     long identifier;
42     int type;
43     NSRange range;
45 - (id)initWithIdentifier:(long)ident type:(int)type;
46 - (long)identifier;
47 - (int)type;
48 - (NSRange)range;
49 - (void)setRange:(NSRange)newRange;
50 @end
53 @interface MMVimView (Private)
54 - (BOOL)bottomScrollbarVisible;
55 - (BOOL)leftScrollbarVisible;
56 - (BOOL)rightScrollbarVisible;
57 - (void)placeScrollbars;
58 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi;
59 - (MMScroller *)scrollbarForIdentifier:(long)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 ([ud boolForKey:MMAtsuiRendererKey]) {
93         // Use ATSUI for text rendering.
94         //
95         // HACK! 'textView' has type MMTextView, but MMAtsuiTextView is not
96         // derived from MMTextView.
97         textView = [[MMAtsuiTextView alloc] initWithFrame:frame];
98     } else {
99         // Use Cocoa text system for text rendering.
100         textView = [[MMTextView alloc] initWithFrame:frame];
101     }
103     // Allow control of text view inset via MMTextInset* user defaults.
104     int left = [ud integerForKey:MMTextInsetLeftKey];
105     int top = [ud integerForKey:MMTextInsetTopKey];
106     [textView setTextContainerInset:NSMakeSize(left, top)];
108     [textView setAutoresizingMask:NSViewNotSizable];
109     [self addSubview:textView];
110     
111     // Create the tab view (which is never visible, but the tab bar control
112     // needs it to function).
113     tabView = [[NSTabView alloc] initWithFrame:NSZeroRect];
115     // Create the tab bar control (which is responsible for actually
116     // drawing the tabline and tabs).
117     NSRect tabFrame = { { 0, frame.size.height - 22 },
118                         { frame.size.width, 22 } };
119     tabBarControl = [[PSMTabBarControl alloc] initWithFrame:tabFrame];
121     [tabView setDelegate:tabBarControl];
123     [tabBarControl setTabView:tabView];
124     [tabBarControl setDelegate:self];
125     [tabBarControl setHidden:YES];
127     [tabBarControl setCellMinWidth:[ud integerForKey:MMTabMinWidthKey]];
128     [tabBarControl setCellMaxWidth:[ud integerForKey:MMTabMaxWidthKey]];
129     [tabBarControl setCellOptimumWidth:
130                                      [ud integerForKey:MMTabOptimumWidthKey]];
132     [tabBarControl setShowAddTabButton:[ud boolForKey:MMShowAddTabButtonKey]];
133     [[tabBarControl addTabButton] setTarget:self];
134     [[tabBarControl addTabButton] setAction:@selector(addNewTab:)];
135     [tabBarControl setAllowsDragBetweenWindows:NO];
136     [tabBarControl registerForDraggedTypes:
137                             [NSArray arrayWithObject:NSFilenamesPboardType]];
139     [tabBarControl setAutoresizingMask:NSViewWidthSizable|NSViewMinYMargin];
140     
141     //[tabBarControl setPartnerView:textView];
142     
143     // tab bar resizing only works if awakeFromNib is called (that's where
144     // the NSViewFrameDidChangeNotification callback is installed). Sounds like
145     // a PSMTabBarControl bug, let's live with it for now.
146     [tabBarControl awakeFromNib];
148     [self addSubview:tabBarControl];
150     return self;
153 - (void)dealloc
155     LOG_DEALLOC
157     [tabBarControl release];  tabBarControl = nil;
158     [tabView release];  tabView = nil;
159     [scrollbars release];  scrollbars = nil;
161     // HACK! The text storage is the principal owner of the text system, but we
162     // keep only a reference to the text view, so release the text storage
163     // first (unless we are using the ATSUI renderer).
164     if ([textView isKindOfClass:[MMTextView class]])
165         [[textView textStorage] release];
167     [textView release];  textView = nil;
169     [super dealloc];
172 - (void)drawRect:(NSRect)rect
174     // On Leopard, we want to have a textured window background for nice
175     // looking tabs. However, the textured window background looks really
176     // weird behind the window resize throbber, so emulate the look of an
177     // NSScrollView in the bottom right corner.
178     if (![[self window] showsResizeIndicator]  // XXX: make this a flag
179             || !([[self window] styleMask] & NSTexturedBackgroundWindowMask))
180         return;
182     int sw = [NSScroller scrollerWidth];
184     // add .5 to the pixel locations to put the lines on a pixel boundary.
185     // the top and right edges of the rect will be outside of the bounds rect
186     // and clipped away.
187     NSRect sizerRect = NSMakeRect([self bounds].size.width - sw + .5, -.5,
188             sw, sw);
189     //NSBezierPath* path = [NSBezierPath bezierPath];
190     NSBezierPath* path = [NSBezierPath bezierPathWithRect:sizerRect];
192     // On Tiger, we have color #E8E8E8 behind the resize throbber
193     // (which is windowBackgroundColor on untextured windows or controlColor in
194     // general). Terminal.app on Leopard has #FFFFFF background and #D9D9D9 as
195     // stroke. The colors below are #FFFFFF and #D4D4D4, which is close enough
196     // for me.
197     [[NSColor controlBackgroundColor] set];
198     [path fill];
200     [[NSColor secondarySelectedControlColor] set];
201     [path stroke];
204 - (MMTextView *)textView
206     return textView;
209 - (PSMTabBarControl *)tabBarControl
211     return tabBarControl;
214 - (void)cleanup
216     vimController = nil;
217     
218     // NOTE! There is a bug in PSMTabBarControl in that it retains the delegate
219     // so reset the delegate here, otherwise the delegate may never get
220     // released.
221     [tabView setDelegate:nil];
222     [tabBarControl setDelegate:nil];
223     [tabBarControl setTabView:nil];
224     [[self window] setDelegate:nil];
226     // NOTE! There is another bug in PSMTabBarControl where the control is not
227     // removed as an observer, so remove it here (failing to remove an observer
228     // may lead to very strange bugs).
229     [[NSNotificationCenter defaultCenter] removeObserver:tabBarControl];
231     [tabBarControl removeFromSuperviewWithoutNeedingDisplay];
232     [textView removeFromSuperviewWithoutNeedingDisplay];
234     unsigned i, count = [scrollbars count];
235     for (i = 0; i < count; ++i) {
236         MMScroller *sb = [scrollbars objectAtIndex:i];
237         [sb removeFromSuperviewWithoutNeedingDisplay];
238     }
240     [tabView removeAllTabViewItems];
243 - (NSSize)desiredSize
245     return [self vimViewSizeForTextViewSize:[textView desiredSize]];
248 - (NSSize)minSize
250     return [self vimViewSizeForTextViewSize:[textView minSize]];
253 - (NSSize)constrainRows:(int *)r columns:(int *)c toSize:(NSSize)size
255     NSSize textViewSize = [self textViewRectForVimViewSize:size].size;
256     textViewSize = [textView constrainRows:r columns:c toSize:textViewSize];
257     return [self vimViewSizeForTextViewSize:textViewSize];
260 - (void)setDesiredRows:(int)r columns:(int)c
262     [textView setMaxRows:r columns:c];
265 - (IBAction)addNewTab:(id)sender
267     [vimController sendMessage:AddNewTabMsgID data:nil];
270 - (void)updateTabsWithData:(NSData *)data
272     const void *p = [data bytes];
273     const void *end = p + [data length];
274     int tabIdx = 0;
276     // HACK!  Current tab is first in the message.  This way it is not
277     // necessary to guess which tab should be the selected one (this can be
278     // problematic for instance when new tabs are created).
279     int curtabIdx = *((int*)p);  p += sizeof(int);
281     NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
283     while (p < end) {
284         NSTabViewItem *tvi = nil;
286         //int wincount = *((int*)p);  p += sizeof(int);
287         int infoCount = *((int*)p); p += sizeof(int);
288         unsigned i;
289         for (i = 0; i < infoCount; ++i) {
290             int length = *((int*)p);  p += sizeof(int);
291             if (length <= 0)
292                 continue;
294             NSString *val = [[NSString alloc]
295                     initWithBytes:(void*)p length:length
296                          encoding:NSUTF8StringEncoding];
297             p += length;
299             switch (i) {
300                 case MMTabLabel:
301                     // Set the label of the tab, adding a new tab when needed.
302                     tvi = [[self tabView] numberOfTabViewItems] <= tabIdx
303                             ? [self addNewTabViewItem]
304                             : [tabViewItems objectAtIndex:tabIdx];
305                     [tvi setLabel:val];
306                     ++tabIdx;
307                     break;
308                 case MMTabToolTip:
309                     if (tvi)
310                         [[self tabBarControl] setToolTip:val
311                                           forTabViewItem:tvi];
312                     break;
313                 default:
314                     NSLog(@"WARNING: Unknown tab info for index: %d", i);
315             }
317             [val release];
318         }
319     }
321     // Remove unused tabs from the NSTabView.  Note that when a tab is closed
322     // the NSTabView will automatically select another tab, but we want Vim to
323     // take care of which tab to select so set the vimTaskSelectedTab flag to
324     // prevent the tab selection message to be passed on to the VimTask.
325     vimTaskSelectedTab = YES;
326     int i, count = [[self tabView] numberOfTabViewItems];
327     for (i = count-1; i >= tabIdx; --i) {
328         id tvi = [tabViewItems objectAtIndex:i];
329         //NSLog(@"Removing tab with index %d", i);
330         [[self tabView] removeTabViewItem:tvi];
331     }
332     vimTaskSelectedTab = NO;
334     [self selectTabWithIndex:curtabIdx];
337 - (void)selectTabWithIndex:(int)idx
339     //NSLog(@"%s%d", _cmd, idx);
341     NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
342     if (idx < 0 || idx >= [tabViewItems count]) {
343         NSLog(@"WARNING: 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:(long)ident type:(int)type
379     //NSLog(@"Create scroller %d of type %d", ident, type);
381     MMScroller *scroller = [[MMScroller alloc] initWithIdentifier:ident
382                                                              type:type];
383     [scroller setTarget:self];
384     [scroller setAction:@selector(scroll:)];
386     [self addSubview:scroller];
387     [scrollbars addObject:scroller];
388     [scroller release];
391 - (BOOL)destroyScrollbarWithIdentifier:(long)ident
393     //NSLog(@"Destroy scroller %d", ident);
395     unsigned idx = 0;
396     MMScroller *scroller = [self scrollbarForIdentifier:ident index:&idx];
397     if (!scroller) return NO;
399     [scroller removeFromSuperview];
400     [scrollbars removeObjectAtIndex:idx];
402     // If a visible scroller was removed then the vim view must resize.  This
403     // is handled by the window controller (the vim view never resizes itself).
404     return ![scroller isHidden];
407 - (BOOL)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
409     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
410     if (!scroller) return NO;
412     BOOL wasVisible = ![scroller isHidden];
413     //NSLog(@"%s scroller %d (was %svisible)", visible ? "Show" : "Hide",
414     //      ident, wasVisible ? "" : "in");
415     [scroller setHidden:!visible];
417     // If a scroller was hidden or shown then the vim view must resize.  This
418     // is handled by the window controller (the vim view never resizes itself).
419     return wasVisible != visible;
422 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
423                     identifier:(long)ident
425     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
426     //NSLog(@"Set thumb value %.2f proportion %.2f for scroller %d",
427     //        val, prop, ident);
428     [scroller setFloatValue:val knobProportion:prop];
429     [scroller setEnabled:prop != 1.f];
433 - (void)scroll:(id)sender
435     NSMutableData *data = [NSMutableData data];
436     long ident = [(MMScroller*)sender identifier];
437     int hitPart = [sender hitPart];
438     float value = [sender floatValue];
440     [data appendBytes:&ident length:sizeof(long)];
441     [data appendBytes:&hitPart length:sizeof(int)];
442     [data appendBytes:&value length:sizeof(float)];
444     [vimController sendMessage:ScrollbarEventMsgID data:data];
447 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
449     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
450     NSRange range = NSMakeRange(pos, len);
451     if (!NSEqualRanges(range, [scroller range])) {
452         //NSLog(@"Set range %@ for scroller %d",
453         //        NSStringFromRange(range), ident);
454         [scroller setRange:range];
455         // TODO!  Should only do this once per update.
457         // This could be sent because a text window was created or closed, so
458         // we might need to update which scrollbars are visible.
459         [self placeScrollbars];
460     }
463 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
465     [textView setDefaultColorsBackground:back foreground:fore];
469 // -- PSMTabBarControl delegate ----------------------------------------------
472 - (BOOL)tabView:(NSTabView *)theTabView shouldSelectTabViewItem:
473     (NSTabViewItem *)tabViewItem
475     // NOTE: It would be reasonable to think that 'shouldSelect...' implies
476     // that this message only gets sent when the user clicks the tab.
477     // Unfortunately it is not so, which is why we need the
478     // 'vimTaskSelectedTab' flag.
479     //
480     // HACK!  The selection message should not be propagated to Vim if Vim
481     // selected the tab (e.g. as opposed the user clicking the tab).  The
482     // delegate method has no way of knowing who initiated the selection so a
483     // flag is set when Vim initiated the selection.
484     if (!vimTaskSelectedTab) {
485         // Propagate the selection message to Vim.
486         int idx = [self representedIndexOfTabViewItem:tabViewItem];
487         if (NSNotFound != idx) {
488             NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
489             [vimController sendMessage:SelectTabMsgID data:data];
490         }
491     }
493     // Unless Vim selected the tab, return NO, and let Vim decide if the tab
494     // should get selected or not.
495     return vimTaskSelectedTab;
498 - (BOOL)tabView:(NSTabView *)theTabView shouldCloseTabViewItem:
499         (NSTabViewItem *)tabViewItem
501     // HACK!  This method is only called when the user clicks the close button
502     // on the tab.  Instead of letting the tab bar close the tab, we return NO
503     // and pass a message on to Vim to let it handle the closing.
504     int idx = [self representedIndexOfTabViewItem:tabViewItem];
505     //NSLog(@"Closing tab with index %d", idx);
506     NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
507     [vimController sendMessage:CloseTabMsgID data:data];
509     return NO;
512 - (void)tabView:(NSTabView *)theTabView didDragTabViewItem:
513         (NSTabViewItem *)tabViewItem toIndex:(int)idx
515     NSMutableData *data = [NSMutableData data];
516     [data appendBytes:&idx length:sizeof(int)];
518     [vimController sendMessage:DraggedTabMsgID data:data];
521 - (NSDragOperation)tabBarControl:(PSMTabBarControl *)theTabBarControl
522         draggingEntered:(id <NSDraggingInfo>)sender
523         forTabAtIndex:(unsigned)tabIndex
525     NSPasteboard *pb = [sender draggingPasteboard];
526     return [[pb types] containsObject:NSFilenamesPboardType]
527             ? NSDragOperationCopy
528             : NSDragOperationNone;
531 - (BOOL)tabBarControl:(PSMTabBarControl *)theTabBarControl
532         performDragOperation:(id <NSDraggingInfo>)sender
533         forTabAtIndex:(unsigned)tabIndex
535     NSPasteboard *pb = [sender draggingPasteboard];
536     if ([[pb types] containsObject:NSFilenamesPboardType]) {
537         NSArray *filenames = [pb propertyListForType:NSFilenamesPboardType];
538         if ([filenames count] == 0)
539             return NO;
540         if (tabIndex != NSNotFound) {
541             // If dropping on a specific tab, only open one file
542             [vimController file:[filenames objectAtIndex:0]
543                 draggedToTabAtIndex:tabIndex];
544         } else {
545             // Files were dropped on empty part of tab bar; open them all
546             [vimController filesDraggedToTabBar:filenames];
547         }
548         return YES;
549     } else {
550         return NO;
551     }
556 // -- NSView customization ---------------------------------------------------
559 - (void)viewWillStartLiveResize
561     id windowController = [[self window] windowController];
562     [windowController liveResizeWillStart];
564     [super viewWillStartLiveResize];
567 - (void)viewDidEndLiveResize
569     id windowController = [[self window] windowController];
570     [windowController liveResizeDidEnd];
572     [super viewDidEndLiveResize];
575 - (void)setFrameSize:(NSSize)size
577     // NOTE: Instead of only acting when a frame was resized, we do some
578     // updating each time a frame may be resized.  (At the moment, if we only
579     // respond to actual frame changes then typing ":set lines=1000" twice in a
580     // row will result in the vim view holding more rows than the can fit
581     // inside the window.)
582     [super setFrameSize:size];
583     [self frameSizeMayHaveChanged];
586 - (void)setFrame:(NSRect)frame
588     // See comment in setFrameSize: above.
589     [super setFrame:frame];
590     [self frameSizeMayHaveChanged];
593 @end // MMVimView
598 @implementation MMVimView (Private)
600 - (BOOL)bottomScrollbarVisible
602     unsigned i, count = [scrollbars count];
603     for (i = 0; i < count; ++i) {
604         MMScroller *scroller = [scrollbars objectAtIndex:i];
605         if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden])
606             return YES;
607     }
609     return NO;
612 - (BOOL)leftScrollbarVisible
614     unsigned i, count = [scrollbars count];
615     for (i = 0; i < count; ++i) {
616         MMScroller *scroller = [scrollbars objectAtIndex:i];
617         if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden])
618             return YES;
619     }
621     return NO;
624 - (BOOL)rightScrollbarVisible
626     unsigned i, count = [scrollbars count];
627     for (i = 0; i < count; ++i) {
628         MMScroller *scroller = [scrollbars objectAtIndex:i];
629         if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden])
630             return YES;
631     }
633     return NO;
636 - (void)placeScrollbars
638     NSRect textViewFrame = [textView frame];
639     BOOL lsbVisible = [self leftScrollbarVisible];
641     // HACK!  Find the lowest left&right vertical scrollbars, as well as the
642     // rightmost horizontal scrollbar.  This hack continues further down.
643     //
644     // TODO!  Can there be no more than one horizontal scrollbar?  If so, the
645     // code can be simplified.
646     unsigned lowestLeftSbIdx = (unsigned)-1;
647     unsigned lowestRightSbIdx = (unsigned)-1;
648     unsigned rightmostSbIdx = (unsigned)-1;
649     unsigned rowMaxLeft = 0, rowMaxRight = 0, colMax = 0;
650     unsigned i, count = [scrollbars count];
651     for (i = 0; i < count; ++i) {
652         MMScroller *scroller = [scrollbars objectAtIndex:i];
653         if (![scroller isHidden]) {
654             NSRange range = [scroller range];
655             if ([scroller type] == MMScrollerTypeLeft
656                     && range.location >= rowMaxLeft) {
657                 rowMaxLeft = range.location;
658                 lowestLeftSbIdx = i;
659             } else if ([scroller type] == MMScrollerTypeRight
660                     && range.location >= rowMaxRight) {
661                 rowMaxRight = range.location;
662                 lowestRightSbIdx = i;
663             } else if ([scroller type] == MMScrollerTypeBottom
664                     && range.location >= colMax) {
665                 colMax = range.location;
666                 rightmostSbIdx = i;
667             }
668         }
669     }
671     // Place the scrollbars.
672     for (i = 0; i < count; ++i) {
673         MMScroller *scroller = [scrollbars objectAtIndex:i];
674         if ([scroller isHidden])
675             continue;
677         NSRect rect;
678         if ([scroller type] == MMScrollerTypeBottom) {
679             rect = [textView rectForColumnsInRange:[scroller range]];
680             rect.size.height = [NSScroller scrollerWidth];
681             if (lsbVisible)
682                 rect.origin.x += [NSScroller scrollerWidth];
684             // HACK!  Make sure the rightmost horizontal scrollbar covers the
685             // text view all the way to the right, otherwise it looks ugly when
686             // the user drags the window to resize.
687             if (i == rightmostSbIdx) {
688                 float w = NSMaxX(textViewFrame) - NSMaxX(rect);
689                 if (w > 0)
690                     rect.size.width += w;
691             }
693             // Make sure scrollbar rect is bounded by the text view frame.
694             if (rect.origin.x < textViewFrame.origin.x)
695                 rect.origin.x = textViewFrame.origin.x;
696             else if (rect.origin.x > NSMaxX(textViewFrame))
697                 rect.origin.x = NSMaxX(textViewFrame);
698             if (NSMaxX(rect) > NSMaxX(textViewFrame))
699                 rect.size.width -= NSMaxX(rect) - NSMaxX(textViewFrame);
700             if (rect.size.width < 0)
701                 rect.size.width = 0;
702         } else {
703             rect = [textView rectForRowsInRange:[scroller range]];
704             // Adjust for the fact that text layout is flipped.
705             rect.origin.y = NSMaxY(textViewFrame) - rect.origin.y
706                     - rect.size.height;
707             rect.size.width = [NSScroller scrollerWidth];
708             if ([scroller type] == MMScrollerTypeRight)
709                 rect.origin.x = NSMaxX(textViewFrame);
711             // HACK!  Make sure the lowest vertical scrollbar covers the text
712             // view all the way to the bottom.  This is done because Vim only
713             // makes the scrollbar cover the (vim-)window it is associated with
714             // and this means there is always an empty gap in the scrollbar
715             // region next to the command line.
716             // TODO!  Find a nicer way to do this.
717             if (i == lowestLeftSbIdx || i == lowestRightSbIdx) {
718                 float h = rect.origin.y + rect.size.height
719                           - textViewFrame.origin.y;
720                 if (rect.size.height < h) {
721                     rect.origin.y = textViewFrame.origin.y;
722                     rect.size.height = h;
723                 }
724             }
726             // Vertical scrollers must not cover the resize box in the
727             // bottom-right corner of the window.
728             if ([[self window] showsResizeIndicator]  // XXX: make this a flag
729                 && rect.origin.y < [NSScroller scrollerWidth]) {
730                 rect.size.height -= [NSScroller scrollerWidth] - rect.origin.y;
731                 rect.origin.y = [NSScroller scrollerWidth];
732             }
734             // Make sure scrollbar rect is bounded by the text view frame.
735             if (rect.origin.y < textViewFrame.origin.y) {
736                 rect.size.height -= textViewFrame.origin.y - rect.origin.y;
737                 rect.origin.y = textViewFrame.origin.y;
738             } else if (rect.origin.y > NSMaxY(textViewFrame))
739                 rect.origin.y = NSMaxY(textViewFrame);
740             if (NSMaxY(rect) > NSMaxY(textViewFrame))
741                 rect.size.height -= NSMaxY(rect) - NSMaxY(textViewFrame);
742             if (rect.size.height < 0)
743                 rect.size.height = 0;
744         }
746         //NSLog(@"set scroller #%d frame = %@", i, NSStringFromRect(rect));
747         NSRect oldRect = [scroller frame];
748         if (!NSEqualRects(oldRect, rect)) {
749             [scroller setFrame:rect];
750             // Clear behind the old scroller frame, or parts of the old
751             // scroller might still be visible after setFrame:.
752             [[[self window] contentView] setNeedsDisplayInRect:oldRect];
753             [scroller setNeedsDisplay:YES];
754         }
755     }
758 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
760     NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
761     return [tabViewItems indexOfObject:tvi];
764 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx
766     unsigned i, count = [scrollbars count];
767     for (i = 0; i < count; ++i) {
768         MMScroller *scroller = [scrollbars objectAtIndex:i];
769         if ([scroller identifier] == ident) {
770             if (idx) *idx = i;
771             return scroller;
772         }
773     }
775     return nil;
778 - (NSSize)vimViewSizeForTextViewSize:(NSSize)textViewSize
780     NSSize size = textViewSize;
782     if (![[self tabBarControl] isHidden])
783         size.height += [[self tabBarControl] frame].size.height;
785     if ([self bottomScrollbarVisible])
786         size.height += [NSScroller scrollerWidth];
787     if ([self leftScrollbarVisible])
788         size.width += [NSScroller scrollerWidth];
789     if ([self rightScrollbarVisible])
790         size.width += [NSScroller scrollerWidth];
792     return size;
795 - (NSRect)textViewRectForVimViewSize:(NSSize)contentSize
797     NSRect rect = { 0, 0, contentSize.width, contentSize.height };
799     if (![[self tabBarControl] isHidden])
800         rect.size.height -= [[self tabBarControl] frame].size.height;
802     if ([self bottomScrollbarVisible]) {
803         rect.size.height -= [NSScroller scrollerWidth];
804         rect.origin.y += [NSScroller scrollerWidth];
805     }
806     if ([self leftScrollbarVisible]) {
807         rect.size.width -= [NSScroller scrollerWidth];
808         rect.origin.x += [NSScroller scrollerWidth];
809     }
810     if ([self rightScrollbarVisible])
811         rect.size.width -= [NSScroller scrollerWidth];
813     return rect;
816 - (NSTabView *)tabView
818     return tabView;
821 - (void)frameSizeMayHaveChanged
823     // NOTE: Whenever a call is made that may have changed the frame size we
824     // take the opportunity to make sure all subviews are in place and that the
825     // (rows,columns) are constrained to lie inside the new frame.  We not only
826     // do this when the frame really has changed since it is possible to modify
827     // the number of (rows,columns) without changing the frame size.
829     // Give all superfluous space to the text view. It might be smaller or
830     // larger than it wants to be, but this is needed during live resizing.
831     NSRect textViewRect = [self textViewRectForVimViewSize:[self frame].size];
832     [textView setFrame:textViewRect];
834     [self placeScrollbars];
836     // It is possible that the current number of (rows,columns) is too big or
837     // too small to fit the new frame.  If so, notify Vim that the text
838     // dimensions should change, but don't actually change the number of
839     // (rows,columns).  These numbers may only change when Vim initiates the
840     // change (as opposed to the user dragging the window resizer, for
841     // example).
842     //
843     // Note that the message sent to Vim depends on whether we're in
844     // a live resize or not -- this is necessary to avoid the window jittering
845     // when the user drags to resize.
846     int constrained[2];
847     NSSize textViewSize = [textView frame].size;
848     [textView constrainRows:&constrained[0] columns:&constrained[1]
849                      toSize:textViewSize];
851     int rows, cols;
852     [textView getMaxRows:&rows columns:&cols];
854     if (constrained[0] != rows || constrained[1] != cols) {
855         NSData *data = [NSData dataWithBytes:constrained length:2*sizeof(int)];
856         int msgid = [self inLiveResize] ? LiveResizeMsgID
857                                         : SetTextDimensionsMsgID;
859         //NSLog(@"Notify Vim that text dimensions changed from %dx%d to %dx%d"
860         //       " (%s)", cols, rows, constrained[1], constrained[0],
861         //       MessageStrings[msgid]);
863         [vimController sendMessage:msgid data:data];
865         // We only want to set the window title if this resize came from
866         // a live-resize, not (for example) setting 'columns' or 'lines'.
867         if ([self inLiveResize]) {
868             [[self window] setTitle:[NSString stringWithFormat:@"%dx%d",
869                     constrained[1], constrained[0]]];
870         }
871     }
874 @end // MMVimView (Private)
879 @implementation MMScroller
881 - (id)initWithIdentifier:(long)ident type:(int)theType
883     // HACK! NSScroller creates a horizontal scroller if it is init'ed with a
884     // frame whose with exceeds its height; so create a bogus rect and pass it
885     // to initWithFrame.
886     NSRect frame = theType == MMScrollerTypeBottom
887             ? NSMakeRect(0, 0, 1, 0)
888             : NSMakeRect(0, 0, 0, 1);
890     self = [super initWithFrame:frame];
891     if (!self) return nil;
893     identifier = ident;
894     type = theType;
895     [self setHidden:YES];
896     [self setEnabled:YES];
897     [self setAutoresizingMask:NSViewNotSizable];
899     return self;
902 - (long)identifier
904     return identifier;
907 - (int)type
909     return type;
912 - (NSRange)range
914     return range;
917 - (void)setRange:(NSRange)newRange
919     range = newRange;
922 - (void)scrollWheel:(NSEvent *)event
924     // HACK! Pass message on to the text view.
925     NSView *vimView = [self superview];
926     if ([vimView isKindOfClass:[MMVimView class]])
927         [[(MMVimView*)vimView textView] scrollWheel:event];
930 - (void)mouseDown:(NSEvent *)event
932     // TODO: This is an ugly way of getting the connection to the backend.
933     NSConnection *connection = nil;
934     id wc = [[self window] windowController];
935     if ([wc isKindOfClass:[MMWindowController class]]) {
936         MMVimController *vc = [(MMWindowController*)wc vimController];
937         id proxy = [vc backendProxy];
938         connection = [(NSDistantObject*)proxy connectionForProxy];
939     }
941     // NOTE: The scroller goes into "event tracking mode" when the user clicks
942     // (and holds) the mouse button.  We have to manually add the backend
943     // connection to this mode while the mouse button is held, else DO messages
944     // from Vim will not be processed until the mouse button is released.
945     [connection addRequestMode:NSEventTrackingRunLoopMode];
946     [super mouseDown:event];
947     [connection removeRequestMode:NSEventTrackingRunLoopMode];
950 @end // MMScroller