New text drawing model, better unicode support, 'gfw' support, etc.
[MacVim.git] / src / MacVim / MMVimView.m
blobdca6f3cd193b24f49d308452cb541585bf23334d
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 text view.
14  */
16 #import "MMVimView.h"
18 #import <PSMTabBarControl.h>
19 #import "MacVim.h"
20 #import "MMTextView.h"
21 #import "MMTextStorage.h"
22 #import "MMTypesetter.h"
23 #import "MMVimController.h"
25 #import "MMWindowController.h"  // needed by MMScroller. TODO: remove
27 // Scroller type; these must match SBAR_* in gui.h
28 enum {
29     MMScrollerTypeLeft = 0,
30     MMScrollerTypeRight,
31     MMScrollerTypeBottom
34 // TODO:  Move!
35 @interface NSTabView (MMExtras)
36 - (void)removeAllTabViewItems;
37 @end
40 // TODO:  Move!
41 @interface MMScroller : NSScroller {
42     long identifier;
43     int type;
44     NSRange range;
46 - (id)initWithIdentifier:(long)ident type:(int)type;
47 - (long)identifier;
48 - (int)type;
49 - (NSRange)range;
50 - (void)setRange:(NSRange)newRange;
51 @end
54 @interface MMVimView (Private)
55 - (BOOL)bottomScrollbarVisible;
56 - (BOOL)leftScrollbarVisible;
57 - (BOOL)rightScrollbarVisible;
58 - (void)placeScrollbars;
59 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi;
60 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx;
61 - (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize;
62 - (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize;
63 - (NSTabView *)tabView;
64 @end
67 @implementation MMVimView
69 - (NSRect)tabBarFrameForFrame:(NSRect)frame
71     NSRect tabFrame = {
72         { 0, frame.size.height - 22 },
73         { frame.size.width, 22 }
74     };
75     return tabFrame;
78 - (MMVimView *)initWithFrame:(NSRect)frame
79                vimController:(MMVimController *)controller {
80     if (![super initWithFrame:frame])
81         return nil;
82     
83     vimController = controller;
84     scrollbars = [[NSMutableArray alloc] init];
86     // Set up a complete text system.
87     textStorage = [[MMTextStorage alloc] init];
88     NSLayoutManager *lm = [[NSLayoutManager alloc] init];
89     NSTextContainer *tc = [[NSTextContainer alloc] initWithContainerSize:
90                     NSMakeSize(1.0e7,1.0e7)];
92     NSString *typesetterString = [[NSUserDefaults standardUserDefaults]
93             stringForKey:MMTypesetterKey];
94     if ([typesetterString isEqual:@"MMTypesetter"]) {
95         NSTypesetter *typesetter = [[MMTypesetter alloc] init];
96         [lm setTypesetter:typesetter];
97         [typesetter release];
98     } else if ([typesetterString isEqual:@"MMTypesetter2"]) {
99         NSTypesetter *typesetter = [[MMTypesetter2 alloc] init];
100         [lm setTypesetter:typesetter];
101         [typesetter release];
102     } else {
103         // Only MMTypesetter supports different cell width multipliers.
104         [[NSUserDefaults standardUserDefaults]
105                 setFloat:1.0 forKey:MMCellWidthMultiplierKey];
106     }
108     // The characters in the text storage are in display order, so disable
109     // bidirectional text processing (this call is 10.4 only).
110     [[lm typesetter] setBidiProcessingEnabled:NO];
112     [tc setWidthTracksTextView:NO];
113     [tc setHeightTracksTextView:NO];
114     [tc setLineFragmentPadding:0];
116     [textStorage addLayoutManager:lm];
117     [lm addTextContainer:tc];
119     textView = [[MMTextView alloc] initWithFrame:frame
120                                    textContainer:tc];
122     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
123     int left = [ud integerForKey:MMTextInsetLeftKey];
124     int top = [ud integerForKey:MMTextInsetTopKey];
125     [textView setTextContainerInset:NSMakeSize(left, top)];
127     [self addSubview:textView];
129     // The text storage retains the layout manager which in turn retains
130     // the text container.
131     [tc release];
132     [lm release];
133     
134     // Create the tab view (which is never visible, but the tab bar control
135     // needs it to function).
136     tabView = [[NSTabView alloc] initWithFrame:NSZeroRect];
138     // Create the tab bar control (which is responsible for actually
139     // drawing the tabline and tabs).
140     NSRect tabFrame = [self tabBarFrameForFrame:frame];
141     tabBarControl = [[PSMTabBarControl alloc] initWithFrame:tabFrame];
143     [tabView setDelegate:tabBarControl];
145     [tabBarControl setTabView:tabView];
146     [tabBarControl setDelegate:self];
147     [tabBarControl setHidden:YES];
148     [tabBarControl setCellMinWidth:[ud integerForKey:MMTabMinWidthKey]];
149     [tabBarControl setCellMaxWidth:[ud integerForKey:MMTabMaxWidthKey]];
150     [tabBarControl setCellOptimumWidth:
151                                      [ud integerForKey:MMTabOptimumWidthKey]];
152     [tabBarControl setShowAddTabButton:YES];
153     [[tabBarControl addTabButton] setTarget:self];
154     [[tabBarControl addTabButton] setAction:@selector(addNewTab:)];
155     [tabBarControl setAllowsDragBetweenWindows:NO];
156     
157     [tabBarControl setPartnerView:[self textView]];
158     
159     // tab bar resizing only works if awakeFromNib is called (that's where
160     // the NSViewFrameDidChangeNotification callback is installed). Sounds like
161     // a PSMTabBarControl bug, let's live with it for now.
162     [tabBarControl awakeFromNib];
164     [self addSubview:tabBarControl];
166     [self setPostsFrameChangedNotifications:YES];
167     [[NSNotificationCenter defaultCenter]
168         addObserver:self selector:@selector(placeViews)
169                             name:NSViewFrameDidChangeNotification object:self];
171     return self;
174 - (void)dealloc
176     [tabBarControl release];  tabBarControl = nil;
177     [tabView release];  tabView = nil;
178     [scrollbars release];  scrollbars = nil;
179     [textView release];  textView = nil;
180     [textStorage release];  textStorage = nil;
182     [super dealloc];
185 - (MMTextView *)textView
187     return textView;
190 - (MMTextStorage *)textStorage
192     return textStorage;
195 - (NSMutableArray *)scrollbars
197     return scrollbars;
200 - (BOOL)inLiveResize
202     return [textView inLiveResize];
205 - (PSMTabBarControl *)tabBarControl
207     return tabBarControl;
210 - (NSTabView *)tabView
212     return tabView;
216 - (void)cleanup
218     vimController = nil;
219     
220     // NOTE! There is a bug in PSMTabBarControl in that it retains the delegate
221     // (which is the MMWindowController) so reset the delegate here, otherwise
222     // the MMWindowController never gets released resulting in a pretty serious
223     // memory leak.
224     [tabView setDelegate:nil];
225     [tabBarControl setDelegate:nil];
226     [tabBarControl setTabView:nil];
227     [[self window] setDelegate:nil];
229     // NOTE! There is another bug in PSMTabBarControl where the control is not
230     // removed as an observer, so remove it here (else lots of evil nasty bugs
231     // will come and gnaw at your feet while you are sleeping).
232     [[NSNotificationCenter defaultCenter] removeObserver:tabBarControl];
234     [[NSNotificationCenter defaultCenter] removeObserver:self];
235     
236     [tabBarControl removeFromSuperviewWithoutNeedingDisplay];
237     [textView removeFromSuperviewWithoutNeedingDisplay];
239     unsigned i, count = [scrollbars count];
240     for (i = 0; i < count; ++i) {
241         MMScroller *sb = [scrollbars objectAtIndex:i];
242         [sb removeFromSuperviewWithoutNeedingDisplay];
243     }
245     [tabView removeAllTabViewItems];
248 - (NSSize)desiredSizeForActualRowsAndColumns
250     return [self contentSizeForTextStorageSize:[[self textStorage] size]];
253 - (NSSize)getDesiredRows:(int *)r columns:(int *)c forSize:(NSSize)size
255   NSSize textViewSize = [self textViewRectForContentSize:size].size;
256   NSSize textStorageSize = [self textStorageSizeForTextViewSize:textViewSize];
257   NSSize newSize = [textStorage fitToSize:textStorageSize rows:r columns:c];
258   return [self contentSizeForTextStorageSize:newSize];
261 - (void)getActualRows:(int *)r columns:(int *)c
263     [textStorage getMaxRows:r columns:c];
266 - (void)setActualRows:(int)r columns:(int)c
268     [textStorage setMaxRows:r columns:c];
271 - (IBAction)addNewTab:(id)sender
273     // NOTE! This can get called a lot if the user holds down the key
274     // equivalent for this action, which causes the ports to fill up.  If we
275     // wait for the message to be sent then the app might become unresponsive.
276     [vimController sendMessage:AddNewTabMsgID data:nil];
279 - (void)updateTabsWithData:(NSData *)data
281     const void *p = [data bytes];
282     const void *end = p + [data length];
283     int tabIdx = 0;
285     // HACK!  Current tab is first in the message.  This way it is not
286     // necessary to guess which tab should be the selected one (this can be
287     // problematic for instance when new tabs are created).
288     int curtabIdx = *((int*)p);  p += sizeof(int);
290     NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
292     while (p < end) {
293         //int wincount = *((int*)p);  p += sizeof(int);
294         int length = *((int*)p);  p += sizeof(int);
296         NSString *label = [[NSString alloc]
297                 initWithBytesNoCopy:(void*)p
298                              length:length
299                            encoding:NSUTF8StringEncoding
300                        freeWhenDone:NO];
301         p += length;
303         // Set the label of the tab;  add a new tab when needed.
304         NSTabViewItem *tvi = [[self tabView] numberOfTabViewItems] <= tabIdx
305                 ? [self addNewTabViewItem]
306                 : [tabViewItems objectAtIndex:tabIdx];
308         [tvi setLabel:label];
310         [label release];
312         ++tabIdx;
313     }
315     // Remove unused tabs from the NSTabView.  Note that when a tab is closed
316     // the NSTabView will automatically select another tab, but we want Vim to
317     // take care of which tab to select so set the vimTaskSelectedTab flag to
318     // prevent the tab selection message to be passed on to the VimTask.
319     vimTaskSelectedTab = YES;
320     int i, count = [[self tabView] numberOfTabViewItems];
321     for (i = count-1; i >= tabIdx; --i) {
322         id tvi = [tabViewItems objectAtIndex:i];
323         //NSLog(@"Removing tab with index %d", i);
324         [[self tabView] removeTabViewItem:tvi];
325     }
326     vimTaskSelectedTab = NO;
328     [self selectTabWithIndex:curtabIdx];
331 - (void)selectTabWithIndex:(int)idx
333     //NSLog(@"%s%d", _cmd, idx);
335     NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
336     if (idx < 0 || idx >= [tabViewItems count]) {
337         NSLog(@"WARNING: No tab with index %d exists.", idx);
338         return;
339     }
341     // Do not try to select a tab if already selected.
342     NSTabViewItem *tvi = [tabViewItems objectAtIndex:idx];
343     if (tvi != [[self tabView] selectedTabViewItem]) {
344         vimTaskSelectedTab = YES;
345         [[self tabView] selectTabViewItem:tvi];
346         vimTaskSelectedTab = NO;
348         // we might need to change the scrollbars that are visible
349         [self placeScrollbars];
350     }
353 - (NSTabViewItem *)addNewTabViewItem
355     // NOTE!  A newly created tab is not by selected by default; the VimTask
356     // decides which tab should be selected at all times.  However, the AppKit
357     // will automatically select the first tab added to a tab view.
359     NSTabViewItem *tvi = [[NSTabViewItem alloc] initWithIdentifier:nil];
361     // NOTE: If this is the first tab it will be automatically selected.
362     vimTaskSelectedTab = YES;
363     [[self tabView] addTabViewItem:tvi];
364     vimTaskSelectedTab = NO;
366     [tvi release];
368     return tvi;
371 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
373     NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
374     return [tabViewItems indexOfObject:tvi];
377 - (BOOL)bottomScrollbarVisible
379     unsigned i, count = [scrollbars count];
380     for (i = 0; i < count; ++i) {
381         MMScroller *scroller = [scrollbars objectAtIndex:i];
382         if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden])
383             return YES;
384     }
386     return NO;
389 - (BOOL)leftScrollbarVisible
391     unsigned i, count = [scrollbars count];
392     for (i = 0; i < count; ++i) {
393         MMScroller *scroller = [scrollbars objectAtIndex:i];
394         if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden])
395             return YES;
396     }
398     return NO;
401 - (BOOL)rightScrollbarVisible
403     unsigned i, count = [scrollbars count];
404     for (i = 0; i < count; ++i) {
405         MMScroller *scroller = [scrollbars objectAtIndex:i];
406         if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden])
407             return YES;
408     }
410     return NO;
413 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
415     //NSLog(@"Create scroller %d of type %d", ident, type);
417     MMScroller *scroller = [[MMScroller alloc] initWithIdentifier:ident
418                                                              type:type];
419     [scroller setTarget:self];
420     [scroller setAction:@selector(scroll:)];
422     [self addSubview:scroller];
423     [[self scrollbars] addObject:scroller];
424     [scroller release];
427 - (void)destroyScrollbarWithIdentifier:(long)ident
429     //NSLog(@"Destroy scroller %d", ident);
431     unsigned idx = 0;
432     MMScroller *scroller = [self scrollbarForIdentifier:ident index:&idx];
433     if (scroller) {
434         [scroller removeFromSuperview];
435         [[self scrollbars] removeObjectAtIndex:idx];
437         if (![scroller isHidden]) {
438             // A visible scroller was removed, so the window must resize to
439             // fit.
440             //NSLog(@"Visible scroller %d was destroyed, resizing window.",
441             //        ident);
442             shouldUpdateWindowSize = YES;
443         }
444     }
447 - (void)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
449     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
450     if (!scroller) return;
452     BOOL wasVisible = ![scroller isHidden];
453     //NSLog(@"%s scroller %d (was %svisible)", visible ? "Show" : "Hide",
454     //      ident, wasVisible ? "" : "in");
455     [scroller setHidden:!visible];
457     if (wasVisible != visible) {
458         // A scroller was hidden or shown, so the window must resize to fit.
459         //NSLog(@"%s scroller %d and resize.", visible ? "Show" : "Hide",
460         //        ident);
461         shouldUpdateWindowSize = YES;
462     }
465 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
466                     identifier:(long)ident
468     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
469     //NSLog(@"Set thumb value %.2f proportion %.2f for scroller %d",
470     //        val, prop, ident);
471     [scroller setFloatValue:val knobProportion:prop];
472     [scroller setEnabled:prop != 1.f];
476 - (void)scroll:(id)sender
478     NSMutableData *data = [NSMutableData data];
479     long ident = [(MMScroller*)sender identifier];
480     int hitPart = [sender hitPart];
481     float value = [sender floatValue];
483     [data appendBytes:&ident length:sizeof(long)];
484     [data appendBytes:&hitPart length:sizeof(int)];
485     [data appendBytes:&value length:sizeof(float)];
487     [vimController sendMessage:ScrollbarEventMsgID data:data];
490 - (void)placeScrollbars
492     NSRect textViewFrame = [textView frame];
493     BOOL lsbVisible = [self leftScrollbarVisible];
495     // HACK!  Find the lowest left&right vertical scrollbars, as well as the
496     // rightmost horizontal scrollbar.  This hack continues further down.
497     //
498     // TODO!  Can there be no more than one horizontal scrollbar?  If so, the
499     // code can be simplified.
500     unsigned lowestLeftSbIdx = (unsigned)-1;
501     unsigned lowestRightSbIdx = (unsigned)-1;
502     unsigned rightmostSbIdx = (unsigned)-1;
503     unsigned rowMaxLeft = 0, rowMaxRight = 0, colMax = 0;
504     unsigned i, count = [scrollbars count];
505     for (i = 0; i < count; ++i) {
506         MMScroller *scroller = [scrollbars objectAtIndex:i];
507         if (![scroller isHidden]) {
508             NSRange range = [scroller range];
509             if ([scroller type] == MMScrollerTypeLeft
510                     && range.location >= rowMaxLeft) {
511                 rowMaxLeft = range.location;
512                 lowestLeftSbIdx = i;
513             } else if ([scroller type] == MMScrollerTypeRight
514                     && range.location >= rowMaxRight) {
515                 rowMaxRight = range.location;
516                 lowestRightSbIdx = i;
517             } else if ([scroller type] == MMScrollerTypeBottom
518                     && range.location >= colMax) {
519                 colMax = range.location;
520                 rightmostSbIdx = i;
521             }
522         }
523     }
525     // Place the scrollbars.
526     for (i = 0; i < count; ++i) {
527         MMScroller *scroller = [scrollbars objectAtIndex:i];
528         if ([scroller isHidden])
529             continue;
531         NSRect rect;
532         if ([scroller type] == MMScrollerTypeBottom) {
533             rect = [textStorage rectForColumnsInRange:[scroller range]];
534             rect.size.height = [NSScroller scrollerWidth];
535             if (lsbVisible)
536                 rect.origin.x += [NSScroller scrollerWidth];
538             // HACK!  Make sure the rightmost horizontal scrollbar covers the
539             // text view all the way to the right, otherwise it looks ugly when
540             // the user drags the window to resize.
541             if (i == rightmostSbIdx) {
542                 float w = NSMaxX(textViewFrame) - NSMaxX(rect);
543                 if (w > 0)
544                     rect.size.width += w;
545             }
547             // Make sure scrollbar rect is bounded by the text view frame.
548             if (rect.origin.x < textViewFrame.origin.x)
549                 rect.origin.x = textViewFrame.origin.x;
550             else if (rect.origin.x > NSMaxX(textViewFrame))
551                 rect.origin.x = NSMaxX(textViewFrame);
552             if (NSMaxX(rect) > NSMaxX(textViewFrame))
553                 rect.size.width -= NSMaxX(rect) - NSMaxX(textViewFrame);
554             if (rect.size.width < 0)
555                 rect.size.width = 0;
556         } else {
557             rect = [textStorage rectForRowsInRange:[scroller range]];
558             // Adjust for the fact that text layout is flipped.
559             rect.origin.y = NSMaxY(textViewFrame) - rect.origin.y
560                     - rect.size.height;
561             rect.size.width = [NSScroller scrollerWidth];
562             if ([scroller type] == MMScrollerTypeRight)
563                 rect.origin.x = NSMaxX(textViewFrame);
565             // HACK!  Make sure the lowest vertical scrollbar covers the text
566             // view all the way to the bottom.  This is done because Vim only
567             // makes the scrollbar cover the (vim-)window it is associated with
568             // and this means there is always an empty gap in the scrollbar
569             // region next to the command line.
570             // TODO!  Find a nicer way to do this.
571             if (i == lowestLeftSbIdx || i == lowestRightSbIdx) {
572                 float h = rect.origin.y + rect.size.height
573                           - textViewFrame.origin.y;
574                 if (rect.size.height < h) {
575                     rect.origin.y = textViewFrame.origin.y;
576                     rect.size.height = h;
577                 }
578             }
580             // Vertical scrollers must not cover the resize box in the
581             // bottom-right corner of the window.
582             if ([[self window] showsResizeIndicator]  // XXX: make this a flag
583                 && rect.origin.y < [NSScroller scrollerWidth]) {
584                 rect.size.height -= [NSScroller scrollerWidth] - rect.origin.y;
585                 rect.origin.y = [NSScroller scrollerWidth];
586             }
588             // Make sure scrollbar rect is bounded by the text view frame.
589             if (rect.origin.y < textViewFrame.origin.y) {
590                 rect.size.height -= textViewFrame.origin.y - rect.origin.y;
591                 rect.origin.y = textViewFrame.origin.y;
592             } else if (rect.origin.y > NSMaxY(textViewFrame))
593                 rect.origin.y = NSMaxY(textViewFrame);
594             if (NSMaxY(rect) > NSMaxY(textViewFrame))
595                 rect.size.height -= NSMaxY(rect) - NSMaxY(textViewFrame);
596             if (rect.size.height < 0)
597                 rect.size.height = 0;
598         }
600         //NSLog(@"set scroller #%d frame = %@", i, NSStringFromRect(rect));
601         NSRect oldRect = [scroller frame];
602         if (!NSEqualRects(oldRect, rect)) {
603             [scroller setFrame:rect];
604             // Clear behind the old scroller frame, or parts of the old
605             // scroller might still be visible after setFrame:.
606             [[[self window] contentView] setNeedsDisplayInRect:oldRect];
607             [scroller setNeedsDisplay:YES];
608         }
609     }
612 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx
614     unsigned i, count = [[self scrollbars] count];
615     for (i = 0; i < count; ++i) {
616         MMScroller *scroller = [[self scrollbars] objectAtIndex:i];
617         if ([scroller identifier] == ident) {
618             if (idx) *idx = i;
619             return scroller;
620         }
621     }
623     return nil;
626 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
628     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
629     NSRange range = NSMakeRange(pos, len);
630     if (!NSEqualRanges(range, [scroller range])) {
631         //NSLog(@"Set range %@ for scroller %d",
632         //        NSStringFromRange(range), ident);
633         [scroller setRange:range];
634         // TODO!  Should only do this once per update.
636         // This could be sent because a text window was created or closed, so
637         // we might need to update which scrollbars are visible.
638         [self placeScrollbars];
639     }
642 - (void)placeViews
644     NSRect textViewRect = [self textViewRectForContentSize:[self frame].size];
646     // Give all superfluous space to the text view. It might be smaller or
647     // larger than it wants to be, but this is needed during life resizing
648     [[self textView] setFrame:textViewRect];
650     // for some reason, autoresizing doesn't work...set tab size manually
651     [tabBarControl setFrame:[self tabBarFrameForFrame:[self frame]]];
653     [self placeScrollbars];
656 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
658     [textStorage setDefaultColorsBackground:back foreground:fore];
659     [textView setBackgroundColor:back];
662 - (BOOL)shouldUpdateWindowSize
664     return shouldUpdateWindowSize;
667 - (void)setShouldUpdateWindowSize:(BOOL)b
669     shouldUpdateWindowSize = b;
672 - (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize
674     NSSize size = textViewSize;
676     if (![[self tabBarControl] isHidden])
677         size.height += [[self tabBarControl] frame].size.height;
679     if ([self bottomScrollbarVisible])
680         size.height += [NSScroller scrollerWidth];
681     if ([self leftScrollbarVisible])
682         size.width += [NSScroller scrollerWidth];
683     if ([self rightScrollbarVisible])
684         size.width += [NSScroller scrollerWidth];
686     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
687     int right = [ud integerForKey:MMTextInsetRightKey];
688     int bot = [ud integerForKey:MMTextInsetBottomKey];
690     size.width += [[self textView] textContainerOrigin].x + right;
691     size.height += [[self textView] textContainerOrigin].y + bot;
693     return size;
696 - (NSRect)textViewRectForContentSize:(NSSize)contentSize
698     NSRect rect = { 0, 0, contentSize.width, contentSize.height };
700     if (![[self tabBarControl] isHidden])
701         rect.size.height -= [[self tabBarControl] frame].size.height;
703     if ([self bottomScrollbarVisible]) {
704         rect.size.height -= [NSScroller scrollerWidth];
705         rect.origin.y += [NSScroller scrollerWidth];
706     }
707     if ([self leftScrollbarVisible]) {
708         rect.size.width -= [NSScroller scrollerWidth];
709         rect.origin.x += [NSScroller scrollerWidth];
710     }
711     if ([self rightScrollbarVisible])
712         rect.size.width -= [NSScroller scrollerWidth];
714     return rect;
717 - (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize
719     NSSize size = textViewSize;
721     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
722     int right = [ud integerForKey:MMTextInsetRightKey];
723     int bot = [ud integerForKey:MMTextInsetBottomKey];
725     size.width -= [[self textView] textContainerOrigin].x + right;
726     size.height -= [[self textView] textContainerOrigin].y + bot;
728     return size;
732 // -- PSMTabBarControl delegate ----------------------------------------------
735 - (BOOL)tabView:(NSTabView *)theTabView shouldSelectTabViewItem:
736     (NSTabViewItem *)tabViewItem
738     // NOTE: It would be reasonable to think that 'shouldSelect...' implies
739     // that this message only gets sent when the user clicks the tab.
740     // Unfortunately it is not so, which is why we need the
741     // 'vimTaskSelectedTab' flag.
742     //
743     // HACK!  The selection message should not be propagated to the VimTask if
744     // the VimTask selected the tab (e.g. as opposed the user clicking the
745     // tab).  The delegate method has no way of knowing who initiated the
746     // selection so a flag is set when the VimTask initiated the selection.
747     if (!vimTaskSelectedTab) {
748         // Propagate the selection message to the VimTask.
749         int idx = [self representedIndexOfTabViewItem:tabViewItem];
750         if (NSNotFound != idx) {
751             NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
752             [vimController sendMessage:SelectTabMsgID data:data];
753         }
754     }
756     // Unless Vim selected the tab, return NO, and let Vim decide if the tab
757     // should get selected or not.
758     return vimTaskSelectedTab;
761 - (BOOL)tabView:(NSTabView *)theTabView shouldCloseTabViewItem:
762         (NSTabViewItem *)tabViewItem
764     // HACK!  This method is only called when the user clicks the close button
765     // on the tab.  Instead of letting the tab bar close the tab, we return NO
766     // and pass a message on to Vim to let it handle the closing.
767     int idx = [self representedIndexOfTabViewItem:tabViewItem];
768     //NSLog(@"Closing tab with index %d", idx);
769     NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
770     [vimController sendMessage:CloseTabMsgID data:data];
772     return NO;
775 - (void)tabView:(NSTabView *)theTabView didDragTabViewItem:
776         (NSTabViewItem *)tabViewItem toIndex:(int)idx
778     NSMutableData *data = [NSMutableData data];
779     [data appendBytes:&idx length:sizeof(int)];
781     [vimController sendMessage:DraggedTabMsgID data:data];
784 @end
789 @implementation NSTabView (MMExtras)
791 - (void)removeAllTabViewItems
793     NSArray *existingItems = [self tabViewItems];
794     NSEnumerator *e = [existingItems objectEnumerator];
795     NSTabViewItem *item;
796     while (item = [e nextObject]){
797         [self removeTabViewItem:item];
798     }
801 @end // NSTabView (MMExtras)
806 @implementation MMScroller
808 - (id)initWithIdentifier:(long)ident type:(int)theType
810     // HACK! NSScroller creates a horizontal scroller if it is init'ed with a
811     // frame whose with exceeds its height; so create a bogus rect and pass it
812     // to initWithFrame.
813     NSRect frame = theType == MMScrollerTypeBottom
814             ? NSMakeRect(0, 0, 1, 0)
815             : NSMakeRect(0, 0, 0, 1);
817     if ((self = [super initWithFrame:frame])) {
818         identifier = ident;
819         type = theType;
820         [self setHidden:YES];
821         [self setEnabled:YES];
822     }
824     return self;
827 - (long)identifier
829     return identifier;
832 - (int)type
834     return type;
837 - (NSRange)range
839     return range;
842 - (void)setRange:(NSRange)newRange
844     range = newRange;
847 - (void)scrollWheel:(NSEvent *)event
849     // HACK! Pass message on to the text view.
850     MMWindowController *wc = [[self window] windowController];
851     [[wc textView] scrollWheel:event];
854 @end // MMScroller