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 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi;
61 - (IBAction)vimMenuItemAction:(id)sender;
62 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx;
63 - (BOOL)bottomScrollbarVisible;
64 - (BOOL)leftScrollbarVisible;
65 - (BOOL)rightScrollbarVisible;
66 - (void)placeScrollbars;
67 - (void)scroll:(id)sender;
74 NSString *buildMenuItemDescriptor(NSMenu *menu, NSString *tail)
76 return menu ? buildMenuItemDescriptor([menu supermenu], [[menu title]
77 stringByAppendingString:tail])
81 NSMutableArray *buildMenuAddress(NSMenu *menu)
85 addr = buildMenuAddress([menu supermenu]);
86 [addr addObject:[menu title]];
88 addr = [NSMutableArray array];
96 @implementation MMWindowController
98 - (id)initWithVimController:(MMVimController *)controller
100 if ((self = [super initWithWindowNibName:@"EmptyWindow"])) {
101 vimController = controller;
102 scrollbars = [[NSMutableArray alloc] init];
104 // Window cascading is handled by MMAppController.
105 [self setShouldCascadeWindows:NO];
107 // Setup a complete text system.
108 textStorage = [[MMTextStorage alloc] init];
109 NSLayoutManager *lm = [[NSLayoutManager alloc] init];
110 NSTextContainer *tc = [[NSTextContainer alloc] initWithContainerSize:
111 NSMakeSize(1.0e7,1.0e7)];
113 NSString *typesetterString = [[NSUserDefaults standardUserDefaults]
114 stringForKey:MMTypesetterKey];
115 if (![typesetterString isEqual:@"NSTypesetter"]) {
116 MMTypesetter *typesetter = [[MMTypesetter alloc] init];
117 [lm setTypesetter:typesetter];
118 [typesetter release];
120 // Only MMTypesetter supports different cell width multipliers.
121 [[NSUserDefaults standardUserDefaults]
122 setFloat:1.0 forKey:MMCellWidthMultiplierKey];
125 [tc setWidthTracksTextView:NO];
126 [tc setHeightTracksTextView:NO];
127 [tc setLineFragmentPadding:0];
129 [textStorage addLayoutManager:lm];
130 [lm addTextContainer:tc];
132 NSView *contentView = [[self window] contentView];
133 textView = [[MMTextView alloc] initWithFrame:[contentView frame]
136 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
137 int left = [ud integerForKey:MMTextInsetLeftKey];
138 int top = [ud integerForKey:MMTextInsetTopKey];
139 [textView setTextContainerInset:NSMakeSize(left, top)];
141 [contentView addSubview:textView];
143 // The text storage retains the layout manager which in turn retains
144 // the text container.
148 // Create the tabline separator (which may be visible when the tabline
150 NSRect tabSepRect = [contentView frame];
151 tabSepRect.origin.y = NSMaxY(tabSepRect)-1;
152 tabSepRect.size.height = 1;
153 tablineSeparator = [[NSBox alloc] initWithFrame:tabSepRect];
155 // Create the tab view (which is never visible, but the tab bar control
156 // needs it to function).
157 tabView = [[NSTabView alloc] initWithFrame:NSZeroRect];
159 // Create the tab bar control (which is responsible for actually
160 // drawing the tabline and tabs).
161 NSRect tabFrame = [contentView frame];
162 tabFrame.origin.y = NSMaxY(tabFrame) - 22;
163 tabFrame.size.height = 22;
164 tabBarControl = [[PSMTabBarControl alloc] initWithFrame:tabFrame];
166 [tabView setDelegate:tabBarControl];
168 [tabBarControl setTabView:tabView];
169 [tabBarControl setDelegate:self];
170 [tabBarControl setHidden:YES];
171 [tabBarControl setAutoresizingMask:NSViewWidthSizable|NSViewMinYMargin];
172 [tabBarControl setCellMinWidth:[ud integerForKey:MMTabMinWidthKey]];
173 [tabBarControl setCellMaxWidth:[ud integerForKey:MMTabMaxWidthKey]];
174 [tabBarControl setCellOptimumWidth:
175 [ud integerForKey:MMTabOptimumWidthKey]];
176 [tabBarControl setShowAddTabButton:YES];
177 [[tabBarControl addTabButton] setTarget:self];
178 [[tabBarControl addTabButton] setAction:@selector(addNewTab:)];
179 [tabBarControl setAllowsDragBetweenWindows:NO];
181 [tablineSeparator setBoxType:NSBoxSeparator];
182 [tablineSeparator setHidden:NO];
183 [tablineSeparator setAutoresizingMask:NSViewWidthSizable
186 [contentView setAutoresizesSubviews:YES];
187 [contentView addSubview:tabBarControl];
188 [contentView addSubview:tablineSeparator];
190 [[self window] setDelegate:self];
191 [[self window] setInitialFirstResponder:textView];
199 //NSLog(@"%@ %s", [self className], _cmd);
201 [tabBarControl release]; tabBarControl = nil;
202 [tabView release]; tabView = nil;
203 [tablineSeparator release]; tablineSeparator = nil;
204 [windowAutosaveKey release]; windowAutosaveKey = nil;
205 [scrollbars release]; scrollbars = nil;
206 [textView release]; textView = nil;
207 [textStorage release]; textStorage = nil;
212 - (NSString *)description
214 return [NSString stringWithFormat:@"%@ : setupDone=%d windowAutosaveKey=%@ vimController=%@", [self className], setupDone, windowAutosaveKey, vimController];
217 - (MMVimController *)vimController
219 return vimController;
222 - (MMTextView *)textView
227 - (MMTextStorage *)textStorage
232 - (NSString *)windowAutosaveKey
234 return windowAutosaveKey;
237 - (void)setWindowAutosaveKey:(NSString *)key
239 [windowAutosaveKey autorelease];
240 windowAutosaveKey = [key copy];
245 //NSLog(@"%@ %s", [self className], _cmd);
250 // NOTE! There is a bug in PSMTabBarControl in that it retains the delegate
251 // (which is the MMWindowController) so reset the delegate here, otherwise
252 // the MMWindowController never gets released resulting in a pretty serious
254 [tabView setDelegate:nil];
255 [tabBarControl setDelegate:nil];
256 [tabBarControl setTabView:nil];
257 [[self window] setDelegate:nil];
259 // NOTE! There is another bug in PSMTabBarControl where the control is not
260 // removed as an observer, so remove it here (else lots of evil nasty bugs
261 // will come and gnaw at your feet while you are sleeping).
262 [[NSNotificationCenter defaultCenter] removeObserver:tabBarControl];
264 [tabBarControl removeFromSuperviewWithoutNeedingDisplay];
265 [tablineSeparator removeFromSuperviewWithoutNeedingDisplay];
266 [textView removeFromSuperviewWithoutNeedingDisplay];
268 unsigned i, count = [scrollbars count];
269 for (i = 0; i < count; ++i) {
270 MMScroller *sb = [scrollbars objectAtIndex:i];
271 [sb removeFromSuperviewWithoutNeedingDisplay];
274 [tabView removeAllTabViewItems];
276 [[self window] orderOut:self];
281 [[NSApp delegate] windowControllerWillOpen:self];
283 [self addNewTabViewItem];
287 [self updateResizeIncrements];
288 [self resizeWindowToFit:self];
289 [[self window] makeKeyAndOrderFront:self];
292 - (void)updateTabsWithData:(NSData *)data
294 const void *p = [data bytes];
295 const void *end = p + [data length];
298 // HACK! Current tab is first in the message. This way it is not
299 // necessary to guess which tab should be the selected one (this can be
300 // problematic for instance when new tabs are created).
301 int curtabIdx = *((int*)p); p += sizeof(int);
303 NSArray *tabViewItems = [tabBarControl representedTabViewItems];
306 //int wincount = *((int*)p); p += sizeof(int);
307 int length = *((int*)p); p += sizeof(int);
309 NSString *label = [[NSString alloc]
310 initWithBytesNoCopy:(void*)p
312 encoding:NSUTF8StringEncoding
316 // Set the label of the tab; add a new tab when needed.
317 NSTabViewItem *tvi = [tabView numberOfTabViewItems] <= tabIdx
318 ? [self addNewTabViewItem]
319 : [tabViewItems objectAtIndex:tabIdx];
321 [tvi setLabel:label];
328 // Remove unused tabs from the NSTabView. Note that when a tab is closed
329 // the NSTabView will automatically select another tab, but we want Vim to
330 // take care of which tab to select so set the vimTaskSelectedTab flag to
331 // prevent the tab selection message to be passed on to the VimTask.
332 vimTaskSelectedTab = YES;
333 int i, count = [tabView numberOfTabViewItems];
334 for (i = count-1; i >= tabIdx; --i) {
335 id tvi = [tabViewItems objectAtIndex:i];
336 //NSLog(@"Removing tab with index %d", i);
337 [tabView removeTabViewItem:tvi];
339 vimTaskSelectedTab = NO;
341 [self selectTabWithIndex:curtabIdx];
344 - (void)selectTabWithIndex:(int)idx
346 //NSLog(@"%s%d", _cmd, idx);
348 NSArray *tabViewItems = [tabBarControl representedTabViewItems];
349 if (idx < 0 || idx >= [tabViewItems count]) {
350 NSLog(@"WARNING: No tab with index %d exists.", idx);
354 // Do not try to select a tab if already selected.
355 NSTabViewItem *tvi = [tabViewItems objectAtIndex:idx];
356 if (tvi != [tabView selectedTabViewItem]) {
357 vimTaskSelectedTab = YES;
358 [tabView selectTabViewItem:tvi];
359 vimTaskSelectedTab = NO;
363 - (void)setTextDimensionsWithRows:(int)rows columns:(int)cols
365 //NSLog(@"setTextDimensionsWithRows:%d columns:%d", rows, cols);
367 [textStorage setMaxRows:rows columns:cols];
369 if (setupDone && ![textView inLiveResize])
370 shouldUpdateWindowSize = YES;
373 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
375 //NSLog(@"Create scroller %d of type %d", ident, type);
377 MMScroller *scroller = [[MMScroller alloc] initWithIdentifier:ident
379 [scroller setTarget:self];
380 [scroller setAction:@selector(scroll:)];
382 [[[self window] contentView] addSubview:scroller];
383 [scrollbars addObject:scroller];
387 - (void)destroyScrollbarWithIdentifier:(long)ident
389 //NSLog(@"Destroy scroller %d", ident);
392 MMScroller *scroller = [self scrollbarForIdentifier:ident index:&idx];
394 [scroller removeFromSuperview];
395 [scrollbars removeObjectAtIndex:idx];
397 if (![scroller isHidden]) {
398 // A visible scroller was removed, so the window must resize to
400 //NSLog(@"Visible scroller %d was destroyed, resizing window.",
402 shouldUpdateWindowSize = YES;
407 - (void)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
409 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
410 if (!scroller) return;
412 BOOL wasVisible = ![scroller isHidden];
413 //NSLog(@"%s scroller %d (was %svisible)", visible ? "Show" : "Hide",
414 // ident, wasVisible ? "" : "in");
415 [scroller setHidden:!visible];
417 if (wasVisible != visible) {
418 // A scroller was hidden or shown, so the window must resize to fit.
419 //NSLog(@"%s scroller %d and resize.", visible ? "Show" : "Hide",
421 shouldUpdateWindowSize = YES;
425 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
427 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
428 NSRange range = NSMakeRange(pos, len);
429 if (!NSEqualRanges(range, [scroller range])) {
430 //NSLog(@"Set range %@ for scroller %d",
431 // NSStringFromRange(range), ident);
432 [scroller setRange:range];
433 // TODO! Should only do this once per update.
434 [self placeScrollbars];
438 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
439 identifier:(long)ident
441 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
442 //NSLog(@"Set thumb value %.2f proportion %.2f for scroller %d",
443 // val, prop, ident);
444 [scroller setFloatValue:val knobProportion:prop];
447 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
449 [textStorage setDefaultColorsBackground:back foreground:fore];
450 [textView setBackgroundColor:back];
453 - (void)setFont:(NSFont *)font
455 [textStorage setFont:font];
456 [self updateResizeIncrements];
459 - (void)processCommandQueueDidFinish
461 if (shouldUpdateWindowSize) {
462 shouldUpdateWindowSize = NO;
463 [self resizeWindowToFit:self];
467 - (void)popupMenu:(NSMenu *)menu atRow:(int)row column:(int)col
469 if (!setupDone) return;
472 if (row >= 0 && col >= 0) {
473 NSSize cellSize = [textStorage cellSize];
474 NSPoint pt = { (col+1)*cellSize.width, [textView frame].size.height
475 - (row+1)*cellSize.height };
477 event = [NSEvent mouseEventWithType:NSRightMouseDown
481 windowNumber:[[self window] windowNumber]
487 event = [textView lastMouseDownEvent];
490 [NSMenu popUpContextMenu:menu withEvent:event forView:textView];
493 - (void)showTabBar:(BOOL)on
495 [tabBarControl setHidden:!on];
498 NSToolbar *toolbar = [[self window] toolbar];
499 [tablineSeparator setHidden:![toolbar isVisible]];
501 [tablineSeparator setHidden:on];
505 shouldUpdateWindowSize = YES;
508 - (void)showToolbar:(BOOL)on size:(int)size mode:(int)mode
510 NSToolbar *toolbar = [[self window] toolbar];
511 if (!toolbar) return;
513 [toolbar setSizeMode:size];
514 [toolbar setDisplayMode:mode];
515 [toolbar setVisible:on];
518 [tablineSeparator setHidden:YES];
520 [tablineSeparator setHidden:![tabBarControl isHidden]];
524 - (IBAction)addNewTab:(id)sender
526 // NOTE! This can get called a lot if the user holds down the key
527 // equivalent for this action, which causes the ports to fill up. If we
528 // wait for the message to be sent then the app might become unresponsive.
529 [vimController sendMessage:AddNewTabMsgID data:nil wait:NO];
532 - (IBAction)toggleToolbar:(id)sender
534 [vimController sendMessage:ToggleToolbarMsgID data:nil wait:NO];
539 // -- PSMTabBarControl delegate ----------------------------------------------
542 - (void)tabView:(NSTabView *)theTabView didSelectTabViewItem:
543 (NSTabViewItem *)tabViewItem
545 // HACK! The selection message should not be propagated to the VimTask if
546 // the VimTask selected the tab (e.g. as opposed the user clicking the
547 // tab). The delegate method has no way of knowing who initiated the
548 // selection so a flag is set when the VimTask initiated the selection.
549 if (!vimTaskSelectedTab) {
550 // Propagate the selection message to the VimTask.
551 int idx = [self representedIndexOfTabViewItem:tabViewItem];
552 if (NSNotFound != idx) {
553 NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
554 [vimController sendMessage:SelectTabMsgID data:data wait:YES];
559 - (BOOL)tabView:(NSTabView *)theTabView shouldCloseTabViewItem:
560 (NSTabViewItem *)tabViewItem
562 // HACK! This method is only called when the user clicks the close button
563 // on the tab. Instead of letting the tab bar close the tab, we return NO
564 // and pass a message on to Vim to let it handle the closing.
565 int idx = [self representedIndexOfTabViewItem:tabViewItem];
566 //NSLog(@"Closing tab with index %d", idx);
567 NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
568 [vimController sendMessage:CloseTabMsgID data:data wait:YES];
573 - (void)tabView:(NSTabView *)theTabView didDragTabViewItem:
574 (NSTabViewItem *)tabViewItem toIndex:(int)idx
576 NSMutableData *data = [NSMutableData data];
577 [data appendBytes:&idx length:sizeof(int)];
579 [vimController sendMessage:DraggedTabMsgID data:data wait:YES];
585 // -- NSWindow delegate ------------------------------------------------------
587 - (void)windowDidBecomeMain:(NSNotification *)notification
589 [vimController sendMessage:GotFocusMsgID data:nil wait:NO];
592 - (void)windowDidResignMain:(NSNotification *)notification
594 [vimController sendMessage:LostFocusMsgID data:nil wait:NO];
597 - (BOOL)windowShouldClose:(id)sender
599 [vimController sendMessage:VimShouldCloseMsgID data:nil wait:YES];
603 - (void)windowDidMove:(NSNotification *)notification
605 if (setupDone && windowAutosaveKey) {
606 NSRect frame = [[self window] frame];
607 NSPoint topLeft = { frame.origin.x, NSMaxY(frame) };
608 NSString *topLeftString = NSStringFromPoint(topLeft);
610 [[NSUserDefaults standardUserDefaults]
611 setObject:topLeftString forKey:windowAutosaveKey];
615 - (void)windowDidResize:(id)sender
617 if (!setupDone) return;
621 - (NSRect)windowWillUseStandardFrame:(NSWindow *)win
622 defaultFrame:(NSRect)frame
624 // HACK! For some reason 'frame' is not always constrained to fit on the
625 // screen (e.g. it may overlap the menu bar), so first constrain it to the
626 // screen; otherwise the new frame we compute may be too large and this
627 // will mess up the display after the window resizes.
628 frame = [win constrainFrameRect:frame toScreen:[win screen]];
630 // HACK! If the top of 'frame' is lower than the current window frame,
631 // increase 'frame' so that their tops align. Really, 'frame' should
632 // already have its top at least as high as the current window frame, but
633 // for some reason this is not always the case.
634 // (See resizeWindowToFit: for a similar hack.)
635 NSRect cur = [win frame];
636 if (NSMaxY(cur) > NSMaxY(frame)) {
637 frame.size.height = cur.origin.y - frame.origin.y + cur.size.height;
640 frame = [self fitWindowToFrame:frame];
642 // Keep old width and horizontal position unless user clicked with the
643 // Command key is held down.
644 NSEvent *event = [NSApp currentEvent];
645 if (!([event type] == NSLeftMouseUp
646 && [event modifierFlags] & NSCommandKeyMask)) {
647 NSRect currentFrame = [win frame];
648 frame.size.width = currentFrame.size.width;
649 frame.origin.x = currentFrame.origin.x;
658 // -- Services menu delegate -------------------------------------------------
660 - (id)validRequestorForSendType:(NSString *)sendType
661 returnType:(NSString *)returnType
663 id backendProxy = [vimController backendProxy];
665 if (backendProxy && [sendType isEqual:NSStringPboardType]) {
667 if ([backendProxy starRegisterToPasteboard:nil])
670 @catch (NSException *e) {
671 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
675 return [super validRequestorForSendType:sendType returnType:returnType];
678 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
679 types:(NSArray *)types
681 if (![types containsObject:NSStringPboardType])
684 id backendProxy = [vimController backendProxy];
687 return [backendProxy starRegisterToPasteboard:pboard];
689 @catch (NSException *e) {
690 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
697 @end // MMWindowController
701 @implementation MMWindowController (Private)
703 - (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize
705 NSSize size = textViewSize;
707 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
708 int right = [ud integerForKey:MMTextInsetRightKey];
709 int bot = [ud integerForKey:MMTextInsetBottomKey];
711 size.width += [textView textContainerOrigin].x + right;
712 size.height += [textView textContainerOrigin].y + bot;
714 if (![tablineSeparator isHidden])
716 if (![tabBarControl isHidden])
717 size.height += [tabBarControl frame].size.height;
719 if (![ud boolForKey:MMStatuslineOffKey])
720 size.height += StatusLineHeight;
722 if ([self bottomScrollbarVisible])
723 size.height += [NSScroller scrollerWidth];
724 if ([self leftScrollbarVisible])
725 size.width += [NSScroller scrollerWidth];
726 if ([self rightScrollbarVisible])
727 size.width += [NSScroller scrollerWidth];
732 - (NSRect)textViewRectForContentSize:(NSSize)contentSize
734 NSRect rect = { 0, 0, contentSize.width, contentSize.height };
736 if (![tablineSeparator isHidden])
738 if (![tabBarControl isHidden])
739 rect.size.height -= [tabBarControl frame].size.height;
741 if (![[NSUserDefaults standardUserDefaults]
742 boolForKey:MMStatuslineOffKey]) {
743 rect.size.height -= StatusLineHeight;
744 rect.origin.y += StatusLineHeight;
747 if ([self bottomScrollbarVisible]) {
748 rect.size.height -= [NSScroller scrollerWidth];
749 rect.origin.y += [NSScroller scrollerWidth];
751 if ([self leftScrollbarVisible]) {
752 rect.size.width -= [NSScroller scrollerWidth];
753 rect.origin.x += [NSScroller scrollerWidth];
755 if ([self rightScrollbarVisible])
756 rect.size.width -= [NSScroller scrollerWidth];
761 - (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize
763 NSSize size = textViewSize;
765 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
766 int right = [ud integerForKey:MMTextInsetRightKey];
767 int bot = [ud integerForKey:MMTextInsetBottomKey];
769 size.width -= [textView textContainerOrigin].x + right;
770 size.height -= [textView textContainerOrigin].y + bot;
775 - (void)resizeWindowToFit:(id)sender
777 if (!setupDone) return;
779 NSWindow *win = [self window];
780 NSRect frame = [win frame];
781 NSRect contentRect = [win contentRectForFrameRect:frame];
782 NSSize newSize = [self contentSizeForTextStorageSize:[textStorage size]];
784 // Keep top-left corner of the window fixed when resizing.
785 contentRect.origin.y -= newSize.height - contentRect.size.height;
786 contentRect.size = newSize;
788 frame = [win frameRectForContentRect:contentRect];
789 NSRect maxFrame = [win constrainFrameRect:frame toScreen:[win screen]];
791 // HACK! Assuming the window frame cannot already be placed too high,
792 // adjust 'maxFrame' so that it at least as high up as the current frame.
793 // The reason for doing this is that constrainFrameRect:toScreen: does not
794 // always seem to utilize as much area as possible.
795 if (NSMaxY(frame) > NSMaxY(maxFrame)) {
796 maxFrame.size.height = frame.origin.y - maxFrame.origin.y
800 if (!NSEqualRects(maxFrame, frame)) {
801 // The new window frame is too big to fit on the screen, so fit the
802 // text storage to the biggest frame which will fit on the screen.
803 //NSLog(@"Proposed window frame does not fit on the screen!");
804 frame = [self fitWindowToFrame:maxFrame];
807 //NSLog(@"%s %@", _cmd, NSStringFromRect(frame));
809 // HACK! If the window does resize, then windowDidResize is called which in
810 // turn calls placeViews. In case the computed new size of the window is
811 // no different from the current size, then we need to call placeViews
813 if (NSEqualRects(frame, [win frame])) {
816 [win setFrame:frame display:YES];
820 - (NSRect)fitWindowToFrame:(NSRect)frame
822 if (!setupDone) return frame;
824 NSWindow *win = [self window];
825 NSRect contentRect = [win contentRectForFrameRect:frame];
826 NSSize size = [self textViewRectForContentSize:contentRect.size].size;
827 size = [self textStorageSizeForTextViewSize:size];
828 size = [textStorage fitToSize:size];
829 size = [self contentSizeForTextStorageSize:size];
831 // Keep top-left corner of 'frame' fixed.
832 contentRect.origin.y -= size.height - contentRect.size.height;
833 contentRect.size = size;
835 return [win frameRectForContentRect:contentRect];
838 - (void)updateResizeIncrements
840 if (!setupDone) return;
842 NSSize size = [textStorage cellSize];
843 [[self window] setContentResizeIncrements:size];
846 - (NSTabViewItem *)addNewTabViewItem
848 // NOTE! A newly created tab is not by selected by default; the VimTask
849 // decides which tab should be selected at all times. However, the AppKit
850 // will automatically select the first tab added to a tab view.
852 NSTabViewItem *tvi = [[NSTabViewItem alloc] initWithIdentifier:nil];
854 // NOTE: If this is the first tab it will be automatically selected.
855 vimTaskSelectedTab = YES;
856 [tabView addTabViewItem:tvi];
857 vimTaskSelectedTab = NO;
864 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
866 NSArray *tabViewItems = [tabBarControl representedTabViewItems];
867 return [tabViewItems indexOfObject:tvi];
870 - (IBAction)vimMenuItemAction:(id)sender
872 int tag = [sender tag];
874 NSMutableData *data = [NSMutableData data];
875 [data appendBytes:&tag length:sizeof(int)];
877 [vimController sendMessage:ExecuteMenuMsgID data:data wait:NO];
880 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx
882 unsigned i, count = [scrollbars count];
883 for (i = 0; i < count; ++i) {
884 MMScroller *scroller = [scrollbars objectAtIndex:i];
885 if ([scroller identifier] == ident) {
894 - (BOOL)bottomScrollbarVisible
896 unsigned i, count = [scrollbars count];
897 for (i = 0; i < count; ++i) {
898 MMScroller *scroller = [scrollbars objectAtIndex:i];
899 if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden])
906 - (BOOL)leftScrollbarVisible
908 unsigned i, count = [scrollbars count];
909 for (i = 0; i < count; ++i) {
910 MMScroller *scroller = [scrollbars objectAtIndex:i];
911 if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden])
918 - (BOOL)rightScrollbarVisible
920 unsigned i, count = [scrollbars count];
921 for (i = 0; i < count; ++i) {
922 MMScroller *scroller = [scrollbars objectAtIndex:i];
923 if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden])
930 - (void)placeScrollbars
932 if (!setupDone) return;
934 NSRect textViewFrame = [textView frame];
935 BOOL lsbVisible = [self leftScrollbarVisible];
936 BOOL statusVisible = ![[NSUserDefaults standardUserDefaults]
937 boolForKey:MMStatuslineOffKey];
939 // HACK! Find the lowest left&right vertical scrollbars, as well as the
940 // rightmost horizontal scrollbar. This hack continues further down.
942 // TODO! Can there be no more than one horizontal scrollbar? If so, the
943 // code can be simplified.
944 unsigned lowestLeftSbIdx = (unsigned)-1;
945 unsigned lowestRightSbIdx = (unsigned)-1;
946 unsigned rightmostSbIdx = (unsigned)-1;
947 unsigned rowMaxLeft = 0, rowMaxRight = 0, colMax = 0;
948 unsigned i, count = [scrollbars count];
949 for (i = 0; i < count; ++i) {
950 MMScroller *scroller = [scrollbars objectAtIndex:i];
951 if (![scroller isHidden]) {
952 NSRange range = [scroller range];
953 if ([scroller type] == MMScrollerTypeLeft
954 && range.location >= rowMaxLeft) {
955 rowMaxLeft = range.location;
957 } else if ([scroller type] == MMScrollerTypeRight
958 && range.location >= rowMaxRight) {
959 rowMaxRight = range.location;
960 lowestRightSbIdx = i;
961 } else if ([scroller type] == MMScrollerTypeBottom
962 && range.location >= colMax) {
963 colMax = range.location;
969 // Place the scrollbars.
970 for (i = 0; i < count; ++i) {
971 MMScroller *scroller = [scrollbars objectAtIndex:i];
972 if ([scroller isHidden])
976 if ([scroller type] == MMScrollerTypeBottom) {
977 rect = [textStorage rectForColumnsInRange:[scroller range]];
978 rect.size.height = [NSScroller scrollerWidth];
980 rect.origin.y += StatusLineHeight;
982 rect.origin.x += [NSScroller scrollerWidth];
984 // HACK! Make sure the rightmost horizontal scrollbar covers the
985 // text view all the way to the right, otherwise it looks ugly when
986 // the user drags the window to resize.
987 if (i == rightmostSbIdx) {
988 float w = NSMaxX(textViewFrame) - NSMaxX(rect);
990 rect.size.width += w;
993 // Make sure scrollbar rect is bounded by the text view frame.
994 if (rect.origin.x < textViewFrame.origin.x)
995 rect.origin.x = textViewFrame.origin.x;
996 else if (rect.origin.x > NSMaxX(textViewFrame))
997 rect.origin.x = NSMaxX(textViewFrame);
998 if (NSMaxX(rect) > NSMaxX(textViewFrame))
999 rect.size.width -= NSMaxX(rect) - NSMaxX(textViewFrame);
1000 if (rect.size.width < 0)
1001 rect.size.width = 0;
1003 rect = [textStorage rectForRowsInRange:[scroller range]];
1004 // Adjust for the fact that text layout is flipped.
1005 rect.origin.y = NSMaxY(textViewFrame) - rect.origin.y
1007 rect.size.width = [NSScroller scrollerWidth];
1008 if ([scroller type] == MMScrollerTypeRight)
1009 rect.origin.x = NSMaxX(textViewFrame);
1011 // HACK! Make sure the lowest vertical scrollbar covers the text
1012 // view all the way to the bottom. This is done because Vim only
1013 // makes the scrollbar cover the (vim-)window it is associated with
1014 // and this means there is always an empty gap in the scrollbar
1015 // region next to the command line.
1016 // TODO! Find a nicer way to do this.
1017 if (i == lowestLeftSbIdx || i == lowestRightSbIdx) {
1018 float h = rect.origin.y + rect.size.height
1019 - textViewFrame.origin.y;
1020 if (rect.size.height < h) {
1021 rect.origin.y = textViewFrame.origin.y;
1022 rect.size.height = h;
1026 // Vertical scrollers must not cover the resize box in the
1027 // bottom-right corner of the window.
1028 if (rect.origin.y < [NSScroller scrollerWidth]) {
1029 rect.size.height -= [NSScroller scrollerWidth] - rect.origin.y;
1030 rect.origin.y = [NSScroller scrollerWidth];
1033 // Make sure scrollbar rect is bounded by the text view frame.
1034 if (rect.origin.y < textViewFrame.origin.y) {
1035 rect.size.height -= textViewFrame.origin.y - rect.origin.y;
1036 rect.origin.y = textViewFrame.origin.y;
1037 } else if (rect.origin.y > NSMaxY(textViewFrame))
1038 rect.origin.y = NSMaxY(textViewFrame);
1039 if (NSMaxY(rect) > NSMaxY(textViewFrame))
1040 rect.size.height -= NSMaxY(rect) - NSMaxY(textViewFrame);
1041 if (rect.size.height < 0)
1042 rect.size.height = 0;
1045 //NSLog(@"set scroller #%d frame = %@", i, NSStringFromRect(rect));
1046 NSRect oldRect = [scroller frame];
1047 if (!NSEqualRects(oldRect, rect)) {
1048 [scroller setFrame:rect];
1049 // Clear behind the old scroller frame, or parts of the old
1050 // scroller might still be visible after setFrame:.
1051 [[[self window] contentView] setNeedsDisplayInRect:oldRect];
1052 [scroller setNeedsDisplay:YES];
1057 - (void)scroll:(id)sender
1059 NSMutableData *data = [NSMutableData data];
1060 long ident = [(MMScroller*)sender identifier];
1061 int hitPart = [sender hitPart];
1062 float value = [sender floatValue];
1064 [data appendBytes:&ident length:sizeof(long)];
1065 [data appendBytes:&hitPart length:sizeof(int)];
1066 [data appendBytes:&value length:sizeof(float)];
1068 [vimController sendMessage:ScrollbarEventMsgID data:data wait:NO];
1073 if (!setupDone) return;
1075 // NOTE! It is assumed that the window has been resized so that it will
1076 // exactly fit the text storage (possibly after resizing it). If this is
1077 // not the case the display might be messed up.
1078 NSWindow *win = [self window];
1079 NSRect contentRect = [win contentRectForFrameRect:[win frame]];
1080 NSRect textViewRect = [self textViewRectForContentSize:contentRect.size];
1081 NSSize tsSize = [self textStorageSizeForTextViewSize:textViewRect.size];
1083 int dim[2], rows, cols;
1084 [textStorage getMaxRows:&rows columns:&cols];
1085 [textStorage fitToSize:tsSize rows:&dim[0] columns:&dim[1]];
1087 if (dim[0] != rows || dim[1] != cols) {
1088 //NSLog(@"Notify Vim that text storage dimensions changed to %dx%d",
1090 NSData *data = [NSData dataWithBytes:dim length:2*sizeof(int)];
1092 // NOTE! This can get called a lot when in live resize, which causes
1093 // the connection buffers to fill up. If we wait for the message to be
1094 // sent then the app might become unresponsive.
1095 [vimController sendMessage:SetTextDimensionsMsgID data:data
1096 wait:![textView inLiveResize]];
1099 [textView setFrame:textViewRect];
1101 [self placeScrollbars];
1104 @end // MMWindowController (Private)
1108 @implementation NSTabView (MMExtras)
1110 - (void)removeAllTabViewItems
1112 NSArray *existingItems = [self tabViewItems];
1113 NSEnumerator *e = [existingItems objectEnumerator];
1114 NSTabViewItem *item;
1115 while (item = [e nextObject]){
1116 [self removeTabViewItem:item];
1120 @end // NSTabView (MMExtras)
1125 @implementation MMScroller
1127 - (id)initWithIdentifier:(long)ident type:(int)theType
1129 // HACK! NSScroller creates a horizontal scroller if it is init'ed with a
1130 // frame whose with exceeds its height; so create a bogus rect and pass it
1131 // to initWithFrame.
1132 NSRect frame = theType == MMScrollerTypeBottom
1133 ? NSMakeRect(0, 0, 1, 0)
1134 : NSMakeRect(0, 0, 0, 1);
1136 if ((self = [super initWithFrame:frame])) {
1139 [self setHidden:YES];
1140 [self setEnabled:YES];
1161 - (void)setRange:(NSRange)newRange
1166 - (void)scrollWheel:(NSEvent *)event
1168 // HACK! Pass message on to the text view.
1169 MMWindowController *wc = [[self window] windowController];
1170 [[wc textView] scrollWheel:event];