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,
31 @interface NSTabView (MMExtras)
32 - (void)removeAllTabViewItems;
37 @interface MMScroller : NSScroller {
42 - (id)initWithIdentifier:(long)ident type:(int)type;
46 - (void)setRange:(NSRange)newRange;
49 @interface MMWindowController (Private)
50 - (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize;
51 - (NSRect)textViewRectForContentSize:(NSSize)contentSize;
52 - (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize;
53 - (NSRect)fitWindowToFrame:(NSRect)frame;
54 - (void)updateResizeIncrements;
55 - (NSTabViewItem *)addNewTabViewItem;
56 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi;
57 - (IBAction)vimMenuItemAction:(id)sender;
58 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx;
59 - (BOOL)bottomScrollbarVisible;
60 - (BOOL)leftScrollbarVisible;
61 - (BOOL)rightScrollbarVisible;
62 - (void)placeScrollbars;
63 - (void)scroll:(id)sender;
65 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb;
71 NSString *buildMenuItemDescriptor(NSMenu *menu, NSString *tail)
73 return menu ? buildMenuItemDescriptor([menu supermenu], [[menu title]
74 stringByAppendingString:tail])
78 NSMutableArray *buildMenuAddress(NSMenu *menu)
82 addr = buildMenuAddress([menu supermenu]);
83 [addr addObject:[menu title]];
85 addr = [NSMutableArray array];
93 @implementation MMWindowController
95 - (id)initWithVimController:(MMVimController *)controller
97 if ((self = [super initWithWindowNibName:@"EmptyWindow"])) {
98 vimController = controller;
99 scrollbars = [[NSMutableArray alloc] init];
101 // Window cascading is handled by MMAppController.
102 [self setShouldCascadeWindows:NO];
104 // Setup a complete text system.
105 textStorage = [[MMTextStorage alloc] init];
106 NSLayoutManager *lm = [[NSLayoutManager alloc] init];
107 NSTextContainer *tc = [[NSTextContainer alloc] initWithContainerSize:
108 NSMakeSize(1.0e7,1.0e7)];
110 NSString *typesetterString = [[NSUserDefaults standardUserDefaults]
111 stringForKey:MMTypesetterKey];
112 if (![typesetterString isEqual:@"NSTypesetter"]) {
113 MMTypesetter *typesetter = [[MMTypesetter alloc] init];
114 [lm setTypesetter:typesetter];
115 [typesetter release];
117 // Only MMTypesetter supports different cell width multipliers.
118 [[NSUserDefaults standardUserDefaults]
119 setFloat:1.0 forKey:MMCellWidthMultiplierKey];
122 [tc setWidthTracksTextView:NO];
123 [tc setHeightTracksTextView:NO];
124 [tc setLineFragmentPadding:0];
126 [textStorage addLayoutManager:lm];
127 [lm addTextContainer:tc];
129 NSView *contentView = [[self window] contentView];
130 textView = [[MMTextView alloc] initWithFrame:[contentView frame]
133 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
134 int left = [ud integerForKey:MMTextInsetLeftKey];
135 int top = [ud integerForKey:MMTextInsetTopKey];
136 [textView setTextContainerInset:NSMakeSize(left, top)];
138 [contentView addSubview:textView];
140 // The text storage retains the layout manager which in turn retains
141 // the text container.
145 // Create the tabline separator (which may be visible when the tabline
147 NSRect tabSepRect = [contentView frame];
148 tabSepRect.origin.y = NSMaxY(tabSepRect)-1;
149 tabSepRect.size.height = 1;
150 tablineSeparator = [[NSBox alloc] initWithFrame:tabSepRect];
152 // Create the tab view (which is never visible, but the tab bar control
153 // needs it to function).
154 tabView = [[NSTabView alloc] initWithFrame:NSZeroRect];
156 // Create the tab bar control (which is responsible for actually
157 // drawing the tabline and tabs).
158 NSRect tabFrame = [contentView frame];
159 tabFrame.origin.y = NSMaxY(tabFrame) - 22;
160 tabFrame.size.height = 22;
161 tabBarControl = [[PSMTabBarControl alloc] initWithFrame:tabFrame];
163 [tabView setDelegate:tabBarControl];
165 [tabBarControl setTabView:tabView];
166 [tabBarControl setDelegate:self];
167 [tabBarControl setHidden:YES];
168 [tabBarControl setAutoresizingMask:NSViewWidthSizable|NSViewMinYMargin];
169 [tabBarControl setCellMinWidth:[ud integerForKey:MMTabMinWidthKey]];
170 [tabBarControl setCellMaxWidth:[ud integerForKey:MMTabMaxWidthKey]];
171 [tabBarControl setCellOptimumWidth:
172 [ud integerForKey:MMTabOptimumWidthKey]];
173 [tabBarControl setShowAddTabButton:YES];
174 [[tabBarControl addTabButton] setTarget:self];
175 [[tabBarControl addTabButton] setAction:@selector(addNewTab:)];
176 [tabBarControl setAllowsDragBetweenWindows:NO];
178 [tablineSeparator setBoxType:NSBoxSeparator];
179 [tablineSeparator setHidden:NO];
180 [tablineSeparator setAutoresizingMask:NSViewWidthSizable
183 [contentView setAutoresizesSubviews:YES];
184 [contentView addSubview:tabBarControl];
185 [contentView addSubview:tablineSeparator];
187 [[self window] setDelegate:self];
188 [[self window] setInitialFirstResponder:textView];
196 //NSLog(@"%@ %s", [self className], _cmd);
198 [tabBarControl release]; tabBarControl = nil;
199 [tabView release]; tabView = nil;
200 [tablineSeparator release]; tablineSeparator = nil;
201 [windowAutosaveKey release]; windowAutosaveKey = nil;
202 [scrollbars release]; scrollbars = nil;
203 [textView release]; textView = nil;
204 [textStorage release]; textStorage = nil;
209 - (NSString *)description
211 return [NSString stringWithFormat:@"%@ : setupDone=%d windowAutosaveKey=%@ vimController=%@", [self className], setupDone, windowAutosaveKey, vimController];
214 - (MMVimController *)vimController
216 return vimController;
219 - (MMTextView *)textView
224 - (MMTextStorage *)textStorage
229 - (NSString *)windowAutosaveKey
231 return windowAutosaveKey;
234 - (void)setWindowAutosaveKey:(NSString *)key
236 [windowAutosaveKey autorelease];
237 windowAutosaveKey = [key copy];
242 //NSLog(@"%@ %s", [self className], _cmd);
247 // NOTE! There is a bug in PSMTabBarControl in that it retains the delegate
248 // (which is the MMWindowController) so reset the delegate here, otherwise
249 // the MMWindowController never gets released resulting in a pretty serious
251 [tabView setDelegate:nil];
252 [tabBarControl setDelegate:nil];
253 [tabBarControl setTabView:nil];
254 [[self window] setDelegate:nil];
256 // NOTE! There is another bug in PSMTabBarControl where the control is not
257 // removed as an observer, so remove it here (else lots of evil nasty bugs
258 // will come and gnaw at your feet while you are sleeping).
259 [[NSNotificationCenter defaultCenter] removeObserver:tabBarControl];
261 [tabBarControl removeFromSuperviewWithoutNeedingDisplay];
262 [tablineSeparator removeFromSuperviewWithoutNeedingDisplay];
263 [textView removeFromSuperviewWithoutNeedingDisplay];
265 unsigned i, count = [scrollbars count];
266 for (i = 0; i < count; ++i) {
267 MMScroller *sb = [scrollbars objectAtIndex:i];
268 [sb removeFromSuperviewWithoutNeedingDisplay];
271 [tabView removeAllTabViewItems];
273 [[self window] orderOut:self];
278 [[NSApp delegate] windowControllerWillOpen:self];
280 [self addNewTabViewItem];
284 [self updateResizeIncrements];
285 [self resizeWindowToFit:self];
286 [[self window] makeKeyAndOrderFront:self];
289 - (void)updateTabsWithData:(NSData *)data
291 const void *p = [data bytes];
292 const void *end = p + [data length];
295 // HACK! Current tab is first in the message. This way it is not
296 // necessary to guess which tab should be the selected one (this can be
297 // problematic for instance when new tabs are created).
298 int curtabIdx = *((int*)p); p += sizeof(int);
300 NSArray *tabViewItems = [tabBarControl representedTabViewItems];
303 //int wincount = *((int*)p); p += sizeof(int);
304 int length = *((int*)p); p += sizeof(int);
306 NSString *label = [[NSString alloc]
307 initWithBytesNoCopy:(void*)p
309 encoding:NSUTF8StringEncoding
313 // Set the label of the tab; add a new tab when needed.
314 NSTabViewItem *tvi = [tabView numberOfTabViewItems] <= tabIdx
315 ? [self addNewTabViewItem]
316 : [tabViewItems objectAtIndex:tabIdx];
318 [tvi setLabel:label];
325 // Remove unused tabs from the NSTabView. Note that when a tab is closed
326 // the NSTabView will automatically select another tab, but we want Vim to
327 // take care of which tab to select so set the vimTaskSelectedTab flag to
328 // prevent the tab selection message to be passed on to the VimTask.
329 vimTaskSelectedTab = YES;
330 int i, count = [tabView numberOfTabViewItems];
331 for (i = count-1; i >= tabIdx; --i) {
332 id tvi = [tabViewItems objectAtIndex:i];
333 //NSLog(@"Removing tab with index %d", i);
334 [tabView removeTabViewItem:tvi];
336 vimTaskSelectedTab = NO;
338 [self selectTabWithIndex:curtabIdx];
341 - (void)selectTabWithIndex:(int)idx
343 //NSLog(@"%s%d", _cmd, idx);
345 NSArray *tabViewItems = [tabBarControl representedTabViewItems];
346 if (idx < 0 || idx >= [tabViewItems count]) {
347 NSLog(@"WARNING: No tab with index %d exists.", idx);
351 // Do not try to select a tab if already selected.
352 NSTabViewItem *tvi = [tabViewItems objectAtIndex:idx];
353 if (tvi != [tabView selectedTabViewItem]) {
354 vimTaskSelectedTab = YES;
355 [tabView selectTabViewItem:tvi];
356 vimTaskSelectedTab = NO;
360 - (void)setTextDimensionsWithRows:(int)rows columns:(int)cols
362 //NSLog(@"setTextDimensionsWithRows:%d columns:%d", rows, cols);
364 [textStorage setMaxRows:rows columns:cols];
366 if (setupDone && ![textView inLiveResize])
367 shouldUpdateWindowSize = YES;
370 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
372 //NSLog(@"Create scroller %d of type %d", ident, type);
374 MMScroller *scroller = [[MMScroller alloc] initWithIdentifier:ident
376 [scroller setTarget:self];
377 [scroller setAction:@selector(scroll:)];
379 [[[self window] contentView] addSubview:scroller];
380 [scrollbars addObject:scroller];
384 - (void)destroyScrollbarWithIdentifier:(long)ident
386 //NSLog(@"Destroy scroller %d", ident);
389 MMScroller *scroller = [self scrollbarForIdentifier:ident index:&idx];
391 [scroller removeFromSuperview];
392 [scrollbars removeObjectAtIndex:idx];
394 if (![scroller isHidden]) {
395 // A visible scroller was removed, so the window must resize to
397 //NSLog(@"Visible scroller %d was destroyed, resizing window.",
399 shouldUpdateWindowSize = YES;
404 - (void)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
406 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
407 if (!scroller) return;
409 BOOL wasVisible = ![scroller isHidden];
410 //NSLog(@"%s scroller %d (was %svisible)", visible ? "Show" : "Hide",
411 // ident, wasVisible ? "" : "in");
412 [scroller setHidden:!visible];
414 if (wasVisible != visible) {
415 // A scroller was hidden or shown, so the window must resize to fit.
416 //NSLog(@"%s scroller %d and resize.", visible ? "Show" : "Hide",
418 shouldUpdateWindowSize = YES;
422 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
424 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
425 NSRange range = NSMakeRange(pos, len);
426 if (!NSEqualRanges(range, [scroller range])) {
427 //NSLog(@"Set range %@ for scroller %d",
428 // NSStringFromRange(range), ident);
429 [scroller setRange:range];
430 // TODO! Should only do this once per update.
431 [self placeScrollbars];
435 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
436 identifier:(long)ident
438 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
439 //NSLog(@"Set thumb value %.2f proportion %.2f for scroller %d",
440 // val, prop, ident);
441 [scroller setFloatValue:val knobProportion:prop];
444 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
446 [textStorage setDefaultColorsBackground:back foreground:fore];
447 [textView setBackgroundColor:back];
450 - (void)setFont:(NSFont *)font
452 [[NSFontManager sharedFontManager] setSelectedFont:font isMultiple:NO];
453 [textStorage setFont:font];
454 [self updateResizeIncrements];
457 - (void)processCommandQueueDidFinish
459 if (shouldUpdateWindowSize) {
460 shouldUpdateWindowSize = NO;
461 [self resizeWindowToFit:self];
465 - (void)popupMenu:(NSMenu *)menu atRow:(int)row column:(int)col
467 if (!setupDone) return;
470 if (row >= 0 && col >= 0) {
471 NSSize cellSize = [textStorage cellSize];
472 NSPoint pt = { (col+1)*cellSize.width, [textView frame].size.height
473 - (row+1)*cellSize.height };
475 event = [NSEvent mouseEventWithType:NSRightMouseDown
479 windowNumber:[[self window] windowNumber]
485 event = [textView lastMouseDownEvent];
488 [NSMenu popUpContextMenu:menu withEvent:event forView:textView];
491 - (void)showTabBar:(BOOL)on
493 [tabBarControl setHidden:!on];
496 NSToolbar *toolbar = [[self window] toolbar];
497 [tablineSeparator setHidden:![toolbar isVisible]];
499 [tablineSeparator setHidden:on];
503 shouldUpdateWindowSize = YES;
506 - (void)showToolbar:(BOOL)on size:(int)size mode:(int)mode
508 NSToolbar *toolbar = [[self window] toolbar];
509 if (!toolbar) return;
511 [toolbar setSizeMode:size];
512 [toolbar setDisplayMode:mode];
513 [toolbar setVisible:on];
516 [tablineSeparator setHidden:YES];
518 [tablineSeparator setHidden:![tabBarControl isHidden]];
522 - (void)setMouseShape:(int)shape
524 // This switch should match mshape_names[] in misc2.c.
526 // TODO: Add missing cursor shapes.
528 case 2: [[NSCursor IBeamCursor] set]; break;
529 case 3: case 4: [[NSCursor resizeUpDownCursor] set]; break;
530 case 5: case 6: [[NSCursor resizeLeftRightCursor] set]; break;
531 case 9: [[NSCursor crosshairCursor] set]; break;
532 case 10: [[NSCursor pointingHandCursor] set]; break;
533 case 11: [[NSCursor openHandCursor] set]; break;
535 [[NSCursor arrowCursor] set]; break;
538 // Shape 1 indicates that the mouse cursor should be hidden.
540 [NSCursor setHiddenUntilMouseMoves:YES];
543 - (void)adjustLinespace:(int)linespace
546 [textStorage setLinespace:(float)linespace];
547 shouldUpdateWindowSize = YES;
551 - (IBAction)addNewTab:(id)sender
553 // NOTE! This can get called a lot if the user holds down the key
554 // equivalent for this action, which causes the ports to fill up. If we
555 // wait for the message to be sent then the app might become unresponsive.
556 [vimController sendMessage:AddNewTabMsgID data:nil];
559 - (IBAction)toggleToolbar:(id)sender
561 [vimController sendMessage:ToggleToolbarMsgID data:nil];
566 // -- PSMTabBarControl delegate ----------------------------------------------
569 - (BOOL)tabView:(NSTabView *)theTabView shouldSelectTabViewItem:
570 (NSTabViewItem *)tabViewItem
572 // NOTE: It would be reasonable to think that 'shouldSelect...' implies
573 // that this message only gets sent when the user clicks the tab.
574 // Unfortunately it is not so, which is why we need the
575 // 'vimTaskSelectedTab' flag.
577 // HACK! The selection message should not be propagated to the VimTask if
578 // the VimTask selected the tab (e.g. as opposed the user clicking the
579 // tab). The delegate method has no way of knowing who initiated the
580 // selection so a flag is set when the VimTask initiated the selection.
581 if (!vimTaskSelectedTab) {
582 // Propagate the selection message to the VimTask.
583 int idx = [self representedIndexOfTabViewItem:tabViewItem];
584 if (NSNotFound != idx) {
585 NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
586 [vimController sendMessage:SelectTabMsgID data:data];
590 // Unless Vim selected the tab, return NO, and let Vim decide if the tab
591 // should get selected or not.
592 return vimTaskSelectedTab;
595 - (BOOL)tabView:(NSTabView *)theTabView shouldCloseTabViewItem:
596 (NSTabViewItem *)tabViewItem
598 // HACK! This method is only called when the user clicks the close button
599 // on the tab. Instead of letting the tab bar close the tab, we return NO
600 // and pass a message on to Vim to let it handle the closing.
601 int idx = [self representedIndexOfTabViewItem:tabViewItem];
602 //NSLog(@"Closing tab with index %d", idx);
603 NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
604 [vimController sendMessage:CloseTabMsgID data:data];
609 - (void)tabView:(NSTabView *)theTabView didDragTabViewItem:
610 (NSTabViewItem *)tabViewItem toIndex:(int)idx
612 NSMutableData *data = [NSMutableData data];
613 [data appendBytes:&idx length:sizeof(int)];
615 [vimController sendMessage:DraggedTabMsgID data:data];
621 // -- NSWindow delegate ------------------------------------------------------
623 - (void)windowDidBecomeMain:(NSNotification *)notification
625 [vimController sendMessage:GotFocusMsgID data:nil];
628 [[NSFontManager sharedFontManager] setSelectedFont:[textStorage font]
632 - (void)windowDidResignMain:(NSNotification *)notification
634 [vimController sendMessage:LostFocusMsgID data:nil];
637 [textView hideMarkedTextField];
640 - (BOOL)windowShouldClose:(id)sender
642 [vimController sendMessage:VimShouldCloseMsgID data:nil];
646 - (void)windowDidMove:(NSNotification *)notification
648 if (setupDone && windowAutosaveKey) {
649 NSRect frame = [[self window] frame];
650 NSPoint topLeft = { frame.origin.x, NSMaxY(frame) };
651 NSString *topLeftString = NSStringFromPoint(topLeft);
653 [[NSUserDefaults standardUserDefaults]
654 setObject:topLeftString forKey:windowAutosaveKey];
658 - (void)windowDidResize:(id)sender
660 if (!setupDone) return;
664 - (NSRect)windowWillUseStandardFrame:(NSWindow *)win
665 defaultFrame:(NSRect)frame
667 // HACK! For some reason 'frame' is not always constrained to fit on the
668 // screen (e.g. it may overlap the menu bar), so first constrain it to the
669 // screen; otherwise the new frame we compute may be too large and this
670 // will mess up the display after the window resizes.
671 frame = [win constrainFrameRect:frame toScreen:[win screen]];
673 // HACK! If the top of 'frame' is lower than the current window frame,
674 // increase 'frame' so that their tops align. Really, 'frame' should
675 // already have its top at least as high as the current window frame, but
676 // for some reason this is not always the case.
677 // (See resizeWindowToFit: for a similar hack.)
678 NSRect cur = [win frame];
679 if (NSMaxY(cur) > NSMaxY(frame)) {
680 frame.size.height = cur.origin.y - frame.origin.y + cur.size.height;
683 frame = [self fitWindowToFrame:frame];
685 // Keep old width and horizontal position unless user clicked with the
686 // Command key is held down.
687 NSEvent *event = [NSApp currentEvent];
688 if (!([event type] == NSLeftMouseUp
689 && [event modifierFlags] & NSCommandKeyMask)) {
690 NSRect currentFrame = [win frame];
691 frame.size.width = currentFrame.size.width;
692 frame.origin.x = currentFrame.origin.x;
701 // -- Services menu delegate -------------------------------------------------
703 - (id)validRequestorForSendType:(NSString *)sendType
704 returnType:(NSString *)returnType
706 if ([sendType isEqual:NSStringPboardType]
707 && [self askBackendForStarRegister:nil])
710 return [super validRequestorForSendType:sendType returnType:returnType];
713 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
714 types:(NSArray *)types
716 if (![types containsObject:NSStringPboardType])
719 return [self askBackendForStarRegister:pboard];
722 - (void)resizeWindowToFit:(id)sender
724 // NOTE: Be very careful when you call this method! Do not call while
725 // processing command queue, instead set 'shouldUpdateWindowSize' to YES.
726 // The only other place it is currently called is when live resize ends.
727 // This is done to ensure that the text view and window sizes match up
728 // (they may become out of sync if a SetTextDimensionsMsgID message to the
729 // backend is dropped).
731 if (!setupDone) return;
733 NSWindow *win = [self window];
734 NSRect frame = [win frame];
735 NSRect contentRect = [win contentRectForFrameRect:frame];
736 NSSize newSize = [self contentSizeForTextStorageSize:[textStorage size]];
738 // Keep top-left corner of the window fixed when resizing.
739 contentRect.origin.y -= newSize.height - contentRect.size.height;
740 contentRect.size = newSize;
742 frame = [win frameRectForContentRect:contentRect];
743 NSRect maxFrame = [win constrainFrameRect:frame toScreen:[win screen]];
745 // HACK! Assuming the window frame cannot already be placed too high,
746 // adjust 'maxFrame' so that it at least as high up as the current frame.
747 // The reason for doing this is that constrainFrameRect:toScreen: does not
748 // always seem to utilize as much area as possible.
749 if (NSMaxY(frame) > NSMaxY(maxFrame)) {
750 maxFrame.size.height = frame.origin.y - maxFrame.origin.y
754 if (!NSEqualRects(maxFrame, frame)) {
755 // The new window frame is too big to fit on the screen, so fit the
756 // text storage to the biggest frame which will fit on the screen.
757 //NSLog(@"Proposed window frame does not fit on the screen!");
758 frame = [self fitWindowToFrame:maxFrame];
761 //NSLog(@"%s %@", _cmd, NSStringFromRect(frame));
763 // HACK! If the window does resize, then windowDidResize is called which in
764 // turn calls placeViews. In case the computed new size of the window is
765 // no different from the current size, then we need to call placeViews
767 if (NSEqualRects(frame, [win frame])) {
770 [win setFrame:frame display:YES];
774 @end // MMWindowController
778 @implementation MMWindowController (Private)
780 - (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize
782 NSSize size = textViewSize;
784 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
785 int right = [ud integerForKey:MMTextInsetRightKey];
786 int bot = [ud integerForKey:MMTextInsetBottomKey];
788 size.width += [textView textContainerOrigin].x + right;
789 size.height += [textView textContainerOrigin].y + bot;
791 if (![tablineSeparator isHidden])
793 if (![tabBarControl isHidden])
794 size.height += [tabBarControl frame].size.height;
796 if ([self bottomScrollbarVisible])
797 size.height += [NSScroller scrollerWidth];
798 if ([self leftScrollbarVisible])
799 size.width += [NSScroller scrollerWidth];
800 if ([self rightScrollbarVisible])
801 size.width += [NSScroller scrollerWidth];
806 - (NSRect)textViewRectForContentSize:(NSSize)contentSize
808 NSRect rect = { 0, 0, contentSize.width, contentSize.height };
810 if (![tablineSeparator isHidden])
812 if (![tabBarControl isHidden])
813 rect.size.height -= [tabBarControl frame].size.height;
815 if ([self bottomScrollbarVisible]) {
816 rect.size.height -= [NSScroller scrollerWidth];
817 rect.origin.y += [NSScroller scrollerWidth];
819 if ([self leftScrollbarVisible]) {
820 rect.size.width -= [NSScroller scrollerWidth];
821 rect.origin.x += [NSScroller scrollerWidth];
823 if ([self rightScrollbarVisible])
824 rect.size.width -= [NSScroller scrollerWidth];
829 - (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize
831 NSSize size = textViewSize;
833 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
834 int right = [ud integerForKey:MMTextInsetRightKey];
835 int bot = [ud integerForKey:MMTextInsetBottomKey];
837 size.width -= [textView textContainerOrigin].x + right;
838 size.height -= [textView textContainerOrigin].y + bot;
843 - (NSRect)fitWindowToFrame:(NSRect)frame
845 if (!setupDone) return frame;
847 NSWindow *win = [self window];
848 NSRect contentRect = [win contentRectForFrameRect:frame];
849 NSSize size = [self textViewRectForContentSize:contentRect.size].size;
850 size = [self textStorageSizeForTextViewSize:size];
851 size = [textStorage fitToSize:size];
852 size = [self contentSizeForTextStorageSize:size];
854 // Keep top-left corner of 'frame' fixed.
855 contentRect.origin.y -= size.height - contentRect.size.height;
856 contentRect.size = size;
858 return [win frameRectForContentRect:contentRect];
861 - (void)updateResizeIncrements
863 if (!setupDone) return;
865 NSSize size = [textStorage cellSize];
866 [[self window] setContentResizeIncrements:size];
869 - (NSTabViewItem *)addNewTabViewItem
871 // NOTE! A newly created tab is not by selected by default; the VimTask
872 // decides which tab should be selected at all times. However, the AppKit
873 // will automatically select the first tab added to a tab view.
875 NSTabViewItem *tvi = [[NSTabViewItem alloc] initWithIdentifier:nil];
877 // NOTE: If this is the first tab it will be automatically selected.
878 vimTaskSelectedTab = YES;
879 [tabView addTabViewItem:tvi];
880 vimTaskSelectedTab = NO;
887 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
889 NSArray *tabViewItems = [tabBarControl representedTabViewItems];
890 return [tabViewItems indexOfObject:tvi];
893 - (IBAction)vimMenuItemAction:(id)sender
895 int tag = [sender tag];
897 NSMutableData *data = [NSMutableData data];
898 [data appendBytes:&tag length:sizeof(int)];
900 [vimController sendMessage:ExecuteMenuMsgID data:data];
903 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx
905 unsigned i, count = [scrollbars count];
906 for (i = 0; i < count; ++i) {
907 MMScroller *scroller = [scrollbars objectAtIndex:i];
908 if ([scroller identifier] == ident) {
917 - (BOOL)bottomScrollbarVisible
919 unsigned i, count = [scrollbars count];
920 for (i = 0; i < count; ++i) {
921 MMScroller *scroller = [scrollbars objectAtIndex:i];
922 if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden])
929 - (BOOL)leftScrollbarVisible
931 unsigned i, count = [scrollbars count];
932 for (i = 0; i < count; ++i) {
933 MMScroller *scroller = [scrollbars objectAtIndex:i];
934 if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden])
941 - (BOOL)rightScrollbarVisible
943 unsigned i, count = [scrollbars count];
944 for (i = 0; i < count; ++i) {
945 MMScroller *scroller = [scrollbars objectAtIndex:i];
946 if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden])
953 - (void)placeScrollbars
955 if (!setupDone) return;
957 NSRect textViewFrame = [textView frame];
958 BOOL lsbVisible = [self leftScrollbarVisible];
960 // HACK! Find the lowest left&right vertical scrollbars, as well as the
961 // rightmost horizontal scrollbar. This hack continues further down.
963 // TODO! Can there be no more than one horizontal scrollbar? If so, the
964 // code can be simplified.
965 unsigned lowestLeftSbIdx = (unsigned)-1;
966 unsigned lowestRightSbIdx = (unsigned)-1;
967 unsigned rightmostSbIdx = (unsigned)-1;
968 unsigned rowMaxLeft = 0, rowMaxRight = 0, colMax = 0;
969 unsigned i, count = [scrollbars count];
970 for (i = 0; i < count; ++i) {
971 MMScroller *scroller = [scrollbars objectAtIndex:i];
972 if (![scroller isHidden]) {
973 NSRange range = [scroller range];
974 if ([scroller type] == MMScrollerTypeLeft
975 && range.location >= rowMaxLeft) {
976 rowMaxLeft = range.location;
978 } else if ([scroller type] == MMScrollerTypeRight
979 && range.location >= rowMaxRight) {
980 rowMaxRight = range.location;
981 lowestRightSbIdx = i;
982 } else if ([scroller type] == MMScrollerTypeBottom
983 && range.location >= colMax) {
984 colMax = range.location;
990 // Place the scrollbars.
991 for (i = 0; i < count; ++i) {
992 MMScroller *scroller = [scrollbars objectAtIndex:i];
993 if ([scroller isHidden])
997 if ([scroller type] == MMScrollerTypeBottom) {
998 rect = [textStorage rectForColumnsInRange:[scroller range]];
999 rect.size.height = [NSScroller scrollerWidth];
1001 rect.origin.x += [NSScroller scrollerWidth];
1003 // HACK! Make sure the rightmost horizontal scrollbar covers the
1004 // text view all the way to the right, otherwise it looks ugly when
1005 // the user drags the window to resize.
1006 if (i == rightmostSbIdx) {
1007 float w = NSMaxX(textViewFrame) - NSMaxX(rect);
1009 rect.size.width += w;
1012 // Make sure scrollbar rect is bounded by the text view frame.
1013 if (rect.origin.x < textViewFrame.origin.x)
1014 rect.origin.x = textViewFrame.origin.x;
1015 else if (rect.origin.x > NSMaxX(textViewFrame))
1016 rect.origin.x = NSMaxX(textViewFrame);
1017 if (NSMaxX(rect) > NSMaxX(textViewFrame))
1018 rect.size.width -= NSMaxX(rect) - NSMaxX(textViewFrame);
1019 if (rect.size.width < 0)
1020 rect.size.width = 0;
1022 rect = [textStorage rectForRowsInRange:[scroller range]];
1023 // Adjust for the fact that text layout is flipped.
1024 rect.origin.y = NSMaxY(textViewFrame) - rect.origin.y
1026 rect.size.width = [NSScroller scrollerWidth];
1027 if ([scroller type] == MMScrollerTypeRight)
1028 rect.origin.x = NSMaxX(textViewFrame);
1030 // HACK! Make sure the lowest vertical scrollbar covers the text
1031 // view all the way to the bottom. This is done because Vim only
1032 // makes the scrollbar cover the (vim-)window it is associated with
1033 // and this means there is always an empty gap in the scrollbar
1034 // region next to the command line.
1035 // TODO! Find a nicer way to do this.
1036 if (i == lowestLeftSbIdx || i == lowestRightSbIdx) {
1037 float h = rect.origin.y + rect.size.height
1038 - textViewFrame.origin.y;
1039 if (rect.size.height < h) {
1040 rect.origin.y = textViewFrame.origin.y;
1041 rect.size.height = h;
1045 // Vertical scrollers must not cover the resize box in the
1046 // bottom-right corner of the window.
1047 if (rect.origin.y < [NSScroller scrollerWidth]) {
1048 rect.size.height -= [NSScroller scrollerWidth] - rect.origin.y;
1049 rect.origin.y = [NSScroller scrollerWidth];
1052 // Make sure scrollbar rect is bounded by the text view frame.
1053 if (rect.origin.y < textViewFrame.origin.y) {
1054 rect.size.height -= textViewFrame.origin.y - rect.origin.y;
1055 rect.origin.y = textViewFrame.origin.y;
1056 } else if (rect.origin.y > NSMaxY(textViewFrame))
1057 rect.origin.y = NSMaxY(textViewFrame);
1058 if (NSMaxY(rect) > NSMaxY(textViewFrame))
1059 rect.size.height -= NSMaxY(rect) - NSMaxY(textViewFrame);
1060 if (rect.size.height < 0)
1061 rect.size.height = 0;
1064 //NSLog(@"set scroller #%d frame = %@", i, NSStringFromRect(rect));
1065 NSRect oldRect = [scroller frame];
1066 if (!NSEqualRects(oldRect, rect)) {
1067 [scroller setFrame:rect];
1068 // Clear behind the old scroller frame, or parts of the old
1069 // scroller might still be visible after setFrame:.
1070 [[[self window] contentView] setNeedsDisplayInRect:oldRect];
1071 [scroller setNeedsDisplay:YES];
1076 - (void)scroll:(id)sender
1078 NSMutableData *data = [NSMutableData data];
1079 long ident = [(MMScroller*)sender identifier];
1080 int hitPart = [sender hitPart];
1081 float value = [sender floatValue];
1083 [data appendBytes:&ident length:sizeof(long)];
1084 [data appendBytes:&hitPart length:sizeof(int)];
1085 [data appendBytes:&value length:sizeof(float)];
1087 [vimController sendMessage:ScrollbarEventMsgID data:data];
1092 if (!setupDone) return;
1094 // NOTE! It is assumed that the window has been resized so that it will
1095 // exactly fit the text storage (possibly after resizing it). If this is
1096 // not the case the display might be messed up.
1097 NSWindow *win = [self window];
1098 NSRect contentRect = [win contentRectForFrameRect:[win frame]];
1099 NSRect textViewRect = [self textViewRectForContentSize:contentRect.size];
1100 NSSize tsSize = [self textStorageSizeForTextViewSize:textViewRect.size];
1102 int dim[2], rows, cols;
1103 [textStorage getMaxRows:&rows columns:&cols];
1104 [textStorage fitToSize:tsSize rows:&dim[0] columns:&dim[1]];
1106 if (dim[0] != rows || dim[1] != cols) {
1107 //NSLog(@"Notify Vim that text storage dimensions changed to %dx%d",
1109 NSData *data = [NSData dataWithBytes:dim length:2*sizeof(int)];
1111 // NOTE! This can get called a lot when in live resize, which causes
1112 // the connection buffers to fill up. If we wait for the message to be
1113 // sent then the app might become unresponsive.
1114 [vimController sendMessage:SetTextDimensionsMsgID data:data];
1117 [textView setFrame:textViewRect];
1119 [self placeScrollbars];
1122 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb
1125 id backendProxy = [vimController backendProxy];
1129 reply = [backendProxy starRegisterToPasteboard:pb];
1131 @catch (NSException *e) {
1132 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1139 @end // MMWindowController (Private)
1143 @implementation NSTabView (MMExtras)
1145 - (void)removeAllTabViewItems
1147 NSArray *existingItems = [self tabViewItems];
1148 NSEnumerator *e = [existingItems objectEnumerator];
1149 NSTabViewItem *item;
1150 while (item = [e nextObject]){
1151 [self removeTabViewItem:item];
1155 @end // NSTabView (MMExtras)
1160 @implementation MMScroller
1162 - (id)initWithIdentifier:(long)ident type:(int)theType
1164 // HACK! NSScroller creates a horizontal scroller if it is init'ed with a
1165 // frame whose with exceeds its height; so create a bogus rect and pass it
1166 // to initWithFrame.
1167 NSRect frame = theType == MMScrollerTypeBottom
1168 ? NSMakeRect(0, 0, 1, 0)
1169 : NSMakeRect(0, 0, 0, 1);
1171 if ((self = [super initWithFrame:frame])) {
1174 [self setHidden:YES];
1175 [self setEnabled:YES];
1196 - (void)setRange:(NSRange)newRange
1201 - (void)scrollWheel:(NSEvent *)event
1203 // HACK! Pass message on to the text view.
1204 MMWindowController *wc = [[self window] windowController];
1205 [[wc textView] scrollWheel:event];