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 #import "MMWindowController.h"
12 #import <PSMTabBarControl.h>
13 #import "MMTextView.h"
14 #import "MMTextStorage.h"
15 #import "MMVimController.h"
17 #import "MMAppController.h"
18 #import "MMTypesetter.h"
21 // Scroller type; these must match SBAR_* in gui.h
23 MMScrollerTypeLeft = 0,
28 // NOTE! This value must match the actual position of the status line
29 // separator in VimWindow.nib.
30 static float StatusLineHeight = 16.0f;
34 @interface NSTabView (MMExtras)
35 - (void)removeAllTabViewItems;
40 @interface MMScroller : NSScroller {
45 - (id)initWithIdentifier:(long)ident type:(int)type;
49 - (void)setRange:(NSRange)newRange;
52 @interface MMWindowController (Private)
53 - (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize;
54 - (NSRect)textViewRectForContentSize:(NSSize)contentSize;
55 - (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize;
56 - (void)resizeWindowToFit:(id)sender;
57 - (NSRect)fitWindowToFrame:(NSRect)frame;
58 - (void)updateResizeIncrements;
59 - (NSTabViewItem *)addNewTabViewItem;
60 - (void)statusTimerFired:(NSTimer *)timer;
61 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi;
62 - (IBAction)vimMenuItemAction:(id)sender;
63 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx;
64 - (BOOL)bottomScrollbarVisible;
65 - (BOOL)leftScrollbarVisible;
66 - (BOOL)rightScrollbarVisible;
67 - (void)placeScrollbars;
68 - (void)scroll:(id)sender;
70 - (NSDictionary *)windowAutosaveDict;
76 NSString *buildMenuItemDescriptor(NSMenu *menu, NSString *tail)
78 return menu ? buildMenuItemDescriptor([menu supermenu], [[menu title]
79 stringByAppendingString:tail])
83 NSMutableArray *buildMenuAddress(NSMenu *menu)
87 addr = buildMenuAddress([menu supermenu]);
88 [addr addObject:[menu title]];
90 addr = [NSMutableArray array];
98 @implementation MMWindowController
100 - (id)initWithVimController:(MMVimController *)controller
102 if ((self = [super initWithWindowNibName:@"VimWindow"])) {
103 vimController = controller;
104 scrollbars = [[NSMutableArray alloc] init];
106 // Window cascading is handled by MMAppController.
107 [self setShouldCascadeWindows:NO];
109 // Setup a complete text system.
110 textStorage = [[MMTextStorage alloc] init];
111 NSLayoutManager *lm = [[NSLayoutManager alloc] init];
112 NSTextContainer *tc = [[NSTextContainer alloc] initWithContainerSize:
113 NSMakeSize(1.0e7,1.0e7)];
115 NSString *typesetterString = [[NSUserDefaults standardUserDefaults]
116 stringForKey:MMTypesetterKey];
117 if (![typesetterString isEqual:@"NSTypesetter"]) {
118 MMTypesetter *typesetter = [[MMTypesetter alloc] init];
119 [lm setTypesetter:typesetter];
120 [typesetter release];
122 // Only MMTypesetter supports different cell width multipliers.
123 [[NSUserDefaults standardUserDefaults]
124 setFloat:1.0 forKey:MMCellWidthMultiplierKey];
127 [tc setWidthTracksTextView:NO];
128 [tc setHeightTracksTextView:NO];
129 [tc setLineFragmentPadding:0];
131 [textStorage addLayoutManager:lm];
132 [lm addTextContainer:tc];
134 textView = [[MMTextView alloc] initWithFrame:NSZeroRect
137 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
138 int left = [ud integerForKey:MMTextInsetLeftKey];
139 int top = [ud integerForKey:MMTextInsetTopKey];
140 [textView setTextContainerInset:NSMakeSize(left, top)];
142 // The text storage retains the layout manager which in turn retains
143 // the text container.
153 //NSLog(@"%@ %s", [self className], _cmd);
155 // TODO: release tabBarControl and tabView?
159 [tabBarControl setDelegate:nil];
160 [[self window] setDelegate:nil];
162 [tabView removeAllTabViewItems];
164 [scrollbars release];
166 [textStorage release];
171 - (void)windowDidLoad
173 // Called after window nib file is loaded.
175 [tablineSeparator setHidden:NO];
176 [tabBarControl setHidden:YES];
178 // NOTE: Size to fit looks good, but not many tabs will fit and there are
179 // quite a few drawing bugs in this code, so it is disabled for now.
180 //[tabBarControl setSizeCellsToFit:YES];
182 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
183 [tabBarControl setCellMinWidth:[ud integerForKey:MMTabMinWidthKey]];
184 [tabBarControl setCellMaxWidth:[ud integerForKey:MMTabMaxWidthKey]];
185 [tabBarControl setCellOptimumWidth:[ud integerForKey:MMTabOptimumWidthKey]];
187 [tabBarControl setAllowsDragBetweenWindows:NO];
188 [tabBarControl setShowAddTabButton:YES];
189 [[tabBarControl addTabButton] setTarget:self];
190 [[tabBarControl addTabButton] setAction:@selector(addNewTab:)];
192 // HACK! remove any tabs present in the nib
193 [tabView removeAllTabViewItems];
195 // HACK! These observers used to be set in the designated init of
196 // MMVimController, but this occasionally caused exceptions from within the
197 // AppKit to be raised. The problem seemed related to the fact that the
198 // window got loaded 'too early'; adding the observers here seems to
199 // alleviate this problem.
200 [[NSNotificationCenter defaultCenter]
201 addObserver:vimController
202 selector:@selector(windowWillClose:)
203 name:NSWindowWillCloseNotification
204 object:[self window]];
205 [[NSNotificationCenter defaultCenter]
206 addObserver:vimController
207 selector:@selector(windowDidBecomeMain:)
208 name:NSWindowDidBecomeMainNotification
209 object:[self window]];
212 - (MMVimController *)vimController
214 return vimController;
217 - (MMTextView *)textView
222 - (MMTextStorage *)textStorage
227 - (NSString *)windowAutosaveKey
229 return windowAutosaveKey;
232 - (void)setWindowAutosaveKey:(NSString *)key
234 [windowAutosaveKey autorelease];
235 windowAutosaveKey = [key copy];
240 [[NSApp delegate] windowControllerWillOpen:self];
242 [self addNewTabViewItem];
244 // NOTE! This flag is set once the entire text system is set up.
247 [self updateResizeIncrements];
248 [self resizeWindowToFit:self];
249 [[self window] makeKeyAndOrderFront:self];
251 BOOL statusOff = [[NSUserDefaults standardUserDefaults]
252 boolForKey:MMStatuslineOffKey];
253 [statusTextField setHidden:statusOff];
254 [statusSeparator setHidden:statusOff];
255 [self flashStatusText:@"Welcome to MacVim!"];
258 - (void)updateTabsWithData:(NSData *)data
260 const void *p = [data bytes];
261 const void *end = p + [data length];
264 // HACK! Current tab is first in the message. This way it is not
265 // necessary to guess which tab should be the selected one (this can be
266 // problematic for instance when new tabs are created).
267 int curtabIdx = *((int*)p); p += sizeof(int);
269 NSArray *tabViewItems = [tabBarControl representedTabViewItems];
272 //int wincount = *((int*)p); p += sizeof(int);
273 int length = *((int*)p); p += sizeof(int);
275 NSString *label = [[NSString alloc]
276 initWithBytesNoCopy:(void*)p
278 encoding:NSUTF8StringEncoding
282 // Set the label of the tab; add a new tab when needed.
283 NSTabViewItem *tvi = [tabView numberOfTabViewItems] <= tabIdx
284 ? [self addNewTabViewItem]
285 : [tabViewItems objectAtIndex:tabIdx];
287 [tvi setLabel:label];
294 // Remove unused tabs from the NSTabView. Note that when a tab is closed
295 // the NSTabView will automatically select another tab, but we want Vim to
296 // take care of which tab to select so set the vimTaskSelectedTab flag to
297 // prevent the tab selection message to be passed on to the VimTask.
298 vimTaskSelectedTab = YES;
299 int i, count = [tabView numberOfTabViewItems];
300 for (i = count-1; i >= tabIdx; --i) {
301 id tvi = [tabViewItems objectAtIndex:i];
302 //NSLog(@"Removing tab with index %d", i);
303 [tabView removeTabViewItem:tvi];
305 vimTaskSelectedTab = NO;
307 [self selectTabWithIndex:curtabIdx];
310 - (void)selectTabWithIndex:(int)idx
312 //NSLog(@"%s%d", _cmd, idx);
314 NSArray *tabViewItems = [tabBarControl representedTabViewItems];
315 if (idx < 0 || idx >= [tabViewItems count]) {
316 NSLog(@"WARNING: No tab with index %d exists.", idx);
320 // Do not try to select a tab if already selected.
321 NSTabViewItem *tvi = [tabViewItems objectAtIndex:idx];
322 if (tvi != [tabView selectedTabViewItem]) {
323 vimTaskSelectedTab = YES;
324 [tabView selectTabViewItem:tvi];
325 vimTaskSelectedTab = NO;
329 - (void)setTextDimensionsWithRows:(int)rows columns:(int)cols
331 //NSLog(@"setTextDimensionsWithRows:%d columns:%d", rows, cols);
333 [textStorage setMaxRows:rows columns:cols];
335 if (setupDone && ![textView inLiveResize])
336 shouldUpdateWindowSize = YES;
339 - (void)setStatusText:(NSString *)text
342 [statusTextField setStringValue:text];
344 [statusTextField setStringValue:@""];
347 - (void)flashStatusText:(NSString *)text
349 if ([[NSUserDefaults standardUserDefaults] boolForKey:MMStatuslineOffKey])
352 [self setStatusText:text];
355 [statusTimer invalidate];
356 [statusTimer release];
359 statusTimer = [[NSTimer scheduledTimerWithTimeInterval:3
361 selector:@selector(statusTimerFired:)
366 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
368 //NSLog(@"Create scroller %d of type %d", ident, type);
370 MMScroller *scroller = [[MMScroller alloc] initWithIdentifier:ident
372 [scroller setTarget:self];
373 [scroller setAction:@selector(scroll:)];
375 [[[self window] contentView] addSubview:scroller];
376 [scrollbars addObject:scroller];
380 - (void)destroyScrollbarWithIdentifier:(long)ident
382 //NSLog(@"Destroy scroller %d", ident);
385 MMScroller *scroller = [self scrollbarForIdentifier:ident index:&idx];
387 [scroller removeFromSuperview];
388 [scrollbars removeObjectAtIndex:idx];
390 if (![scroller isHidden]) {
391 // A visible scroller was removed, so the window must resize to
393 //NSLog(@"Visible scroller %d was destroyed, resizing window.",
395 shouldUpdateWindowSize = YES;
400 - (void)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
402 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
403 if (!scroller) return;
405 BOOL wasVisible = ![scroller isHidden];
406 //NSLog(@"%s scroller %d (was %svisible)", visible ? "Show" : "Hide",
407 // ident, wasVisible ? "" : "in");
408 [scroller setHidden:!visible];
410 if (wasVisible != visible) {
411 // A scroller was hidden or shown, so the window must resize to fit.
412 //NSLog(@"%s scroller %d and resize.", visible ? "Show" : "Hide",
414 shouldUpdateWindowSize = YES;
418 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
420 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
421 NSRange range = NSMakeRange(pos, len);
422 if (!NSEqualRanges(range, [scroller range])) {
423 //NSLog(@"Set range %@ for scroller %d",
424 // NSStringFromRange(range), ident);
425 [scroller setRange:range];
426 // TODO! Should only do this once per update.
427 [self placeScrollbars];
431 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
432 identifier:(long)ident
434 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
435 //NSLog(@"Set thumb value %.2f proportion %.2f for scroller %d",
436 // val, prop, ident);
437 [scroller setFloatValue:val knobProportion:prop];
440 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
442 [textStorage setDefaultColorsBackground:back foreground:fore];
443 [textView setBackgroundColor:back];
446 - (void)setFont:(NSFont *)font
448 [textStorage setFont:font];
449 [self updateResizeIncrements];
452 - (void)processCommandQueueDidFinish
454 if (shouldUpdateWindowSize) {
455 shouldUpdateWindowSize = NO;
456 [self resizeWindowToFit:self];
460 - (IBAction)addNewTab:(id)sender
462 // NOTE! This can get called a lot if the user holds down the key
463 // equivalent for this action, which causes the ports to fill up. If we
464 // wait for the message to be sent then the app might become unresponsive.
465 [vimController sendMessage:AddNewTabMsgID data:nil wait:NO];
468 - (IBAction)showTabBar:(id)sender
470 [tablineSeparator setHidden:YES];
471 [tabBarControl setHidden:NO];
474 shouldUpdateWindowSize = YES;
477 - (IBAction)hideTabBar:(id)sender
479 [tablineSeparator setHidden:NO];
480 [tabBarControl setHidden:YES];
483 shouldUpdateWindowSize = YES;
489 // -- PSMTabBarControl delegate ----------------------------------------------
492 - (void)tabView:(NSTabView *)theTabView didSelectTabViewItem:
493 (NSTabViewItem *)tabViewItem
495 // HACK! There seem to be a bug in NSTextView which results in the first
496 // responder not being set to the view of the tab item so it is done
498 [[self window] makeFirstResponder:[tabViewItem view]];
500 // HACK! The selection message should not be propagated to the VimTask if
501 // the VimTask selected the tab (e.g. as opposed the user clicking the
502 // tab). The delegate method has no way of knowing who initiated the
503 // selection so a flag is set when the VimTask initiated the selection.
504 if (!vimTaskSelectedTab) {
505 // Propagate the selection message to the VimTask.
506 int idx = [self representedIndexOfTabViewItem:tabViewItem];
507 if (NSNotFound != idx) {
508 NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
509 [vimController sendMessage:SelectTabMsgID data:data wait:YES];
514 - (BOOL)tabView:(NSTabView *)theTabView shouldCloseTabViewItem:
515 (NSTabViewItem *)tabViewItem
517 // HACK! This method is only called when the user clicks the close button
518 // on the tab. Instead of letting the tab bar close the tab, we return NO
519 // and pass a message on to Vim to let it handle the closing.
520 int idx = [self representedIndexOfTabViewItem:tabViewItem];
521 //NSLog(@"Closing tab with index %d", idx);
522 NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
523 [vimController sendMessage:CloseTabMsgID data:data wait:YES];
528 - (void)tabView:(NSTabView *)theTabView didDragTabViewItem:
529 (NSTabViewItem *)tabViewItem toIndex:(int)idx
531 NSMutableData *data = [NSMutableData data];
532 [data appendBytes:&idx length:sizeof(int)];
534 [vimController sendMessage:DraggedTabMsgID data:data wait:YES];
540 // -- NSWindow delegate ------------------------------------------------------
543 - (BOOL)windowShouldClose:(id)sender
545 [vimController sendMessage:VimShouldCloseMsgID data:nil wait:YES];
549 - (void)windowWillClose:(NSNotification *)notification
551 //NSLog(@"%@ %s", [self className], _cmd);
553 // NOTE! There is a bug in PSMTabBarControl in that it retains the delegate
554 // (which is the MMWindowController) so reset the delegate here, otherwise
555 // the MMWindowController never gets released resulting in a pretty serious
557 [tabBarControl setDelegate:nil];
560 - (void)windowDidMove:(NSNotification *)notification
562 if (windowAutosaveKey) {
563 NSDictionary *dict = [self windowAutosaveDict];
565 [[NSUserDefaults standardUserDefaults]
566 setObject:dict forKey:windowAutosaveKey];
570 - (void)windowDidResize:(id)sender
572 if (!setupDone) return;
576 - (NSRect)windowWillUseStandardFrame:(NSWindow *)win
577 defaultFrame:(NSRect)frame
579 // HACK! For some reason 'frame' is not always constrained to fit on the
580 // screen (e.g. it may overlap the menu bar), so first constrain it to the
581 // screen; otherwise the new frame we compute may be too large and this
582 // will mess up the display after the window resizes.
583 frame = [win constrainFrameRect:frame toScreen:[win screen]];
585 // HACK! If the top of 'frame' is lower than the current window frame,
586 // increase 'frame' so that their tops align. Really, 'frame' should
587 // already have its top at least as high as the current window frame, but
588 // for some reason this is not always the case.
589 // (See resizeWindowToFit: for a similar hack.)
590 NSRect cur = [win frame];
591 if (NSMaxY(cur) > NSMaxY(frame)) {
592 frame.size.height = cur.origin.y - frame.origin.y + cur.size.height;
595 frame = [self fitWindowToFrame:frame];
597 // Keep old width and horizontal position unless user clicked with the
598 // Command key is held down.
599 NSEvent *event = [NSApp currentEvent];
600 if (!([event type] == NSLeftMouseUp
601 && [event modifierFlags] & NSCommandKeyMask)) {
602 NSRect currentFrame = [win frame];
603 frame.size.width = currentFrame.size.width;
604 frame.origin.x = currentFrame.origin.x;
613 // -- Services menu delegate -------------------------------------------------
615 - (id)validRequestorForSendType:(NSString *)sendType
616 returnType:(NSString *)returnType
618 //NSLog(@"validRequestorForSendType:%@ returnType:%@", sendType, returnType);
620 id backendProxy = [vimController backendProxy];
622 if ((!sendType || [sendType isEqual:NSStringPboardType])
623 && (!returnType || [returnType isEqual:NSStringPboardType]))
625 if ((!sendType || [backendProxy starRegisterToPasteboard:nil]) &&
626 (!returnType || [backendProxy starRegisterFromPasteboard:nil]))
632 return [super validRequestorForSendType:sendType returnType:returnType];
635 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
636 types:(NSArray *)types
638 //NSLog(@"writeSelectionToPasteboard:%@ types:%@", pboard, types);
640 if (![types containsObject:NSStringPboardType])
643 id backendProxy = [vimController backendProxy];
644 return [backendProxy starRegisterToPasteboard:pboard];
647 @end // MMWindowController
651 @implementation MMWindowController (Private)
653 - (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize
655 NSSize size = textViewSize;
657 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
658 int right = [ud integerForKey:MMTextInsetRightKey];
659 int bot = [ud integerForKey:MMTextInsetBottomKey];
661 size.width += [textView textContainerOrigin].x + right;
662 size.height += [textView textContainerOrigin].y + bot;
664 // A one pixel high separator is shown if tabline is hidden.
665 if ([tabBarControl isHidden]) ++size.height;
666 else size.height += [tabBarControl frame].size.height;
668 if (![ud boolForKey:MMStatuslineOffKey])
669 size.height += StatusLineHeight;
671 if ([self bottomScrollbarVisible])
672 size.height += [NSScroller scrollerWidth];
673 if ([self leftScrollbarVisible])
674 size.width += [NSScroller scrollerWidth];
675 if ([self rightScrollbarVisible])
676 size.width += [NSScroller scrollerWidth];
681 - (NSRect)textViewRectForContentSize:(NSSize)contentSize
683 NSRect rect = { 0, 0, contentSize.width, contentSize.height };
685 // A one pixel high separator is shown if tabline is hidden.
686 if ([tabBarControl isHidden]) --rect.size.height;
687 else rect.size.height -= [tabBarControl frame].size.height;
689 if (![[NSUserDefaults standardUserDefaults]
690 boolForKey:MMStatuslineOffKey]) {
691 rect.size.height -= StatusLineHeight;
692 rect.origin.y += StatusLineHeight;
695 if ([self bottomScrollbarVisible]) {
696 rect.size.height -= [NSScroller scrollerWidth];
697 rect.origin.y += [NSScroller scrollerWidth];
699 if ([self leftScrollbarVisible]) {
700 rect.size.width -= [NSScroller scrollerWidth];
701 rect.origin.x += [NSScroller scrollerWidth];
703 if ([self rightScrollbarVisible])
704 rect.size.width -= [NSScroller scrollerWidth];
709 - (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize
711 NSSize size = textViewSize;
713 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
714 int right = [ud integerForKey:MMTextInsetRightKey];
715 int bot = [ud integerForKey:MMTextInsetBottomKey];
717 size.width -= [textView textContainerOrigin].x + right;
718 size.height -= [textView textContainerOrigin].y + bot;
723 - (void)resizeWindowToFit:(id)sender
725 if (!setupDone) return;
727 NSWindow *win = [self window];
728 NSRect frame = [win frame];
729 NSRect contentRect = [win contentRectForFrameRect:frame];
730 NSSize newSize = [self contentSizeForTextStorageSize:[textStorage size]];
732 // Keep top-left corner of the window fixed when resizing.
733 contentRect.origin.y -= newSize.height - contentRect.size.height;
734 contentRect.size = newSize;
736 frame = [win frameRectForContentRect:contentRect];
737 NSRect maxFrame = [win constrainFrameRect:frame toScreen:[win screen]];
739 // HACK! Assuming the window frame cannot already be placed too high,
740 // adjust 'maxFrame' so that it at least as high up as the current frame.
741 // The reason for doing this is that constrainFrameRect:toScreen: does not
742 // always seem to utilize as much area as possible.
743 if (NSMaxY(frame) > NSMaxY(maxFrame)) {
744 maxFrame.size.height = frame.origin.y - maxFrame.origin.y
748 if (!NSEqualRects(maxFrame, frame)) {
749 // The new window frame is too big to fit on the screen, so fit the
750 // text storage to the biggest frame which will fit on the screen.
751 //NSLog(@"Proposed window frame does not fit on the screen!");
752 frame = [self fitWindowToFrame:maxFrame];
755 //NSLog(@"%s %@", _cmd, NSStringFromRect(frame));
757 // HACK! If the window does resize, then windowDidResize is called which in
758 // turn calls placeViews. In case the computed new size of the window is
759 // no different from the current size, then we need to call placeViews
761 if (NSEqualRects(frame, [win frame])) {
764 [win setFrame:frame display:YES];
768 - (NSRect)fitWindowToFrame:(NSRect)frame
770 if (!setupDone) return frame;
772 NSWindow *win = [self window];
773 NSRect contentRect = [win contentRectForFrameRect:frame];
774 NSSize size = [self textViewRectForContentSize:contentRect.size].size;
775 size = [self textStorageSizeForTextViewSize:size];
776 size = [textStorage fitToSize:size];
777 size = [self contentSizeForTextStorageSize:size];
779 // Keep top-left corner of 'frame' fixed.
780 contentRect.origin.y -= size.height - contentRect.size.height;
781 contentRect.size = size;
783 return [win frameRectForContentRect:contentRect];
786 - (void)updateResizeIncrements
788 if (!setupDone) return;
790 NSSize size = [textStorage cellSize];
791 [[self window] setContentResizeIncrements:size];
794 - (NSTabViewItem *)addNewTabViewItem
796 // NOTE! A newly created tab is not by selected by default; the VimTask
797 // decides which tab should be selected at all times. However, the AppKit
798 // will automatically select the first tab added to a tab view.
800 NSTabViewItem *tvi = [[NSTabViewItem alloc] initWithIdentifier:nil];
801 [tvi setView:textView];
803 // BUG! This call seems to have no effect; see comment in
804 // tabView:didSelectTabViewItem:.
805 //[tvi setInitialFirstResponder:textView];
807 // NOTE: If this is the first tab it will be automatically selected.
808 vimTaskSelectedTab = YES;
809 [tabView addTabViewItem:tvi];
810 vimTaskSelectedTab = NO;
817 - (void)statusTimerFired:(NSTimer *)timer
819 [self setStatusText:@""];
820 [statusTimer release];
824 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
826 NSArray *tabViewItems = [tabBarControl representedTabViewItems];
827 return [tabViewItems indexOfObject:tvi];
830 - (IBAction)vimMenuItemAction:(id)sender
832 int tag = [sender tag];
834 NSMutableData *data = [NSMutableData data];
835 [data appendBytes:&tag length:sizeof(int)];
837 [vimController sendMessage:ExecuteMenuMsgID data:data wait:NO];
840 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx
842 unsigned i, count = [scrollbars count];
843 for (i = 0; i < count; ++i) {
844 MMScroller *scroller = [scrollbars objectAtIndex:i];
845 if ([scroller identifier] == ident) {
854 - (BOOL)bottomScrollbarVisible
856 unsigned i, count = [scrollbars count];
857 for (i = 0; i < count; ++i) {
858 MMScroller *scroller = [scrollbars objectAtIndex:i];
859 if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden])
866 - (BOOL)leftScrollbarVisible
868 unsigned i, count = [scrollbars count];
869 for (i = 0; i < count; ++i) {
870 MMScroller *scroller = [scrollbars objectAtIndex:i];
871 if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden])
878 - (BOOL)rightScrollbarVisible
880 unsigned i, count = [scrollbars count];
881 for (i = 0; i < count; ++i) {
882 MMScroller *scroller = [scrollbars objectAtIndex:i];
883 if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden])
890 - (void)placeScrollbars
892 if (!setupDone) return;
894 NSRect tabViewFrame = [tabView frame];
895 NSView *contentView = [[self window] contentView];
896 BOOL lsbVisible = [self leftScrollbarVisible];
897 BOOL statusVisible = ![[NSUserDefaults standardUserDefaults]
898 boolForKey:MMStatuslineOffKey];
900 // HACK! Find the lowest left&right vertical scrollbars, as well as the
901 // leftmost horizontal scrollbar. This hack continues further down.
903 // TODO! Can there be no more than one horizontal scrollbar? If so, the
904 // code can be simplified.
905 unsigned lowestLeftSbIdx = (unsigned)-1;
906 unsigned lowestRightSbIdx = (unsigned)-1;
907 unsigned leftmostSbIdx = (unsigned)-1;
908 unsigned rowMaxLeft = 0, rowMaxRight = 0, colMax = 0;
909 unsigned i, count = [scrollbars count];
910 for (i = 0; i < count; ++i) {
911 MMScroller *scroller = [scrollbars objectAtIndex:i];
912 if (![scroller isHidden]) {
913 NSRange range = [scroller range];
914 if ([scroller type] == MMScrollerTypeLeft
915 && range.location >= rowMaxLeft) {
916 rowMaxLeft = range.location;
918 } else if ([scroller type] == MMScrollerTypeRight
919 && range.location >= rowMaxRight) {
920 rowMaxRight = range.location;
921 lowestRightSbIdx = i;
922 } else if ([scroller type] == MMScrollerTypeBottom
923 && range.location >= colMax) {
924 colMax = range.location;
930 // Place the scrollbars.
931 for (i = 0; i < count; ++i) {
932 MMScroller *scroller = [scrollbars objectAtIndex:i];
933 if ([scroller isHidden])
937 if ([scroller type] == MMScrollerTypeBottom) {
938 rect = [textStorage rectForColumnsInRange:[scroller range]];
939 rect.size.height = [NSScroller scrollerWidth];
941 rect.origin.y += StatusLineHeight;
943 rect.origin.x += [NSScroller scrollerWidth];
945 // HACK! Make sure the leftmost horizontal scrollbar covers the
946 // text view all the way to the right, otherwise it looks ugly when
947 // the user drags the window to resize.
948 if (i == leftmostSbIdx) {
949 float w = NSMaxX(tabViewFrame) - NSMaxX(rect);
951 rect.size.width += w;
954 // Make sure scrollbar rect is bounded by the tab view frame.
955 if (rect.origin.x < tabViewFrame.origin.x)
956 rect.origin.x = tabViewFrame.origin.x;
957 else if (rect.origin.x > NSMaxX(tabViewFrame))
958 rect.origin.x = NSMaxX(tabViewFrame);
959 if (NSMaxX(rect) > NSMaxX(tabViewFrame))
960 rect.size.width -= NSMaxX(rect) - NSMaxX(tabViewFrame);
961 if (rect.size.width < 0)
964 rect = [textStorage rectForRowsInRange:[scroller range]];
965 // Adjust for the fact that text layout is flipped.
966 rect.origin.y = NSMaxY(tabViewFrame) - rect.origin.y
968 rect.size.width = [NSScroller scrollerWidth];
969 if ([scroller type] == MMScrollerTypeRight)
970 rect.origin.x = NSMaxX(tabViewFrame);
972 // HACK! Make sure the lowest vertical scrollbar covers the text
973 // view all the way to the bottom. This is done because Vim only
974 // makes the scrollbar cover the (vim-)window it is associated with
975 // and this means there is always an empty gap in the scrollbar
976 // region next to the command line.
977 // TODO! Find a nicer way to do this.
978 if (i == lowestLeftSbIdx || i == lowestRightSbIdx) {
979 float h = rect.origin.y + rect.size.height
980 - tabViewFrame.origin.y;
981 if (rect.size.height < h) {
982 rect.origin.y = tabViewFrame.origin.y;
983 rect.size.height = h;
987 // Vertical scrollers must not cover the resize box in the
988 // bottom-right corner of the window.
989 if (rect.origin.y < [NSScroller scrollerWidth]) {
990 rect.size.height -= [NSScroller scrollerWidth] - rect.origin.y;
991 rect.origin.y = [NSScroller scrollerWidth];
994 // Make sure scrollbar rect is bounded by the tab view frame.
995 if (rect.origin.y < tabViewFrame.origin.y) {
996 rect.size.height -= tabViewFrame.origin.y - rect.origin.y;
997 rect.origin.y = tabViewFrame.origin.y;
998 } else if (rect.origin.y > NSMaxY(tabViewFrame))
999 rect.origin.y = NSMaxY(tabViewFrame);
1000 if (NSMaxY(rect) > NSMaxY(tabViewFrame))
1001 rect.size.height -= NSMaxY(rect) - NSMaxY(tabViewFrame);
1002 if (rect.size.height < 0)
1003 rect.size.height = 0;
1006 //NSLog(@"set scroller #%d frame = %@", i, NSStringFromRect(rect));
1007 NSRect oldRect = [scroller frame];
1008 if (!NSEqualRects(oldRect, rect)) {
1009 [scroller setFrame:rect];
1010 // Clear behind the old scroller frame, or parts of the old
1011 // scroller might still be visible after setFrame:.
1012 [contentView setNeedsDisplayInRect:oldRect];
1013 [scroller setNeedsDisplay:YES];
1018 - (void)scroll:(id)sender
1020 NSMutableData *data = [NSMutableData data];
1021 long ident = [(MMScroller*)sender identifier];
1022 int hitPart = [sender hitPart];
1023 float value = [sender floatValue];
1025 [data appendBytes:&ident length:sizeof(long)];
1026 [data appendBytes:&hitPart length:sizeof(int)];
1027 [data appendBytes:&value length:sizeof(float)];
1029 [vimController sendMessage:ScrollbarEventMsgID data:data wait:NO];
1034 if (!setupDone) return;
1036 // NOTE! It is assumed that the window has been resized so that it will
1037 // exactly fit the text storage (possibly after resizing it). If this is
1038 // not the case the display might be messed up.
1039 NSWindow *win = [self window];
1040 NSRect contentRect = [win contentRectForFrameRect:[win frame]];
1041 NSRect textViewRect = [self textViewRectForContentSize:contentRect.size];
1042 NSSize tsSize = [self textStorageSizeForTextViewSize:textViewRect.size];
1044 int dim[2], rows, cols;
1045 [textStorage getMaxRows:&rows columns:&cols];
1046 [textStorage fitToSize:tsSize rows:&dim[0] columns:&dim[1]];
1048 if (dim[0] != rows || dim[1] != cols) {
1049 NSString *sdim = [NSString stringWithFormat:@"%dx%d", dim[1], dim[0]];
1050 [self flashStatusText:sdim];
1052 //NSLog(@"Notify Vim that text storage dimensions changed to %dx%d",
1054 NSData *data = [NSData dataWithBytes:dim length:2*sizeof(int)];
1056 // NOTE! This can get called a lot when in live resize, which causes
1057 // the connection buffers to fill up. If we wait for the message to be
1058 // sent then the app might become unresponsive.
1059 [vimController sendMessage:SetTextDimensionsMsgID data:data
1060 wait:![textView inLiveResize]];
1063 [tabView setFrame:textViewRect];
1065 [self placeScrollbars];
1068 - (NSDictionary *)windowAutosaveDict
1073 int rows = 0, cols = 0;
1075 [textStorage getMaxRows:&rows columns:&cols];
1077 NSPoint origin = NSZeroPoint;
1079 NSRect frame = [[self window] frame];
1080 origin = NSMakePoint(frame.origin.x, NSMaxY(frame));
1083 NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
1084 [NSNumber numberWithInt:rows], @"Rows",
1085 [NSNumber numberWithInt:cols], @"Columns",
1086 [NSNumber numberWithFloat:origin.x], @"x",
1087 [NSNumber numberWithFloat:origin.y], @"y", nil];
1092 @end // MMWindowController (Private)
1096 @implementation NSTabView (MMExtras)
1098 - (void)removeAllTabViewItems
1100 NSArray *existingItems = [self tabViewItems];
1101 NSEnumerator *e = [existingItems objectEnumerator];
1102 NSTabViewItem *item;
1103 while (item = [e nextObject]){
1104 [self removeTabViewItem:item];
1108 @end // NSTabView (MMExtras)
1113 @implementation MMScroller
1115 - (id)initWithIdentifier:(long)ident type:(int)theType
1117 // HACK! NSScroller creates a horizontal scroller if it is init'ed with a
1118 // frame whose with exceeds its height; so create a bogus rect and pass it
1119 // to initWithFrame.
1120 NSRect frame = theType == MMScrollerTypeBottom
1121 ? NSMakeRect(0, 0, 1, 0)
1122 : NSMakeRect(0, 0, 0, 1);
1124 if ((self = [super initWithFrame:frame])) {
1127 [self setHidden:YES];
1128 [self setEnabled:YES];
1149 - (void)setRange:(NSRange)newRange