merge changes from upstream
[MacVim/jjgod.git] / src / MacVim / MMVimView.m
blob583ec34b5796a36dba455f4a4fc55e0d77208b87
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];
89     NSLog(@"creating MMTextView");
90     
91     textView = [[MMTextView alloc] initWithFrame:frame];
92     [textView setTextStorage:textStorage];
94     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
95     int left = [ud integerForKey:MMTextInsetLeftKey];
96     int top = [ud integerForKey:MMTextInsetTopKey];
97     [textView setTextContainerInset:NSMakeSize(left, top)];
99     [self addSubview:textView];
100     
101     // Create the tab view (which is never visible, but the tab bar control
102     // needs it to function).
103     tabView = [[NSTabView alloc] initWithFrame:NSZeroRect];
105     // Create the tab bar control (which is responsible for actually
106     // drawing the tabline and tabs).
107     NSRect tabFrame = [self tabBarFrameForFrame:frame];
108     tabBarControl = [[PSMTabBarControl alloc] initWithFrame:tabFrame];
110     [tabView setDelegate:tabBarControl];
112     [tabBarControl setTabView:tabView];
113     [tabBarControl setDelegate:self];
114     [tabBarControl setHidden:YES];
115     [tabBarControl setCellMinWidth:[ud integerForKey:MMTabMinWidthKey]];
116     [tabBarControl setCellMaxWidth:[ud integerForKey:MMTabMaxWidthKey]];
117     [tabBarControl setCellOptimumWidth:
118                                      [ud integerForKey:MMTabOptimumWidthKey]];
119     [tabBarControl setShowAddTabButton:YES];
120     [[tabBarControl addTabButton] setTarget:self];
121     [[tabBarControl addTabButton] setAction:@selector(addNewTab:)];
122     [tabBarControl setAllowsDragBetweenWindows:NO];
123     
124     [tabBarControl setPartnerView:[self textView]];
125     
126     // tab bar resizing only works if awakeFromNib is called (that's where
127     // the NSViewFrameDidChangeNotification callback is installed). Sounds like
128     // a PSMTabBarControl bug, let's live with it for now.
129     [tabBarControl awakeFromNib];
131     [self addSubview:tabBarControl];
133     [self setPostsFrameChangedNotifications:YES];
134     [[NSNotificationCenter defaultCenter]
135         addObserver:self selector:@selector(placeViews)
136                             name:NSViewFrameDidChangeNotification object:self];
138     NSLog(@"init done");
139     return self;
142 - (void)dealloc
144     [tabBarControl release];  tabBarControl = nil;
145     [tabView release];  tabView = nil;
146     [scrollbars release];  scrollbars = nil;
147     [textView release];  textView = nil;
148     [textStorage release];  textStorage = nil;
150     [super dealloc];
153 - (MMTextView *)textView
155     return textView;
158 - (MMTextStorage *)textStorage
160     return textStorage;
163 - (NSMutableArray *)scrollbars
165     return scrollbars;
168 - (BOOL)inLiveResize
170     return [textView inLiveResize];
173 - (PSMTabBarControl *)tabBarControl
175     return tabBarControl;
178 - (NSTabView *)tabView
180     return tabView;
184 - (void)cleanup
186     vimController = nil;
187     
188     // NOTE! There is a bug in PSMTabBarControl in that it retains the delegate
189     // (which is the MMWindowController) so reset the delegate here, otherwise
190     // the MMWindowController never gets released resulting in a pretty serious
191     // memory leak.
192     [tabView setDelegate:nil];
193     [tabBarControl setDelegate:nil];
194     [tabBarControl setTabView:nil];
195     [[self window] setDelegate:nil];
197     // NOTE! There is another bug in PSMTabBarControl where the control is not
198     // removed as an observer, so remove it here (else lots of evil nasty bugs
199     // will come and gnaw at your feet while you are sleeping).
200     [[NSNotificationCenter defaultCenter] removeObserver:tabBarControl];
202     [[NSNotificationCenter defaultCenter] removeObserver:self];
203     
204     [tabBarControl removeFromSuperviewWithoutNeedingDisplay];
205     [textView removeFromSuperviewWithoutNeedingDisplay];
207     unsigned i, count = [scrollbars count];
208     for (i = 0; i < count; ++i) {
209         MMScroller *sb = [scrollbars objectAtIndex:i];
210         [sb removeFromSuperviewWithoutNeedingDisplay];
211     }
213     [tabView removeAllTabViewItems];
216 - (NSSize)desiredSizeForActualRowsAndColumns
218     return [self contentSizeForTextStorageSize:[[self textStorage] size]];
221 - (NSSize)getDesiredRows:(int *)r columns:(int *)c forSize:(NSSize)size
223   NSSize textViewSize = [self textViewRectForContentSize:size].size;
224   NSSize textStorageSize = [self textStorageSizeForTextViewSize:textViewSize];
225   NSSize newSize = [textStorage fitToSize:textStorageSize rows:r columns:c];
226   return [self contentSizeForTextStorageSize:newSize];
229 - (void)getActualRows:(int *)r columns:(int *)c
231     [textStorage getMaxRows:r columns:c];
234 - (void)setActualRows:(int)r columns:(int)c
236     [textStorage setMaxRows:r columns:c];
239 - (IBAction)addNewTab:(id)sender
241     // NOTE! This can get called a lot if the user holds down the key
242     // equivalent for this action, which causes the ports to fill up.  If we
243     // wait for the message to be sent then the app might become unresponsive.
244     [vimController sendMessage:AddNewTabMsgID data:nil];
247 - (void)updateTabsWithData:(NSData *)data
249     const void *p = [data bytes];
250     const void *end = p + [data length];
251     int tabIdx = 0;
253     // HACK!  Current tab is first in the message.  This way it is not
254     // necessary to guess which tab should be the selected one (this can be
255     // problematic for instance when new tabs are created).
256     int curtabIdx = *((int*)p);  p += sizeof(int);
258     NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
260     while (p < end) {
261         //int wincount = *((int*)p);  p += sizeof(int);
262         int length = *((int*)p);  p += sizeof(int);
264         NSString *label = [[NSString alloc]
265                 initWithBytesNoCopy:(void*)p
266                              length:length
267                            encoding:NSUTF8StringEncoding
268                        freeWhenDone:NO];
269         p += length;
271         // Set the label of the tab;  add a new tab when needed.
272         NSTabViewItem *tvi = [[self tabView] numberOfTabViewItems] <= tabIdx
273                 ? [self addNewTabViewItem]
274                 : [tabViewItems objectAtIndex:tabIdx];
276         [tvi setLabel:label];
278         [label release];
280         ++tabIdx;
281     }
283     // Remove unused tabs from the NSTabView.  Note that when a tab is closed
284     // the NSTabView will automatically select another tab, but we want Vim to
285     // take care of which tab to select so set the vimTaskSelectedTab flag to
286     // prevent the tab selection message to be passed on to the VimTask.
287     vimTaskSelectedTab = YES;
288     int i, count = [[self tabView] numberOfTabViewItems];
289     for (i = count-1; i >= tabIdx; --i) {
290         id tvi = [tabViewItems objectAtIndex:i];
291         //NSLog(@"Removing tab with index %d", i);
292         [[self tabView] removeTabViewItem:tvi];
293     }
294     vimTaskSelectedTab = NO;
296     [self selectTabWithIndex:curtabIdx];
299 - (void)selectTabWithIndex:(int)idx
301     //NSLog(@"%s%d", _cmd, idx);
303     NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
304     if (idx < 0 || idx >= [tabViewItems count]) {
305         NSLog(@"WARNING: No tab with index %d exists.", idx);
306         return;
307     }
309     // Do not try to select a tab if already selected.
310     NSTabViewItem *tvi = [tabViewItems objectAtIndex:idx];
311     if (tvi != [[self tabView] selectedTabViewItem]) {
312         vimTaskSelectedTab = YES;
313         [[self tabView] selectTabViewItem:tvi];
314         vimTaskSelectedTab = NO;
316         // we might need to change the scrollbars that are visible
317         [self placeScrollbars];
318     }
321 - (NSTabViewItem *)addNewTabViewItem
323     // NOTE!  A newly created tab is not by selected by default; the VimTask
324     // decides which tab should be selected at all times.  However, the AppKit
325     // will automatically select the first tab added to a tab view.
327     NSTabViewItem *tvi = [[NSTabViewItem alloc] initWithIdentifier:nil];
329     // NOTE: If this is the first tab it will be automatically selected.
330     vimTaskSelectedTab = YES;
331     [[self tabView] addTabViewItem:tvi];
332     vimTaskSelectedTab = NO;
334     [tvi release];
336     return tvi;
339 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
341     NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
342     return [tabViewItems indexOfObject:tvi];
345 - (BOOL)bottomScrollbarVisible
347     unsigned i, count = [scrollbars count];
348     for (i = 0; i < count; ++i) {
349         MMScroller *scroller = [scrollbars objectAtIndex:i];
350         if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden])
351             return YES;
352     }
354     return NO;
357 - (BOOL)leftScrollbarVisible
359     unsigned i, count = [scrollbars count];
360     for (i = 0; i < count; ++i) {
361         MMScroller *scroller = [scrollbars objectAtIndex:i];
362         if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden])
363             return YES;
364     }
366     return NO;
369 - (BOOL)rightScrollbarVisible
371     unsigned i, count = [scrollbars count];
372     for (i = 0; i < count; ++i) {
373         MMScroller *scroller = [scrollbars objectAtIndex:i];
374         if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden])
375             return YES;
376     }
378     return NO;
381 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
383     //NSLog(@"Create scroller %d of type %d", ident, type);
385     MMScroller *scroller = [[MMScroller alloc] initWithIdentifier:ident
386                                                              type:type];
387     [scroller setTarget:self];
388     [scroller setAction:@selector(scroll:)];
390     [self addSubview:scroller];
391     [[self scrollbars] addObject:scroller];
392     [scroller release];
395 - (void)destroyScrollbarWithIdentifier:(long)ident
397     //NSLog(@"Destroy scroller %d", ident);
399     unsigned idx = 0;
400     MMScroller *scroller = [self scrollbarForIdentifier:ident index:&idx];
401     if (scroller) {
402         [scroller removeFromSuperview];
403         [[self scrollbars] removeObjectAtIndex:idx];
405         if (![scroller isHidden]) {
406             // A visible scroller was removed, so the window must resize to
407             // fit.
408             //NSLog(@"Visible scroller %d was destroyed, resizing window.",
409             //        ident);
410             shouldUpdateWindowSize = YES;
411         }
412     }
415 - (void)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
417     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
418     if (!scroller) return;
420     BOOL wasVisible = ![scroller isHidden];
421     //NSLog(@"%s scroller %d (was %svisible)", visible ? "Show" : "Hide",
422     //      ident, wasVisible ? "" : "in");
423     [scroller setHidden:!visible];
425     if (wasVisible != visible) {
426         // A scroller was hidden or shown, so the window must resize to fit.
427         //NSLog(@"%s scroller %d and resize.", visible ? "Show" : "Hide",
428         //        ident);
429         shouldUpdateWindowSize = YES;
430     }
433 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
434                     identifier:(long)ident
436     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
437     //NSLog(@"Set thumb value %.2f proportion %.2f for scroller %d",
438     //        val, prop, ident);
439     [scroller setFloatValue:val knobProportion:prop];
440     [scroller setEnabled:prop != 1.f];
444 - (void)scroll:(id)sender
446     NSMutableData *data = [NSMutableData data];
447     long ident = [(MMScroller*)sender identifier];
448     int hitPart = [sender hitPart];
449     float value = [sender floatValue];
451     [data appendBytes:&ident length:sizeof(long)];
452     [data appendBytes:&hitPart length:sizeof(int)];
453     [data appendBytes:&value length:sizeof(float)];
455     [vimController sendMessage:ScrollbarEventMsgID data:data];
458 - (void)placeScrollbars
460     NSRect textViewFrame = [textView frame];
461     BOOL lsbVisible = [self leftScrollbarVisible];
463     // HACK!  Find the lowest left&right vertical scrollbars, as well as the
464     // rightmost horizontal scrollbar.  This hack continues further down.
465     //
466     // TODO!  Can there be no more than one horizontal scrollbar?  If so, the
467     // code can be simplified.
468     unsigned lowestLeftSbIdx = (unsigned)-1;
469     unsigned lowestRightSbIdx = (unsigned)-1;
470     unsigned rightmostSbIdx = (unsigned)-1;
471     unsigned rowMaxLeft = 0, rowMaxRight = 0, colMax = 0;
472     unsigned i, count = [scrollbars count];
473     for (i = 0; i < count; ++i) {
474         MMScroller *scroller = [scrollbars objectAtIndex:i];
475         if (![scroller isHidden]) {
476             NSRange range = [scroller range];
477             if ([scroller type] == MMScrollerTypeLeft
478                     && range.location >= rowMaxLeft) {
479                 rowMaxLeft = range.location;
480                 lowestLeftSbIdx = i;
481             } else if ([scroller type] == MMScrollerTypeRight
482                     && range.location >= rowMaxRight) {
483                 rowMaxRight = range.location;
484                 lowestRightSbIdx = i;
485             } else if ([scroller type] == MMScrollerTypeBottom
486                     && range.location >= colMax) {
487                 colMax = range.location;
488                 rightmostSbIdx = i;
489             }
490         }
491     }
493     // Place the scrollbars.
494     for (i = 0; i < count; ++i) {
495         MMScroller *scroller = [scrollbars objectAtIndex:i];
496         if ([scroller isHidden])
497             continue;
499         NSRect rect;
500         if ([scroller type] == MMScrollerTypeBottom) {
501             rect = [textStorage rectForColumnsInRange:[scroller range]];
502             rect.size.height = [NSScroller scrollerWidth];
503             if (lsbVisible)
504                 rect.origin.x += [NSScroller scrollerWidth];
506             // HACK!  Make sure the rightmost horizontal scrollbar covers the
507             // text view all the way to the right, otherwise it looks ugly when
508             // the user drags the window to resize.
509             if (i == rightmostSbIdx) {
510                 float w = NSMaxX(textViewFrame) - NSMaxX(rect);
511                 if (w > 0)
512                     rect.size.width += w;
513             }
515             // Make sure scrollbar rect is bounded by the text view frame.
516             if (rect.origin.x < textViewFrame.origin.x)
517                 rect.origin.x = textViewFrame.origin.x;
518             else if (rect.origin.x > NSMaxX(textViewFrame))
519                 rect.origin.x = NSMaxX(textViewFrame);
520             if (NSMaxX(rect) > NSMaxX(textViewFrame))
521                 rect.size.width -= NSMaxX(rect) - NSMaxX(textViewFrame);
522             if (rect.size.width < 0)
523                 rect.size.width = 0;
524         } else {
525             rect = [textStorage rectForRowsInRange:[scroller range]];
526             // Adjust for the fact that text layout is flipped.
527             rect.origin.y = NSMaxY(textViewFrame) - rect.origin.y
528                     - rect.size.height;
529             rect.size.width = [NSScroller scrollerWidth];
530             if ([scroller type] == MMScrollerTypeRight)
531                 rect.origin.x = NSMaxX(textViewFrame);
533             // HACK!  Make sure the lowest vertical scrollbar covers the text
534             // view all the way to the bottom.  This is done because Vim only
535             // makes the scrollbar cover the (vim-)window it is associated with
536             // and this means there is always an empty gap in the scrollbar
537             // region next to the command line.
538             // TODO!  Find a nicer way to do this.
539             if (i == lowestLeftSbIdx || i == lowestRightSbIdx) {
540                 float h = rect.origin.y + rect.size.height
541                           - textViewFrame.origin.y;
542                 if (rect.size.height < h) {
543                     rect.origin.y = textViewFrame.origin.y;
544                     rect.size.height = h;
545                 }
546             }
548             // Vertical scrollers must not cover the resize box in the
549             // bottom-right corner of the window.
550             if ([[self window] showsResizeIndicator]  // XXX: make this a flag
551                 && rect.origin.y < [NSScroller scrollerWidth]) {
552                 rect.size.height -= [NSScroller scrollerWidth] - rect.origin.y;
553                 rect.origin.y = [NSScroller scrollerWidth];
554             }
556             // Make sure scrollbar rect is bounded by the text view frame.
557             if (rect.origin.y < textViewFrame.origin.y) {
558                 rect.size.height -= textViewFrame.origin.y - rect.origin.y;
559                 rect.origin.y = textViewFrame.origin.y;
560             } else if (rect.origin.y > NSMaxY(textViewFrame))
561                 rect.origin.y = NSMaxY(textViewFrame);
562             if (NSMaxY(rect) > NSMaxY(textViewFrame))
563                 rect.size.height -= NSMaxY(rect) - NSMaxY(textViewFrame);
564             if (rect.size.height < 0)
565                 rect.size.height = 0;
566         }
568         //NSLog(@"set scroller #%d frame = %@", i, NSStringFromRect(rect));
569         NSRect oldRect = [scroller frame];
570         if (!NSEqualRects(oldRect, rect)) {
571             [scroller setFrame:rect];
572             // Clear behind the old scroller frame, or parts of the old
573             // scroller might still be visible after setFrame:.
574             [[[self window] contentView] setNeedsDisplayInRect:oldRect];
575             [scroller setNeedsDisplay:YES];
576         }
577     }
580 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx
582     unsigned i, count = [[self scrollbars] count];
583     for (i = 0; i < count; ++i) {
584         MMScroller *scroller = [[self scrollbars] objectAtIndex:i];
585         if ([scroller identifier] == ident) {
586             if (idx) *idx = i;
587             return scroller;
588         }
589     }
591     return nil;
594 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
596     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
597     NSRange range = NSMakeRange(pos, len);
598     if (!NSEqualRanges(range, [scroller range])) {
599         //NSLog(@"Set range %@ for scroller %d",
600         //        NSStringFromRange(range), ident);
601         [scroller setRange:range];
602         // TODO!  Should only do this once per update.
604         // This could be sent because a text window was created or closed, so
605         // we might need to update which scrollbars are visible.
606         [self placeScrollbars];
607     }
610 - (void)placeViews
612     NSRect textViewRect = [self textViewRectForContentSize:[self frame].size];
614     // Give all superfluous space to the text view. It might be smaller or
615     // larger than it wants to be, but this is needed during life resizing
616     [[self textView] setFrame:textViewRect];
618     // for some reason, autoresizing doesn't work...set tab size manually
619     [tabBarControl setFrame:[self tabBarFrameForFrame:[self frame]]];
621     [self placeScrollbars];
624 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
626     [textStorage setDefaultColorsBackground:back foreground:fore];
627     [textView setBackgroundColor:back];
630 - (BOOL)shouldUpdateWindowSize
632     return shouldUpdateWindowSize;
635 - (void)setShouldUpdateWindowSize:(BOOL)b
637     shouldUpdateWindowSize = b;
640 - (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize
642     NSSize size = textViewSize;
644     if (![[self tabBarControl] isHidden])
645         size.height += [[self tabBarControl] frame].size.height;
647     if ([self bottomScrollbarVisible])
648         size.height += [NSScroller scrollerWidth];
649     if ([self leftScrollbarVisible])
650         size.width += [NSScroller scrollerWidth];
651     if ([self rightScrollbarVisible])
652         size.width += [NSScroller scrollerWidth];
654     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
655     int right = [ud integerForKey:MMTextInsetRightKey];
656     int bot = [ud integerForKey:MMTextInsetBottomKey];
658     size.width += [[self textView] textContainerOrigin].x + right;
659     size.height += [[self textView] textContainerOrigin].y + bot;
661     return size;
664 - (NSRect)textViewRectForContentSize:(NSSize)contentSize
666     NSRect rect = { 0, 0, contentSize.width, contentSize.height };
668     if (![[self tabBarControl] isHidden])
669         rect.size.height -= [[self tabBarControl] frame].size.height;
671     if ([self bottomScrollbarVisible]) {
672         rect.size.height -= [NSScroller scrollerWidth];
673         rect.origin.y += [NSScroller scrollerWidth];
674     }
675     if ([self leftScrollbarVisible]) {
676         rect.size.width -= [NSScroller scrollerWidth];
677         rect.origin.x += [NSScroller scrollerWidth];
678     }
679     if ([self rightScrollbarVisible])
680         rect.size.width -= [NSScroller scrollerWidth];
682     return rect;
685 - (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize
687     NSSize size = textViewSize;
689     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
690     int right = [ud integerForKey:MMTextInsetRightKey];
691     int bot = [ud integerForKey:MMTextInsetBottomKey];
693     size.width -= [[self textView] textContainerOrigin].x + right;
694     size.height -= [[self textView] textContainerOrigin].y + bot;
696     return size;
700 // -- PSMTabBarControl delegate ----------------------------------------------
703 - (BOOL)tabView:(NSTabView *)theTabView shouldSelectTabViewItem:
704     (NSTabViewItem *)tabViewItem
706     // NOTE: It would be reasonable to think that 'shouldSelect...' implies
707     // that this message only gets sent when the user clicks the tab.
708     // Unfortunately it is not so, which is why we need the
709     // 'vimTaskSelectedTab' flag.
710     //
711     // HACK!  The selection message should not be propagated to the VimTask if
712     // the VimTask selected the tab (e.g. as opposed the user clicking the
713     // tab).  The delegate method has no way of knowing who initiated the
714     // selection so a flag is set when the VimTask initiated the selection.
715     if (!vimTaskSelectedTab) {
716         // Propagate the selection message to the VimTask.
717         int idx = [self representedIndexOfTabViewItem:tabViewItem];
718         if (NSNotFound != idx) {
719             NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
720             [vimController sendMessage:SelectTabMsgID data:data];
721         }
722     }
724     // Unless Vim selected the tab, return NO, and let Vim decide if the tab
725     // should get selected or not.
726     return vimTaskSelectedTab;
729 - (BOOL)tabView:(NSTabView *)theTabView shouldCloseTabViewItem:
730         (NSTabViewItem *)tabViewItem
732     // HACK!  This method is only called when the user clicks the close button
733     // on the tab.  Instead of letting the tab bar close the tab, we return NO
734     // and pass a message on to Vim to let it handle the closing.
735     int idx = [self representedIndexOfTabViewItem:tabViewItem];
736     //NSLog(@"Closing tab with index %d", idx);
737     NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
738     [vimController sendMessage:CloseTabMsgID data:data];
740     return NO;
743 - (void)tabView:(NSTabView *)theTabView didDragTabViewItem:
744         (NSTabViewItem *)tabViewItem toIndex:(int)idx
746     NSMutableData *data = [NSMutableData data];
747     [data appendBytes:&idx length:sizeof(int)];
749     [vimController sendMessage:DraggedTabMsgID data:data];
752 @end
757 @implementation NSTabView (MMExtras)
759 - (void)removeAllTabViewItems
761     NSArray *existingItems = [self tabViewItems];
762     NSEnumerator *e = [existingItems objectEnumerator];
763     NSTabViewItem *item;
764     while (item = [e nextObject]){
765         [self removeTabViewItem:item];
766     }
769 @end // NSTabView (MMExtras)
774 @implementation MMScroller
776 - (id)initWithIdentifier:(long)ident type:(int)theType
778     // HACK! NSScroller creates a horizontal scroller if it is init'ed with a
779     // frame whose with exceeds its height; so create a bogus rect and pass it
780     // to initWithFrame.
781     NSRect frame = theType == MMScrollerTypeBottom
782             ? NSMakeRect(0, 0, 1, 0)
783             : NSMakeRect(0, 0, 0, 1);
785     if ((self = [super initWithFrame:frame])) {
786         identifier = ident;
787         type = theType;
788         [self setHidden:YES];
789         [self setEnabled:YES];
790     }
792     return self;
795 - (long)identifier
797     return identifier;
800 - (int)type
802     return type;
805 - (NSRange)range
807     return range;
810 - (void)setRange:(NSRange)newRange
812     range = newRange;
815 - (void)scrollWheel:(NSEvent *)event
817     // HACK! Pass message on to the text view.
818     MMWindowController *wc = [[self window] windowController];
819     [[wc textView] scrollWheel:event];
822 @end // MMScroller