1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
3 * VIM - Vi IMproved by Bram Moolenaar
4 * MacVim GUI port by Bjorn Winckler
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.
13 #import <PSMTabBarControl.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
24 MMScrollerTypeLeft = 0,
30 @interface NSTabView (MMExtras)
31 - (void)removeAllTabViewItems;
35 @interface MMVimView (Private)
36 - (BOOL)bottomScrollbarVisible;
37 - (BOOL)leftScrollbarVisible;
38 - (BOOL)rightScrollbarVisible;
39 - (void)placeScrollbars;
43 @implementation MMVimView
45 - (MMVimView *)initWithFrame:(NSRect)frame
46 vimController:(MMVimController *)controller {
47 if (![super initWithFrame:frame])
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];
66 // Only MMTypesetter supports different cell width multipliers.
67 [[NSUserDefaults standardUserDefaults]
68 setFloat:1.0 forKey:MMCellWidthMultiplierKey];
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
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.
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];
119 [tabBarControl setPartnerView:[self textView]];
121 [self addSubview:tabBarControl];
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;
137 - (MMTextView *)textView
142 - (MMTextStorage *)textStorage
147 - (NSMutableArray *)scrollbars
154 return [textView inLiveResize];
157 - (PSMTabBarControl *)tabBarControl
159 return tabBarControl;
162 - (NSTabView *)tabView
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
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];
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];
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];
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];
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
226 encoding:NSUTF8StringEncoding
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];
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];
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);
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;
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;
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])
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])
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])
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
343 [scroller setTarget:self];
344 [scroller setAction:@selector(scroll:)];
346 [self addSubview:scroller];
347 [[self scrollbars] addObject:scroller];
351 - (void)destroyScrollbarWithIdentifier:(long)ident
353 //NSLog(@"Destroy scroller %d", ident);
356 MMScroller *scroller = [self scrollbarForIdentifier:ident index:&idx];
358 [scroller removeFromSuperview];
359 [[self scrollbars] removeObjectAtIndex:idx];
361 if (![scroller isHidden]) {
362 // A visible scroller was removed, so the window must resize to
364 //NSLog(@"Visible scroller %d was destroyed, resizing window.",
366 shouldUpdateWindowSize = YES;
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",
385 shouldUpdateWindowSize = YES;
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.
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;
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;
449 // Place the scrollbars.
450 for (i = 0; i < count; ++i) {
451 MMScroller *scroller = [scrollbars objectAtIndex:i];
452 if ([scroller isHidden])
456 if ([scroller type] == MMScrollerTypeBottom) {
457 rect = [textStorage rectForColumnsInRange:[scroller range]];
458 rect.size.height = [NSScroller scrollerWidth];
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);
468 rect.size.width += w;
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)
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
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;
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];
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;
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];
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) {
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];
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];
601 if ([self leftScrollbarVisible]) {
602 rect.size.width -= [NSScroller scrollerWidth];
603 rect.origin.x += [NSScroller scrollerWidth];
605 if ([self rightScrollbarVisible])
606 rect.size.width -= [NSScroller scrollerWidth];
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;
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.
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];
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];
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];
683 @implementation NSTabView (MMExtras)
685 - (void)removeAllTabViewItems
687 NSArray *existingItems = [self tabViewItems];
688 NSEnumerator *e = [existingItems objectEnumerator];
690 while (item = [e nextObject]){
691 [self removeTabViewItem:item];
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
707 NSRect frame = theType == MMScrollerTypeBottom
708 ? NSMakeRect(0, 0, 1, 0)
709 : NSMakeRect(0, 0, 0, 1);
711 if ((self = [super initWithFrame:frame])) {
714 [self setHidden:YES];
715 [self setEnabled:YES];
736 - (void)setRange:(NSRange)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];