Changed DiffText and Constant highlight groups
[MacVim/jjgod.git] / src / MacVim / MMVimView.m
bloba175f897a76b0768e155e67f06faf0aef11b2555
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 #import "MMVimView.h"
13 #import <PSMTabBarControl.h>
14 #import "MacVim.h"
15 #import "MMTextView.h"
16 #import "MMTextStorage.h"
17 #import "MMTypesetter.h"
18 #import "MMVimController.h"
20 #import "MMWindowController.h"  // needed by MMScroller. TODO: remove
22 // Scroller type; these must match SBAR_* in gui.h
23 enum {
24     MMScrollerTypeLeft = 0,
25     MMScrollerTypeRight,
26     MMScrollerTypeBottom
29 // TODO:  Move!
30 @interface NSTabView (MMExtras)
31 - (void)removeAllTabViewItems;
32 @end
35 @interface MMVimView (Private)
36 - (BOOL)bottomScrollbarVisible;
37 - (BOOL)leftScrollbarVisible;
38 - (BOOL)rightScrollbarVisible;
39 - (void)placeScrollbars;
40 @end
43 @implementation MMVimView
45 - (MMVimView *)initWithFrame:(NSRect)frame
46                vimController:(MMVimController *)controller {
47     if (![super initWithFrame:frame])
48         return nil;
49     
50     vimController = controller;
51     scrollbars = [[NSMutableArray alloc] init];
53     // Set up a complete text system.
54     textStorage = [[MMTextStorage alloc] init];
55     NSLayoutManager *lm = [[NSLayoutManager alloc] init];
56     NSTextContainer *tc = [[NSTextContainer alloc] initWithContainerSize:
57                     NSMakeSize(1.0e7,1.0e7)];
59     NSString *typesetterString = [[NSUserDefaults standardUserDefaults]
60             stringForKey:MMTypesetterKey];
61     if (![typesetterString isEqual:@"NSTypesetter"]) {
62         MMTypesetter *typesetter = [[MMTypesetter alloc] init];
63         [lm setTypesetter:typesetter];
64         [typesetter release];
65     } else {
66         // Only MMTypesetter supports different cell width multipliers.
67         [[NSUserDefaults standardUserDefaults]
68                 setFloat:1.0 forKey:MMCellWidthMultiplierKey];
69     }
71     [tc setWidthTracksTextView:NO];
72     [tc setHeightTracksTextView:NO];
73     [tc setLineFragmentPadding:0];
75     [textStorage addLayoutManager:lm];
76     [lm addTextContainer:tc];
78     textView = [[MMTextView alloc] initWithFrame:frame
79                                    textContainer:tc];
81     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
82     int left = [ud integerForKey:MMTextInsetLeftKey];
83     int top = [ud integerForKey:MMTextInsetTopKey];
84     [textView setTextContainerInset:NSMakeSize(left, top)];
86     [self addSubview:textView];
88     // The text storage retains the layout manager which in turn retains
89     // the text container.
90     [tc release];
91     [lm release];
92     
93     // Create the tab view (which is never visible, but the tab bar control
94     // needs it to function).
95     tabView = [[NSTabView alloc] initWithFrame:NSZeroRect];
97     // Create the tab bar control (which is responsible for actually
98     // drawing the tabline and tabs).
99     NSRect tabFrame = frame;
100     tabFrame.origin.y = NSMaxY(tabFrame) - 22;
101     tabFrame.size.height = 22;
102     tabBarControl = [[PSMTabBarControl alloc] initWithFrame:tabFrame];
104     [tabView setDelegate:tabBarControl];
106     [tabBarControl setTabView:tabView];
107     [tabBarControl setDelegate:self];
108     [tabBarControl setHidden:YES];
109     [tabBarControl setAutoresizingMask:NSViewWidthSizable|NSViewMinYMargin];
110     [tabBarControl setCellMinWidth:[ud integerForKey:MMTabMinWidthKey]];
111     [tabBarControl setCellMaxWidth:[ud integerForKey:MMTabMaxWidthKey]];
112     [tabBarControl setCellOptimumWidth:
113                                      [ud integerForKey:MMTabOptimumWidthKey]];
114     [tabBarControl setShowAddTabButton:YES];
115     [[tabBarControl addTabButton] setTarget:self];
116     [[tabBarControl addTabButton] setAction:@selector(addNewTab:)];
117     [tabBarControl setAllowsDragBetweenWindows:NO];
118     
119     [tabBarControl setPartnerView:[self textView]];
120     
121     [self addSubview:tabBarControl];
123     return self;
126 - (void)dealloc
128     [tabBarControl release];  tabBarControl = nil;
129     [tabView release];  tabView = nil;
130     [scrollbars release];  scrollbars = nil;
131     [textView release];  textView = nil;
132     [textStorage release];  textStorage = nil;
134     [super dealloc];
137 - (MMTextView *)textView
139     return textView;
142 - (MMTextStorage *)textStorage
144     return textStorage;
147 - (NSMutableArray *)scrollbars
149     return scrollbars;
152 - (BOOL)inLiveResize
154     return [textView inLiveResize];
157 - (PSMTabBarControl *)tabBarControl
159     return tabBarControl;
162 - (NSTabView *)tabView
164     return tabView;
168 - (void)cleanup
170     vimController = nil;
171     
172     // NOTE! There is a bug in PSMTabBarControl in that it retains the delegate
173     // (which is the MMWindowController) so reset the delegate here, otherwise
174     // the MMWindowController never gets released resulting in a pretty serious
175     // memory leak.
176     [tabView setDelegate:nil];
177     [tabBarControl setDelegate:nil];
178     [tabBarControl setTabView:nil];
179     [[self window] setDelegate:nil];
181     // NOTE! There is another bug in PSMTabBarControl where the control is not
182     // removed as an observer, so remove it here (else lots of evil nasty bugs
183     // will come and gnaw at your feet while you are sleeping).
184     [[NSNotificationCenter defaultCenter] removeObserver:tabBarControl];
185     
186     [tabBarControl removeFromSuperviewWithoutNeedingDisplay];
187     [textView removeFromSuperviewWithoutNeedingDisplay];
189     unsigned i, count = [scrollbars count];
190     for (i = 0; i < count; ++i) {
191         MMScroller *sb = [scrollbars objectAtIndex:i];
192         [sb removeFromSuperviewWithoutNeedingDisplay];
193     }
195     [tabView removeAllTabViewItems];
198 - (IBAction)addNewTab:(id)sender
200     // NOTE! This can get called a lot if the user holds down the key
201     // equivalent for this action, which causes the ports to fill up.  If we
202     // wait for the message to be sent then the app might become unresponsive.
203     [vimController sendMessage:AddNewTabMsgID data:nil];
206 - (void)updateTabsWithData:(NSData *)data
208     const void *p = [data bytes];
209     const void *end = p + [data length];
210     int tabIdx = 0;
212     // HACK!  Current tab is first in the message.  This way it is not
213     // necessary to guess which tab should be the selected one (this can be
214     // problematic for instance when new tabs are created).
215     int curtabIdx = *((int*)p);  p += sizeof(int);
217     NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
219     while (p < end) {
220         //int wincount = *((int*)p);  p += sizeof(int);
221         int length = *((int*)p);  p += sizeof(int);
223         NSString *label = [[NSString alloc]
224                 initWithBytesNoCopy:(void*)p
225                              length:length
226                            encoding:NSUTF8StringEncoding
227                        freeWhenDone:NO];
228         p += length;
230         // Set the label of the tab;  add a new tab when needed.
231         NSTabViewItem *tvi = [[self tabView] numberOfTabViewItems] <= tabIdx
232                 ? [self addNewTabViewItem]
233                 : [tabViewItems objectAtIndex:tabIdx];
235         [tvi setLabel:label];
237         [label release];
239         ++tabIdx;
240     }
242     // Remove unused tabs from the NSTabView.  Note that when a tab is closed
243     // the NSTabView will automatically select another tab, but we want Vim to
244     // take care of which tab to select so set the vimTaskSelectedTab flag to
245     // prevent the tab selection message to be passed on to the VimTask.
246     vimTaskSelectedTab = YES;
247     int i, count = [[self tabView] numberOfTabViewItems];
248     for (i = count-1; i >= tabIdx; --i) {
249         id tvi = [tabViewItems objectAtIndex:i];
250         //NSLog(@"Removing tab with index %d", i);
251         [[self tabView] removeTabViewItem:tvi];
252     }
253     vimTaskSelectedTab = NO;
255     [self selectTabWithIndex:curtabIdx];
258 - (void)selectTabWithIndex:(int)idx
260     //NSLog(@"%s%d", _cmd, idx);
262     NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
263     if (idx < 0 || idx >= [tabViewItems count]) {
264         NSLog(@"WARNING: No tab with index %d exists.", idx);
265         return;
266     }
268     // Do not try to select a tab if already selected.
269     NSTabViewItem *tvi = [tabViewItems objectAtIndex:idx];
270     if (tvi != [[self tabView] selectedTabViewItem]) {
271         vimTaskSelectedTab = YES;
272         [[self tabView] selectTabViewItem:tvi];
273         vimTaskSelectedTab = NO;
274     }
277 - (NSTabViewItem *)addNewTabViewItem
279     // NOTE!  A newly created tab is not by selected by default; the VimTask
280     // decides which tab should be selected at all times.  However, the AppKit
281     // will automatically select the first tab added to a tab view.
283     NSTabViewItem *tvi = [[NSTabViewItem alloc] initWithIdentifier:nil];
285     // NOTE: If this is the first tab it will be automatically selected.
286     vimTaskSelectedTab = YES;
287     [[self tabView] addTabViewItem:tvi];
288     vimTaskSelectedTab = NO;
290     [tvi release];
292     return tvi;
295 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
297     NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
298     return [tabViewItems indexOfObject:tvi];
301 - (BOOL)bottomScrollbarVisible
303     unsigned i, count = [scrollbars count];
304     for (i = 0; i < count; ++i) {
305         MMScroller *scroller = [scrollbars objectAtIndex:i];
306         if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden])
307             return YES;
308     }
310     return NO;
313 - (BOOL)leftScrollbarVisible
315     unsigned i, count = [scrollbars count];
316     for (i = 0; i < count; ++i) {
317         MMScroller *scroller = [scrollbars objectAtIndex:i];
318         if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden])
319             return YES;
320     }
322     return NO;
325 - (BOOL)rightScrollbarVisible
327     unsigned i, count = [scrollbars count];
328     for (i = 0; i < count; ++i) {
329         MMScroller *scroller = [scrollbars objectAtIndex:i];
330         if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden])
331             return YES;
332     }
334     return NO;
337 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
339     //NSLog(@"Create scroller %d of type %d", ident, type);
341     MMScroller *scroller = [[MMScroller alloc] initWithIdentifier:ident
342                                                              type:type];
343     [scroller setTarget:self];
344     [scroller setAction:@selector(scroll:)];
346     [self addSubview:scroller];
347     [[self scrollbars] addObject:scroller];
348     [scroller release];
351 - (void)destroyScrollbarWithIdentifier:(long)ident
353     //NSLog(@"Destroy scroller %d", ident);
355     unsigned idx = 0;
356     MMScroller *scroller = [self scrollbarForIdentifier:ident index:&idx];
357     if (scroller) {
358         [scroller removeFromSuperview];
359         [[self scrollbars] removeObjectAtIndex:idx];
361         if (![scroller isHidden]) {
362             // A visible scroller was removed, so the window must resize to
363             // fit.
364             //NSLog(@"Visible scroller %d was destroyed, resizing window.",
365             //        ident);
366             shouldUpdateWindowSize = YES;
367         }
368     }
371 - (void)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
373     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
374     if (!scroller) return;
376     BOOL wasVisible = ![scroller isHidden];
377     //NSLog(@"%s scroller %d (was %svisible)", visible ? "Show" : "Hide",
378     //      ident, wasVisible ? "" : "in");
379     [scroller setHidden:!visible];
381     if (wasVisible != visible) {
382         // A scroller was hidden or shown, so the window must resize to fit.
383         //NSLog(@"%s scroller %d and resize.", visible ? "Show" : "Hide",
384         //        ident);
385         shouldUpdateWindowSize = YES;
386     }
389 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
390                     identifier:(long)ident
392     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
393     //NSLog(@"Set thumb value %.2f proportion %.2f for scroller %d",
394     //        val, prop, ident);
395     [scroller setFloatValue:val knobProportion:prop];
396     [scroller setEnabled:prop != 1.f];
400 - (void)scroll:(id)sender
402     NSMutableData *data = [NSMutableData data];
403     long ident = [(MMScroller*)sender identifier];
404     int hitPart = [sender hitPart];
405     float value = [sender floatValue];
407     [data appendBytes:&ident length:sizeof(long)];
408     [data appendBytes:&hitPart length:sizeof(int)];
409     [data appendBytes:&value length:sizeof(float)];
411     [vimController sendMessage:ScrollbarEventMsgID data:data];
414 - (void)placeScrollbars
416     NSRect textViewFrame = [textView frame];
417     BOOL lsbVisible = [self leftScrollbarVisible];
419     // HACK!  Find the lowest left&right vertical scrollbars, as well as the
420     // rightmost horizontal scrollbar.  This hack continues further down.
421     //
422     // TODO!  Can there be no more than one horizontal scrollbar?  If so, the
423     // code can be simplified.
424     unsigned lowestLeftSbIdx = (unsigned)-1;
425     unsigned lowestRightSbIdx = (unsigned)-1;
426     unsigned rightmostSbIdx = (unsigned)-1;
427     unsigned rowMaxLeft = 0, rowMaxRight = 0, colMax = 0;
428     unsigned i, count = [scrollbars count];
429     for (i = 0; i < count; ++i) {
430         MMScroller *scroller = [scrollbars objectAtIndex:i];
431         if (![scroller isHidden]) {
432             NSRange range = [scroller range];
433             if ([scroller type] == MMScrollerTypeLeft
434                     && range.location >= rowMaxLeft) {
435                 rowMaxLeft = range.location;
436                 lowestLeftSbIdx = i;
437             } else if ([scroller type] == MMScrollerTypeRight
438                     && range.location >= rowMaxRight) {
439                 rowMaxRight = range.location;
440                 lowestRightSbIdx = i;
441             } else if ([scroller type] == MMScrollerTypeBottom
442                     && range.location >= colMax) {
443                 colMax = range.location;
444                 rightmostSbIdx = i;
445             }
446         }
447     }
449     // Place the scrollbars.
450     for (i = 0; i < count; ++i) {
451         MMScroller *scroller = [scrollbars objectAtIndex:i];
452         if ([scroller isHidden])
453             continue;
455         NSRect rect;
456         if ([scroller type] == MMScrollerTypeBottom) {
457             rect = [textStorage rectForColumnsInRange:[scroller range]];
458             rect.size.height = [NSScroller scrollerWidth];
459             if (lsbVisible)
460                 rect.origin.x += [NSScroller scrollerWidth];
462             // HACK!  Make sure the rightmost horizontal scrollbar covers the
463             // text view all the way to the right, otherwise it looks ugly when
464             // the user drags the window to resize.
465             if (i == rightmostSbIdx) {
466                 float w = NSMaxX(textViewFrame) - NSMaxX(rect);
467                 if (w > 0)
468                     rect.size.width += w;
469             }
471             // Make sure scrollbar rect is bounded by the text view frame.
472             if (rect.origin.x < textViewFrame.origin.x)
473                 rect.origin.x = textViewFrame.origin.x;
474             else if (rect.origin.x > NSMaxX(textViewFrame))
475                 rect.origin.x = NSMaxX(textViewFrame);
476             if (NSMaxX(rect) > NSMaxX(textViewFrame))
477                 rect.size.width -= NSMaxX(rect) - NSMaxX(textViewFrame);
478             if (rect.size.width < 0)
479                 rect.size.width = 0;
480         } else {
481             rect = [textStorage rectForRowsInRange:[scroller range]];
482             // Adjust for the fact that text layout is flipped.
483             rect.origin.y = NSMaxY(textViewFrame) - rect.origin.y
484                     - rect.size.height;
485             rect.size.width = [NSScroller scrollerWidth];
486             if ([scroller type] == MMScrollerTypeRight)
487                 rect.origin.x = NSMaxX(textViewFrame);
489             // HACK!  Make sure the lowest vertical scrollbar covers the text
490             // view all the way to the bottom.  This is done because Vim only
491             // makes the scrollbar cover the (vim-)window it is associated with
492             // and this means there is always an empty gap in the scrollbar
493             // region next to the command line.
494             // TODO!  Find a nicer way to do this.
495             if (i == lowestLeftSbIdx || i == lowestRightSbIdx) {
496                 float h = rect.origin.y + rect.size.height
497                           - textViewFrame.origin.y;
498                 if (rect.size.height < h) {
499                     rect.origin.y = textViewFrame.origin.y;
500                     rect.size.height = h;
501                 }
502             }
504             // Vertical scrollers must not cover the resize box in the
505             // bottom-right corner of the window.
506             if ([[self window] showsResizeIndicator]  // XXX: make this a flag
507                 && rect.origin.y < [NSScroller scrollerWidth]) {
508                 rect.size.height -= [NSScroller scrollerWidth] - rect.origin.y;
509                 rect.origin.y = [NSScroller scrollerWidth];
510             }
512             // Make sure scrollbar rect is bounded by the text view frame.
513             if (rect.origin.y < textViewFrame.origin.y) {
514                 rect.size.height -= textViewFrame.origin.y - rect.origin.y;
515                 rect.origin.y = textViewFrame.origin.y;
516             } else if (rect.origin.y > NSMaxY(textViewFrame))
517                 rect.origin.y = NSMaxY(textViewFrame);
518             if (NSMaxY(rect) > NSMaxY(textViewFrame))
519                 rect.size.height -= NSMaxY(rect) - NSMaxY(textViewFrame);
520             if (rect.size.height < 0)
521                 rect.size.height = 0;
522         }
524         //NSLog(@"set scroller #%d frame = %@", i, NSStringFromRect(rect));
525         NSRect oldRect = [scroller frame];
526         if (!NSEqualRects(oldRect, rect)) {
527             [scroller setFrame:rect];
528             // Clear behind the old scroller frame, or parts of the old
529             // scroller might still be visible after setFrame:.
530             [[[self window] contentView] setNeedsDisplayInRect:oldRect];
531             [scroller setNeedsDisplay:YES];
532         }
533     }
536 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx
538     unsigned i, count = [[self scrollbars] count];
539     for (i = 0; i < count; ++i) {
540         MMScroller *scroller = [[self scrollbars] objectAtIndex:i];
541         if ([scroller identifier] == ident) {
542             if (idx) *idx = i;
543             return scroller;
544         }
545     }
547     return nil;
550 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
552     [textStorage setDefaultColorsBackground:back foreground:fore];
553     [textView setBackgroundColor:back];
556 - (BOOL)shouldUpdateWindowSize
558     return shouldUpdateWindowSize;
561 - (void)setShouldUpdateWindowSize:(BOOL)b
563     shouldUpdateWindowSize = b;
566 - (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize
568     NSSize size = textViewSize;
570     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
571     int right = [ud integerForKey:MMTextInsetRightKey];
572     int bot = [ud integerForKey:MMTextInsetBottomKey];
574     size.width += [[self textView] textContainerOrigin].x + right;
575     size.height += [[self textView] textContainerOrigin].y + bot;
577     if (![[self tabBarControl] isHidden])
578         size.height += [[self tabBarControl] frame].size.height;
580     if ([self bottomScrollbarVisible])
581         size.height += [NSScroller scrollerWidth];
582     if ([self leftScrollbarVisible])
583         size.width += [NSScroller scrollerWidth];
584     if ([self rightScrollbarVisible])
585         size.width += [NSScroller scrollerWidth];
587     return size;
590 - (NSRect)textViewRectForContentSize:(NSSize)contentSize
592     NSRect rect = { 0, 0, contentSize.width, contentSize.height };
594     if (![[self tabBarControl] isHidden])
595         rect.size.height -= [[self tabBarControl] frame].size.height;
597     if ([self bottomScrollbarVisible]) {
598         rect.size.height -= [NSScroller scrollerWidth];
599         rect.origin.y += [NSScroller scrollerWidth];
600     }
601     if ([self leftScrollbarVisible]) {
602         rect.size.width -= [NSScroller scrollerWidth];
603         rect.origin.x += [NSScroller scrollerWidth];
604     }
605     if ([self rightScrollbarVisible])
606         rect.size.width -= [NSScroller scrollerWidth];
608     return rect;
611 - (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize
613     NSSize size = textViewSize;
615     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
616     int right = [ud integerForKey:MMTextInsetRightKey];
617     int bot = [ud integerForKey:MMTextInsetBottomKey];
619     size.width -= [[self textView] textContainerOrigin].x + right;
620     size.height -= [[self textView] textContainerOrigin].y + bot;
622     return size;
626 // -- PSMTabBarControl delegate ----------------------------------------------
629 - (BOOL)tabView:(NSTabView *)theTabView shouldSelectTabViewItem:
630     (NSTabViewItem *)tabViewItem
632     // NOTE: It would be reasonable to think that 'shouldSelect...' implies
633     // that this message only gets sent when the user clicks the tab.
634     // Unfortunately it is not so, which is why we need the
635     // 'vimTaskSelectedTab' flag.
636     //
637     // HACK!  The selection message should not be propagated to the VimTask if
638     // the VimTask selected the tab (e.g. as opposed the user clicking the
639     // tab).  The delegate method has no way of knowing who initiated the
640     // selection so a flag is set when the VimTask initiated the selection.
641     if (!vimTaskSelectedTab) {
642         // Propagate the selection message to the VimTask.
643         int idx = [self representedIndexOfTabViewItem:tabViewItem];
644         if (NSNotFound != idx) {
645             NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
646             [vimController sendMessage:SelectTabMsgID data:data];
647         }
648     }
650     // Unless Vim selected the tab, return NO, and let Vim decide if the tab
651     // should get selected or not.
652     return vimTaskSelectedTab;
655 - (BOOL)tabView:(NSTabView *)theTabView shouldCloseTabViewItem:
656         (NSTabViewItem *)tabViewItem
658     // HACK!  This method is only called when the user clicks the close button
659     // on the tab.  Instead of letting the tab bar close the tab, we return NO
660     // and pass a message on to Vim to let it handle the closing.
661     int idx = [self representedIndexOfTabViewItem:tabViewItem];
662     //NSLog(@"Closing tab with index %d", idx);
663     NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
664     [vimController sendMessage:CloseTabMsgID data:data];
666     return NO;
669 - (void)tabView:(NSTabView *)theTabView didDragTabViewItem:
670         (NSTabViewItem *)tabViewItem toIndex:(int)idx
672     NSMutableData *data = [NSMutableData data];
673     [data appendBytes:&idx length:sizeof(int)];
675     [vimController sendMessage:DraggedTabMsgID data:data];
678 @end
683 @implementation NSTabView (MMExtras)
685 - (void)removeAllTabViewItems
687     NSArray *existingItems = [self tabViewItems];
688     NSEnumerator *e = [existingItems objectEnumerator];
689     NSTabViewItem *item;
690     while (item = [e nextObject]){
691         [self removeTabViewItem:item];
692     }
695 @end // NSTabView (MMExtras)
700 @implementation MMScroller
702 - (id)initWithIdentifier:(long)ident type:(int)theType
704     // HACK! NSScroller creates a horizontal scroller if it is init'ed with a
705     // frame whose with exceeds its height; so create a bogus rect and pass it
706     // to initWithFrame.
707     NSRect frame = theType == MMScrollerTypeBottom
708             ? NSMakeRect(0, 0, 1, 0)
709             : NSMakeRect(0, 0, 0, 1);
711     if ((self = [super initWithFrame:frame])) {
712         identifier = ident;
713         type = theType;
714         [self setHidden:YES];
715         [self setEnabled:YES];
716     }
718     return self;
721 - (long)identifier
723     return identifier;
726 - (int)type
728     return type;
731 - (NSRange)range
733     return range;
736 - (void)setRange:(NSRange)newRange
738     range = newRange;
741 - (void)scrollWheel:(NSEvent *)event
743     // HACK! Pass message on to the text view.
744     MMWindowController *wc = [[self window] windowController];
745     [[wc textView] scrollWheel:event];
748 @end // MMScroller