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.
11 #define MM_USE_CUSTOM_TYPESETTER 1
13 #import "MMWindowController.h"
14 #import <PSMTabBarControl.h>
15 #import "MMTextView.h"
16 #import "MMTextStorage.h"
17 #import "MMVimController.h"
19 #import "MMAppController.h"
21 #if MM_USE_CUSTOM_TYPESETTER
22 # import "MMTypesetter.h"
26 // Scroller type; these must match SBAR_* in gui.h
28 MMScrollerTypeLeft = 0,
33 // NOTE! This value must match the actual position of the status line
34 // separator in VimWindow.nib.
35 static float StatusLineHeight = 16.0f;
39 @interface NSTabView (MMExtras)
40 - (void)removeAllTabViewItems;
45 @interface MMScroller : NSScroller {
50 - (id)initWithIdentifier:(long)ident type:(int)type;
54 - (void)setRange:(NSRange)newRange;
57 @interface MMWindowController (Private)
58 - (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize;
59 - (NSRect)textViewRectForContentSize:(NSSize)contentSize;
60 - (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize;
61 - (void)resizeWindowToFit:(id)sender;
62 - (NSRect)fitWindowToFrame:(NSRect)frame;
63 - (void)updateResizeIncrements;
64 - (NSTabViewItem *)addNewTabViewItem;
65 - (void)statusTimerFired:(NSTimer *)timer;
66 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi;
67 - (IBAction)vimMenuItemAction:(id)sender;
68 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx;
69 - (BOOL)bottomScrollbarVisible;
70 - (BOOL)leftScrollbarVisible;
71 - (BOOL)rightScrollbarVisible;
72 - (void)placeScrollbars;
73 - (void)scroll:(id)sender;
80 NSString *buildMenuItemDescriptor(NSMenu *menu, NSString *tail)
82 return menu ? buildMenuItemDescriptor([menu supermenu], [[menu title]
83 stringByAppendingString:tail])
87 NSMutableArray *buildMenuAddress(NSMenu *menu)
91 addr = buildMenuAddress([menu supermenu]);
92 [addr addObject:[menu title]];
94 addr = [NSMutableArray array];
102 @implementation MMWindowController
104 - (id)initWithVimController:(MMVimController *)controller
106 if ((self = [super initWithWindowNibName:@"VimWindow"])) {
107 vimController = controller;
108 scrollbars = [[NSMutableArray alloc] init];
110 // Setup a complete text system.
111 textStorage = [[MMTextStorage alloc] init];
112 NSLayoutManager *lm = [[NSLayoutManager alloc] init];
113 NSTextContainer *tc = [[NSTextContainer alloc] initWithContainerSize:
114 NSMakeSize(1.0e7,1.0e7)];
116 #if MM_USE_CUSTOM_TYPESETTER
117 MMTypesetter *typesetter = [[MMTypesetter alloc] init];
118 [lm setTypesetter:typesetter];
119 [typesetter release];
122 [tc setWidthTracksTextView:NO];
123 [tc setHeightTracksTextView:NO];
124 [tc setLineFragmentPadding:0];
126 [textStorage addLayoutManager:lm];
127 [lm addTextContainer:tc];
129 textView = [[MMTextView alloc] initWithFrame:NSZeroRect
132 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
133 int left = [ud integerForKey:MMTextInsetLeft];
134 int top = [ud integerForKey:MMTextInsetTop];
135 [textView setTextContainerInset:NSMakeSize(left, top)];
137 // The text storage retains the layout manager which in turn retains
138 // the text container.
148 //NSLog(@"%@ %s", [self className], _cmd);
150 // TODO: release tabBarControl and tabView?
154 [tabBarControl setDelegate:nil];
155 [[self window] setDelegate:nil];
157 [tabView removeAllTabViewItems];
159 [scrollbars release];
161 [textStorage release];
166 - (void)windowDidLoad
168 // Called after window nib file is loaded.
170 [tablineSeparator setHidden:NO];
171 [tabBarControl setHidden:YES];
173 // NOTE: Size to fit looks good, but not many tabs will fit and there are
174 // quite a few drawing bugs in this code, so it is disabled for now.
175 //[tabBarControl setSizeCellsToFit:YES];
177 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
178 [tabBarControl setCellMinWidth:[ud integerForKey:MMTabMinWidthKey]];
179 [tabBarControl setCellMaxWidth:[ud integerForKey:MMTabMaxWidthKey]];
180 [tabBarControl setCellOptimumWidth:[ud integerForKey:MMTabOptimumWidthKey]];
182 [tabBarControl setAllowsDragBetweenWindows:NO];
183 [tabBarControl setShowAddTabButton:YES];
184 [[tabBarControl addTabButton] setTarget:self];
185 [[tabBarControl addTabButton] setAction:@selector(addNewTab:)];
187 // HACK! remove any tabs present in the nib
188 [tabView removeAllTabViewItems];
190 // HACK! These observers used to be set in the designated init of
191 // MMVimController, but this occasionally caused exceptions from within the
192 // AppKit to be raised. The problem seemed related to the fact that the
193 // window got loaded 'too early'; adding the observers here seems to
194 // alleviate this problem.
195 [[NSNotificationCenter defaultCenter]
196 addObserver:vimController
197 selector:@selector(windowWillClose:)
198 name:NSWindowWillCloseNotification
199 object:[self window]];
200 [[NSNotificationCenter defaultCenter]
201 addObserver:vimController
202 selector:@selector(windowDidBecomeMain:)
203 name:NSWindowDidBecomeMainNotification
204 object:[self window]];
207 - (MMVimController *)vimController
209 return vimController;
212 - (MMTextView *)textView
217 - (MMTextStorage *)textStorage
224 [self addNewTabViewItem];
226 // NOTE! This flag is set once the entire text system is set up.
229 [self updateResizeIncrements];
230 [self resizeWindowToFit:self];
232 [[self window] makeKeyAndOrderFront:self];
234 BOOL statusOff = [[NSUserDefaults standardUserDefaults]
235 boolForKey:MMStatuslineOffKey];
236 [statusTextField setHidden:statusOff];
237 [statusSeparator setHidden:statusOff];
238 [self flashStatusText:@"Welcome to MacVim!"];
241 - (void)updateTabsWithData:(NSData *)data
243 const void *p = [data bytes];
244 const void *end = p + [data length];
247 // HACK! Current tab is first in the message. This way it is not
248 // necessary to guess which tab should be the selected one (this can be
249 // problematic for instance when new tabs are created).
250 int curtabIdx = *((int*)p); p += sizeof(int);
252 NSArray *tabViewItems = [tabBarControl representedTabViewItems];
255 //int wincount = *((int*)p); p += sizeof(int);
256 int length = *((int*)p); p += sizeof(int);
258 NSString *label = [[NSString alloc]
259 initWithBytesNoCopy:(void*)p
261 encoding:NSUTF8StringEncoding
265 // Set the label of the tab; add a new tab when needed.
266 NSTabViewItem *tvi = [tabView numberOfTabViewItems] <= tabIdx
267 ? [self addNewTabViewItem]
268 : [tabViewItems objectAtIndex:tabIdx];
270 [tvi setLabel:label];
277 // Remove unused tabs from the NSTabView. Note that when a tab is closed
278 // the NSTabView will automatically select another tab, but we want Vim to
279 // take care of which tab to select so set the vimTaskSelectedTab flag to
280 // prevent the tab selection message to be passed on to the VimTask.
281 vimTaskSelectedTab = YES;
282 int i, count = [tabView numberOfTabViewItems];
283 for (i = count-1; i >= tabIdx; --i) {
284 id tvi = [tabViewItems objectAtIndex:i];
285 //NSLog(@"Removing tab with index %d", i);
286 [tabView removeTabViewItem:tvi];
288 vimTaskSelectedTab = NO;
290 [self selectTabWithIndex:curtabIdx];
293 - (void)selectTabWithIndex:(int)idx
295 //NSLog(@"%s%d", _cmd, idx);
297 NSArray *tabViewItems = [tabBarControl representedTabViewItems];
298 if (idx < 0 || idx >= [tabViewItems count]) {
299 NSLog(@"WARNING: No tab with index %d exists.", idx);
303 // Do not try to select a tab if already selected.
304 NSTabViewItem *tvi = [tabViewItems objectAtIndex:idx];
305 if (tvi != [tabView selectedTabViewItem]) {
306 vimTaskSelectedTab = YES;
307 [tabView selectTabViewItem:tvi];
308 vimTaskSelectedTab = NO;
312 - (void)setTextDimensionsWithRows:(int)rows columns:(int)cols
314 //NSLog(@"setTextDimensionsWithRows:%d columns:%d", rows, cols);
316 [textStorage setMaxRows:rows columns:cols];
318 if (setupDone && ![textView inLiveResize])
319 shouldUpdateWindowSize = YES;
322 - (void)setStatusText:(NSString *)text
325 [statusTextField setStringValue:text];
327 [statusTextField setStringValue:@""];
330 - (void)flashStatusText:(NSString *)text
332 if ([[NSUserDefaults standardUserDefaults] boolForKey:MMStatuslineOffKey])
335 [self setStatusText:text];
338 [statusTimer invalidate];
339 [statusTimer release];
342 statusTimer = [[NSTimer scheduledTimerWithTimeInterval:3
344 selector:@selector(statusTimerFired:)
349 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
351 //NSLog(@"Create scroller %d of type %d", ident, type);
353 MMScroller *scroller = [[MMScroller alloc] initWithIdentifier:ident
355 [scroller setTarget:self];
356 [scroller setAction:@selector(scroll:)];
358 [[[self window] contentView] addSubview:scroller];
359 [scrollbars addObject:scroller];
363 - (void)destroyScrollbarWithIdentifier:(long)ident
365 //NSLog(@"Destroy scroller %d", ident);
368 MMScroller *scroller = [self scrollbarForIdentifier:ident index:&idx];
370 [scroller removeFromSuperview];
371 [scrollbars removeObjectAtIndex:idx];
373 if (![scroller isHidden]) {
374 // A visible scroller was removed, so the window must resize to
376 //NSLog(@"Visible scroller %d was destroyed, resizing window.",
378 shouldUpdateWindowSize = YES;
383 - (void)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
385 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
386 if (!scroller) return;
388 BOOL wasVisible = ![scroller isHidden];
389 //NSLog(@"%s scroller %d (was %svisible)", visible ? "Show" : "Hide",
390 // ident, wasVisible ? "" : "in");
391 [scroller setHidden:!visible];
393 if (wasVisible != visible) {
394 // A scroller was hidden or shown, so the window must resize to fit.
395 //NSLog(@"%s scroller %d and resize.", visible ? "Show" : "Hide",
397 shouldUpdateWindowSize = YES;
401 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
403 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
404 NSRange range = NSMakeRange(pos, len);
405 if (!NSEqualRanges(range, [scroller range])) {
406 //NSLog(@"Set range %@ for scroller %d",
407 // NSStringFromRange(range), ident);
408 [scroller setRange:range];
409 // TODO! Should only do this once per update.
410 [self placeScrollbars];
414 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
415 identifier:(long)ident
417 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
418 //NSLog(@"Set thumb value %.2f proportion %.2f for scroller %d",
419 // val, prop, ident);
420 [scroller setFloatValue:val knobProportion:prop];
423 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
425 [textStorage setDefaultColorsBackground:back foreground:fore];
426 [textView setBackgroundColor:back];
429 - (void)setFont:(NSFont *)font
431 [textStorage setFont:font];
432 [self updateResizeIncrements];
435 - (void)processCommandQueueDidFinish
437 if (shouldUpdateWindowSize) {
438 shouldUpdateWindowSize = NO;
439 [self resizeWindowToFit:self];
443 - (IBAction)addNewTab:(id)sender
445 // NOTE! This can get called a lot if the user holds down the key
446 // equivalent for this action, which causes the ports to fill up. If we
447 // wait for the message to be sent then the app might become unresponsive.
448 [vimController sendMessage:AddNewTabMsgID data:nil wait:NO];
451 - (IBAction)showTabBar:(id)sender
453 [tablineSeparator setHidden:YES];
454 [tabBarControl setHidden:NO];
457 shouldUpdateWindowSize = YES;
460 - (IBAction)hideTabBar:(id)sender
462 [tablineSeparator setHidden:NO];
463 [tabBarControl setHidden:YES];
466 shouldUpdateWindowSize = YES;
472 // -- PSMTabBarControl delegate ----------------------------------------------
475 - (void)tabView:(NSTabView *)theTabView didSelectTabViewItem:
476 (NSTabViewItem *)tabViewItem
478 // HACK! There seem to be a bug in NSTextView which results in the first
479 // responder not being set to the view of the tab item so it is done
481 [[self window] makeFirstResponder:[tabViewItem view]];
483 // HACK! The selection message should not be propagated to the VimTask if
484 // the VimTask selected the tab (e.g. as opposed the user clicking the
485 // tab). The delegate method has no way of knowing who initiated the
486 // selection so a flag is set when the VimTask initiated the selection.
487 if (!vimTaskSelectedTab) {
488 // Propagate the selection message to the VimTask.
489 int idx = [self representedIndexOfTabViewItem:tabViewItem];
490 if (NSNotFound != idx) {
491 NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
492 [vimController sendMessage:SelectTabMsgID data:data wait:YES];
497 - (BOOL)tabView:(NSTabView *)theTabView shouldCloseTabViewItem:
498 (NSTabViewItem *)tabViewItem
500 // HACK! This method is only called when the user clicks the close button
501 // on the tab. Instead of letting the tab bar close the tab, we return NO
502 // and pass a message on to Vim to let it handle the closing.
503 int idx = [self representedIndexOfTabViewItem:tabViewItem];
504 //NSLog(@"Closing tab with index %d", idx);
505 NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
506 [vimController sendMessage:CloseTabMsgID data:data wait:YES];
511 - (void)tabView:(NSTabView *)theTabView didDragTabViewItem:
512 (NSTabViewItem *)tabViewItem toIndex:(int)idx
514 NSMutableData *data = [NSMutableData data];
515 [data appendBytes:&idx length:sizeof(int)];
517 [vimController sendMessage:DraggedTabMsgID data:data wait:YES];
523 // -- NSWindow delegate ------------------------------------------------------
526 - (BOOL)windowShouldClose:(id)sender
528 [vimController sendMessage:VimShouldCloseMsgID data:nil wait:YES];
532 - (void)windowWillClose:(NSNotification *)notification
534 //NSLog(@"%@ %s", [self className], _cmd);
536 // NOTE! There is a bug in PSMTabBarControl in that it retains the delegate
537 // (which is the MMWindowController) so reset the delegate here, otherwise
538 // the MMWindowController never gets released resulting in a pretty serious
540 [tabBarControl setDelegate:nil];
543 - (void)windowDidResize:(id)sender
545 if (!setupDone) return;
549 - (NSRect)windowWillUseStandardFrame:(NSWindow *)win
550 defaultFrame:(NSRect)frame
552 // HACK! For some reason 'frame' is not always constrained to fit on the
553 // screen (e.g. it may overlap the menu bar), so first constrain it to the
554 // screen; otherwise the new frame we compute may be too large and this
555 // will mess up the display after the window resizes.
556 frame = [win constrainFrameRect:frame toScreen:[win screen]];
558 // HACK! If the top of 'frame' is lower than the current window frame,
559 // increase 'frame' so that their tops align. Really, 'frame' should
560 // already have its top at least as high as the current window frame, but
561 // for some reason this is not always the case.
562 // (See resizeWindowToFit: for a similar hack.)
563 NSRect cur = [win frame];
564 if (NSMaxY(cur) > NSMaxY(frame)) {
565 frame.size.height = cur.origin.y - frame.origin.y + cur.size.height;
568 frame = [self fitWindowToFrame:frame];
570 // Keep old width and horizontal position unless user clicked with the
571 // Command key is held down.
572 NSEvent *event = [NSApp currentEvent];
573 if (!([event type] == NSLeftMouseUp
574 && [event modifierFlags] & NSCommandKeyMask)) {
575 NSRect currentFrame = [win frame];
576 frame.size.width = currentFrame.size.width;
577 frame.origin.x = currentFrame.origin.x;
586 // -- Services menu delegate -------------------------------------------------
588 - (id)validRequestorForSendType:(NSString *)sendType
589 returnType:(NSString *)returnType
591 //NSLog(@"validRequestorForSendType:%@ returnType:%@", sendType, returnType);
593 id backendProxy = [vimController backendProxy];
595 if ((!sendType || [sendType isEqual:NSStringPboardType])
596 && (!returnType || [returnType isEqual:NSStringPboardType]))
598 if ((!sendType || [backendProxy starRegisterToPasteboard:nil]) &&
599 (!returnType || [backendProxy starRegisterFromPasteboard:nil]))
605 return [super validRequestorForSendType:sendType returnType:returnType];
608 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
609 types:(NSArray *)types
611 //NSLog(@"writeSelectionToPasteboard:%@ types:%@", pboard, types);
613 if (![types containsObject:NSStringPboardType])
616 id backendProxy = [vimController backendProxy];
617 return [backendProxy starRegisterToPasteboard:pboard];
620 @end // MMWindowController
624 @implementation MMWindowController (Private)
626 - (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize
628 NSSize size = textViewSize;
630 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
631 int right = [ud integerForKey:MMTextInsetRight];
632 int bot = [ud integerForKey:MMTextInsetBottom];
634 size.width += [textView textContainerOrigin].x + right;
635 size.height += [textView textContainerOrigin].y + bot;
637 // A one pixel high separator is shown if tabline is hidden.
638 if ([tabBarControl isHidden]) ++size.height;
639 else size.height += [tabBarControl frame].size.height;
641 if (![[NSUserDefaults standardUserDefaults] boolForKey:MMStatuslineOffKey])
642 size.height += StatusLineHeight;
644 if ([self bottomScrollbarVisible])
645 size.height += [NSScroller scrollerWidth];
646 if ([self leftScrollbarVisible])
647 size.width += [NSScroller scrollerWidth];
648 if ([self rightScrollbarVisible])
649 size.width += [NSScroller scrollerWidth];
654 - (NSRect)textViewRectForContentSize:(NSSize)contentSize
656 NSRect rect = { 0, 0, contentSize.width, contentSize.height };
658 // A one pixel high separator is shown if tabline is hidden.
659 if ([tabBarControl isHidden]) --rect.size.height;
660 else rect.size.height -= [tabBarControl frame].size.height;
662 if (![[NSUserDefaults standardUserDefaults]
663 boolForKey:MMStatuslineOffKey]) {
664 rect.size.height -= StatusLineHeight;
665 rect.origin.y += StatusLineHeight;
668 if ([self bottomScrollbarVisible]) {
669 rect.size.height -= [NSScroller scrollerWidth];
670 rect.origin.y += [NSScroller scrollerWidth];
672 if ([self leftScrollbarVisible]) {
673 rect.size.width -= [NSScroller scrollerWidth];
674 rect.origin.x += [NSScroller scrollerWidth];
676 if ([self rightScrollbarVisible])
677 rect.size.width -= [NSScroller scrollerWidth];
682 - (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize
684 NSSize size = textViewSize;
686 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
687 int right = [ud integerForKey:MMTextInsetRight];
688 int bot = [ud integerForKey:MMTextInsetBottom];
690 size.width -= [textView textContainerOrigin].x + right;
691 size.height -= [textView textContainerOrigin].y + bot;
696 - (void)resizeWindowToFit:(id)sender
698 if (!setupDone) return;
700 NSWindow *win = [self window];
701 NSRect frame = [win frame];
702 NSRect contentRect = [win contentRectForFrameRect:frame];
703 NSSize newSize = [self contentSizeForTextStorageSize:[textStorage size]];
705 // Keep top-left corner of the window fixed when resizing.
706 contentRect.origin.y -= newSize.height - contentRect.size.height;
707 contentRect.size = newSize;
709 frame = [win frameRectForContentRect:contentRect];
710 NSRect maxFrame = [win constrainFrameRect:frame toScreen:[win screen]];
712 // HACK! Assuming the window frame cannot already be placed too high,
713 // adjust 'maxFrame' so that it at least as high up as the current frame.
714 // The reason for doing this is that constrainFrameRect:toScreen: does not
715 // always seem to utilize as much area as possible.
716 if (NSMaxY(frame) > NSMaxY(maxFrame)) {
717 maxFrame.size.height = frame.origin.y - maxFrame.origin.y
721 if (!NSEqualRects(maxFrame, frame)) {
722 // The new window frame is too big to fit on the screen, so fit the
723 // text storage to the biggest frame which will fit on the screen.
724 //NSLog(@"Proposed window frame does not fit on the screen!");
725 frame = [self fitWindowToFrame:maxFrame];
728 //NSLog(@"%s %@", _cmd, NSStringFromRect(frame));
730 // HACK! If the window does resize, then windowDidResize is called which in
731 // turn calls placeViews. In case the computed new size of the window is
732 // no different from the current size, then we need to call placeViews
734 if (NSEqualRects(frame, [win frame])) {
737 [win setFrame:frame display:YES];
741 - (NSRect)fitWindowToFrame:(NSRect)frame
743 if (!setupDone) return frame;
745 NSWindow *win = [self window];
746 NSRect contentRect = [win contentRectForFrameRect:frame];
747 NSSize size = [self textViewRectForContentSize:contentRect.size].size;
748 size = [self textStorageSizeForTextViewSize:size];
749 size = [textStorage fitToSize:size];
750 size = [self contentSizeForTextStorageSize:size];
752 // Keep top-left corner of 'frame' fixed.
753 contentRect.origin.y -= size.height - contentRect.size.height;
754 contentRect.size = size;
756 return [win frameRectForContentRect:contentRect];
759 - (void)updateResizeIncrements
761 if (!setupDone) return;
763 NSSize size = [textStorage calculateAverageFontSize];
764 [[self window] setContentResizeIncrements:size];
767 - (NSTabViewItem *)addNewTabViewItem
769 // NOTE! A newly created tab is not by selected by default; the VimTask
770 // decides which tab should be selected at all times. However, the AppKit
771 // will automatically select the first tab added to a tab view.
773 NSTabViewItem *tvi = [[NSTabViewItem alloc] initWithIdentifier:nil];
774 [tvi setView:textView];
776 // BUG! This call seems to have no effect; see comment in
777 // tabView:didSelectTabViewItem:.
778 //[tvi setInitialFirstResponder:textView];
780 // NOTE: If this is the first tab it will be automatically selected.
781 vimTaskSelectedTab = YES;
782 [tabView addTabViewItem:tvi];
783 vimTaskSelectedTab = NO;
790 - (void)statusTimerFired:(NSTimer *)timer
792 [self setStatusText:@""];
793 [statusTimer release];
797 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
799 NSArray *tabViewItems = [tabBarControl representedTabViewItems];
800 return [tabViewItems indexOfObject:tvi];
803 - (IBAction)vimMenuItemAction:(id)sender
805 int tag = [sender tag];
807 NSMutableData *data = [NSMutableData data];
808 [data appendBytes:&tag length:sizeof(int)];
810 [vimController sendMessage:ExecuteMenuMsgID data:data wait:NO];
813 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx
815 unsigned i, count = [scrollbars count];
816 for (i = 0; i < count; ++i) {
817 MMScroller *scroller = [scrollbars objectAtIndex:i];
818 if ([scroller identifier] == ident) {
827 - (BOOL)bottomScrollbarVisible
829 unsigned i, count = [scrollbars count];
830 for (i = 0; i < count; ++i) {
831 MMScroller *scroller = [scrollbars objectAtIndex:i];
832 if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden])
839 - (BOOL)leftScrollbarVisible
841 unsigned i, count = [scrollbars count];
842 for (i = 0; i < count; ++i) {
843 MMScroller *scroller = [scrollbars objectAtIndex:i];
844 if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden])
851 - (BOOL)rightScrollbarVisible
853 unsigned i, count = [scrollbars count];
854 for (i = 0; i < count; ++i) {
855 MMScroller *scroller = [scrollbars objectAtIndex:i];
856 if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden])
863 - (void)placeScrollbars
865 if (!setupDone) return;
867 NSRect tabViewFrame = [tabView frame];
868 NSView *contentView = [[self window] contentView];
869 BOOL lsbVisible = [self leftScrollbarVisible];
870 BOOL statusVisible = ![[NSUserDefaults standardUserDefaults]
871 boolForKey:MMStatuslineOffKey];
873 // HACK! Find the lowest left&right vertical scrollbars, as well as the
874 // leftmost horizontal scrollbar. This hack continues further down.
876 // TODO! Can there be no more than one horizontal scrollbar? If so, the
877 // code can be simplified.
878 unsigned lowestLeftSbIdx = (unsigned)-1;
879 unsigned lowestRightSbIdx = (unsigned)-1;
880 unsigned leftmostSbIdx = (unsigned)-1;
881 unsigned rowMaxLeft = 0, rowMaxRight = 0, colMax = 0;
882 unsigned i, count = [scrollbars count];
883 for (i = 0; i < count; ++i) {
884 MMScroller *scroller = [scrollbars objectAtIndex:i];
885 if (![scroller isHidden]) {
886 NSRange range = [scroller range];
887 if ([scroller type] == MMScrollerTypeLeft
888 && range.location >= rowMaxLeft) {
889 rowMaxLeft = range.location;
891 } else if ([scroller type] == MMScrollerTypeRight
892 && range.location >= rowMaxRight) {
893 rowMaxRight = range.location;
894 lowestRightSbIdx = i;
895 } else if ([scroller type] == MMScrollerTypeBottom
896 && range.location >= colMax) {
897 colMax = range.location;
903 // Place the scrollbars.
904 for (i = 0; i < count; ++i) {
905 MMScroller *scroller = [scrollbars objectAtIndex:i];
906 if ([scroller isHidden])
910 if ([scroller type] == MMScrollerTypeBottom) {
911 rect = [textStorage rectForColumnsInRange:[scroller range]];
912 rect.size.height = [NSScroller scrollerWidth];
914 rect.origin.y += StatusLineHeight;
916 rect.origin.x += [NSScroller scrollerWidth];
918 // HACK! Make sure the leftmost horizontal scrollbar covers the
919 // text view all the way to the right, otherwise it looks ugly when
920 // the user drags the window to resize.
921 if (i == leftmostSbIdx) {
922 float w = NSMaxX(tabViewFrame) - NSMaxX(rect);
924 rect.size.width += w;
927 // Make sure scrollbar rect is bounded by the tab view frame.
928 if (rect.origin.x < tabViewFrame.origin.x)
929 rect.origin.x = tabViewFrame.origin.x;
930 else if (rect.origin.x > NSMaxX(tabViewFrame))
931 rect.origin.x = NSMaxX(tabViewFrame);
932 if (NSMaxX(rect) > NSMaxX(tabViewFrame))
933 rect.size.width -= NSMaxX(rect) - NSMaxX(tabViewFrame);
934 if (rect.size.width < 0)
937 rect = [textStorage rectForRowsInRange:[scroller range]];
938 // Adjust for the fact that text layout is flipped.
939 rect.origin.y = NSMaxY(tabViewFrame) - rect.origin.y
941 rect.size.width = [NSScroller scrollerWidth];
942 if ([scroller type] == MMScrollerTypeRight)
943 rect.origin.x = NSMaxX(tabViewFrame);
945 // HACK! Make sure the lowest vertical scrollbar covers the text
946 // view all the way to the bottom. This is done because Vim only
947 // makes the scrollbar cover the (vim-)window it is associated with
948 // and this means there is always an empty gap in the scrollbar
949 // region next to the command line.
950 // TODO! Find a nicer way to do this.
951 if (i == lowestLeftSbIdx || i == lowestRightSbIdx) {
952 float h = rect.origin.y + rect.size.height
953 - tabViewFrame.origin.y;
954 if (rect.size.height < h) {
955 rect.origin.y = tabViewFrame.origin.y;
956 rect.size.height = h;
960 // Vertical scrollers must not cover the resize box in the
961 // bottom-right corner of the window.
962 if (rect.origin.y < [NSScroller scrollerWidth]) {
963 rect.size.height -= [NSScroller scrollerWidth] - rect.origin.y;
964 rect.origin.y = [NSScroller scrollerWidth];
967 // Make sure scrollbar rect is bounded by the tab view frame.
968 if (rect.origin.y < tabViewFrame.origin.y) {
969 rect.size.height -= tabViewFrame.origin.y - rect.origin.y;
970 rect.origin.y = tabViewFrame.origin.y;
971 } else if (rect.origin.y > NSMaxY(tabViewFrame))
972 rect.origin.y = NSMaxY(tabViewFrame);
973 if (NSMaxY(rect) > NSMaxY(tabViewFrame))
974 rect.size.height -= NSMaxY(rect) - NSMaxY(tabViewFrame);
975 if (rect.size.height < 0)
976 rect.size.height = 0;
979 //NSLog(@"set scroller #%d frame = %@", i, NSStringFromRect(rect));
980 NSRect oldRect = [scroller frame];
981 if (!NSEqualRects(oldRect, rect)) {
982 [scroller setFrame:rect];
983 // Clear behind the old scroller frame, or parts of the old
984 // scroller might still be visible after setFrame:.
985 [contentView setNeedsDisplayInRect:oldRect];
986 [scroller setNeedsDisplay:YES];
991 - (void)scroll:(id)sender
993 NSMutableData *data = [NSMutableData data];
994 long ident = [(MMScroller*)sender identifier];
995 int hitPart = [sender hitPart];
996 float value = [sender floatValue];
998 [data appendBytes:&ident length:sizeof(long)];
999 [data appendBytes:&hitPart length:sizeof(int)];
1000 [data appendBytes:&value length:sizeof(float)];
1002 [vimController sendMessage:ScrollbarEventMsgID data:data wait:NO];
1007 if (!setupDone) return;
1009 // NOTE! It is assumed that the window has been resized so that it will
1010 // exactly fit the text storage (possibly after resizing it). If this is
1011 // not the case the display might be messed up.
1012 NSWindow *win = [self window];
1013 NSRect contentRect = [win contentRectForFrameRect:[win frame]];
1014 NSRect textViewRect = [self textViewRectForContentSize:contentRect.size];
1015 NSSize tsSize = [self textStorageSizeForTextViewSize:textViewRect.size];
1017 int dim[2], rows, cols;
1018 [textStorage getMaxRows:&rows columns:&cols];
1019 [textStorage fitToSize:tsSize rows:&dim[0] columns:&dim[1]];
1021 if (dim[0] != rows || dim[1] != cols) {
1022 NSString *sdim = [NSString stringWithFormat:@"%dx%d", dim[1], dim[0]];
1023 [self flashStatusText:sdim];
1025 //NSLog(@"Notify Vim that text storage dimensions changed to %dx%d",
1027 NSData *data = [NSData dataWithBytes:dim length:2*sizeof(int)];
1029 // NOTE! This can get called a lot when in live resize, which causes
1030 // the connection buffers to fill up. If we wait for the message to be
1031 // sent then the app might become unresponsive.
1032 [vimController sendMessage:SetTextDimensionsMsgID data:data
1033 wait:![textView inLiveResize]];
1036 [tabView setFrame:textViewRect];
1038 [self placeScrollbars];
1041 @end // MMWindowController (Private)
1045 @implementation NSTabView (MMExtras)
1047 - (void)removeAllTabViewItems
1049 NSArray *existingItems = [self tabViewItems];
1050 NSEnumerator *e = [existingItems objectEnumerator];
1051 NSTabViewItem *item;
1052 while (item = [e nextObject]){
1053 [self removeTabViewItem:item];
1057 @end // NSTabView (MMExtras)
1062 @implementation MMScroller
1064 - (id)initWithIdentifier:(long)ident type:(int)theType
1066 // HACK! NSScroller creates a horizontal scroller if it is init'ed with a
1067 // frame whose with exceeds its height; so create a bogus rect and pass it
1068 // to initWithFrame.
1069 NSRect frame = theType == MMScrollerTypeBottom
1070 ? NSMakeRect(0, 0, 1, 0)
1071 : NSMakeRect(0, 0, 0, 1);
1073 if ((self = [super initWithFrame:frame])) {
1076 [self setHidden:YES];
1077 [self setEnabled:YES];
1098 - (void)setRange:(NSRange)newRange