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 - (void)resizeWindowToFit:(id)sender;
54 - (NSRect)fitWindowToFrame:(NSRect)frame;
55 - (void)updateResizeIncrements;
56 - (NSTabViewItem *)addNewTabViewItem;
57 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi;
58 - (IBAction)vimMenuItemAction:(id)sender;
59 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx;
60 - (BOOL)bottomScrollbarVisible;
61 - (BOOL)leftScrollbarVisible;
62 - (BOOL)rightScrollbarVisible;
63 - (void)placeScrollbars;
64 - (void)scroll:(id)sender;
66 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb;
72 NSString *buildMenuItemDescriptor(NSMenu *menu, NSString *tail)
74 return menu ? buildMenuItemDescriptor([menu supermenu], [[menu title]
75 stringByAppendingString:tail])
79 NSMutableArray *buildMenuAddress(NSMenu *menu)
83 addr = buildMenuAddress([menu supermenu]);
84 [addr addObject:[menu title]];
86 addr = [NSMutableArray array];
94 @implementation MMWindowController
96 - (id)initWithVimController:(MMVimController *)controller
98 if ((self = [super initWithWindowNibName:@"EmptyWindow"])) {
99 vimController = controller;
100 scrollbars = [[NSMutableArray alloc] init];
102 // Window cascading is handled by MMAppController.
103 [self setShouldCascadeWindows:NO];
105 // Setup a complete text system.
106 textStorage = [[MMTextStorage alloc] init];
107 NSLayoutManager *lm = [[NSLayoutManager alloc] init];
108 NSTextContainer *tc = [[NSTextContainer alloc] initWithContainerSize:
109 NSMakeSize(1.0e7,1.0e7)];
111 NSString *typesetterString = [[NSUserDefaults standardUserDefaults]
112 stringForKey:MMTypesetterKey];
113 if (![typesetterString isEqual:@"NSTypesetter"]) {
114 MMTypesetter *typesetter = [[MMTypesetter alloc] init];
115 [lm setTypesetter:typesetter];
116 [typesetter release];
118 // Only MMTypesetter supports different cell width multipliers.
119 [[NSUserDefaults standardUserDefaults]
120 setFloat:1.0 forKey:MMCellWidthMultiplierKey];
123 [tc setWidthTracksTextView:NO];
124 [tc setHeightTracksTextView:NO];
125 [tc setLineFragmentPadding:0];
127 [textStorage addLayoutManager:lm];
128 [lm addTextContainer:tc];
130 NSView *contentView = [[self window] contentView];
131 textView = [[MMTextView alloc] initWithFrame:[contentView frame]
134 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
135 int left = [ud integerForKey:MMTextInsetLeftKey];
136 int top = [ud integerForKey:MMTextInsetTopKey];
137 [textView setTextContainerInset:NSMakeSize(left, top)];
139 [contentView addSubview:textView];
141 // The text storage retains the layout manager which in turn retains
142 // the text container.
146 // Create the tabline separator (which may be visible when the tabline
148 NSRect tabSepRect = [contentView frame];
149 tabSepRect.origin.y = NSMaxY(tabSepRect)-1;
150 tabSepRect.size.height = 1;
151 tablineSeparator = [[NSBox alloc] initWithFrame:tabSepRect];
153 // Create the tab view (which is never visible, but the tab bar control
154 // needs it to function).
155 tabView = [[NSTabView alloc] initWithFrame:NSZeroRect];
157 // Create the tab bar control (which is responsible for actually
158 // drawing the tabline and tabs).
159 NSRect tabFrame = [contentView frame];
160 tabFrame.origin.y = NSMaxY(tabFrame) - 22;
161 tabFrame.size.height = 22;
162 tabBarControl = [[PSMTabBarControl alloc] initWithFrame:tabFrame];
164 [tabView setDelegate:tabBarControl];
166 [tabBarControl setTabView:tabView];
167 [tabBarControl setDelegate:self];
168 [tabBarControl setHidden:YES];
169 [tabBarControl setAutoresizingMask:NSViewWidthSizable|NSViewMinYMargin];
170 [tabBarControl setCellMinWidth:[ud integerForKey:MMTabMinWidthKey]];
171 [tabBarControl setCellMaxWidth:[ud integerForKey:MMTabMaxWidthKey]];
172 [tabBarControl setCellOptimumWidth:
173 [ud integerForKey:MMTabOptimumWidthKey]];
174 [tabBarControl setShowAddTabButton:YES];
175 [[tabBarControl addTabButton] setTarget:self];
176 [[tabBarControl addTabButton] setAction:@selector(addNewTab:)];
177 [tabBarControl setAllowsDragBetweenWindows:NO];
179 [tablineSeparator setBoxType:NSBoxSeparator];
180 [tablineSeparator setHidden:NO];
181 [tablineSeparator setAutoresizingMask:NSViewWidthSizable
184 [contentView setAutoresizesSubviews:YES];
185 [contentView addSubview:tabBarControl];
186 [contentView addSubview:tablineSeparator];
188 [[self window] setDelegate:self];
189 [[self window] setInitialFirstResponder:textView];
197 //NSLog(@"%@ %s", [self className], _cmd);
199 [tabBarControl release]; tabBarControl = nil;
200 [tabView release]; tabView = nil;
201 [tablineSeparator release]; tablineSeparator = nil;
202 [windowAutosaveKey release]; windowAutosaveKey = nil;
203 [scrollbars release]; scrollbars = nil;
204 [textView release]; textView = nil;
205 [textStorage release]; textStorage = nil;
210 - (NSString *)description
212 return [NSString stringWithFormat:@"%@ : setupDone=%d windowAutosaveKey=%@ vimController=%@", [self className], setupDone, windowAutosaveKey, vimController];
215 - (MMVimController *)vimController
217 return vimController;
220 - (MMTextView *)textView
225 - (MMTextStorage *)textStorage
230 - (NSString *)windowAutosaveKey
232 return windowAutosaveKey;
235 - (void)setWindowAutosaveKey:(NSString *)key
237 [windowAutosaveKey autorelease];
238 windowAutosaveKey = [key copy];
243 //NSLog(@"%@ %s", [self className], _cmd);
248 // NOTE! There is a bug in PSMTabBarControl in that it retains the delegate
249 // (which is the MMWindowController) so reset the delegate here, otherwise
250 // the MMWindowController never gets released resulting in a pretty serious
252 [tabView setDelegate:nil];
253 [tabBarControl setDelegate:nil];
254 [tabBarControl setTabView:nil];
255 [[self window] setDelegate:nil];
257 // NOTE! There is another bug in PSMTabBarControl where the control is not
258 // removed as an observer, so remove it here (else lots of evil nasty bugs
259 // will come and gnaw at your feet while you are sleeping).
260 [[NSNotificationCenter defaultCenter] removeObserver:tabBarControl];
262 [tabBarControl removeFromSuperviewWithoutNeedingDisplay];
263 [tablineSeparator removeFromSuperviewWithoutNeedingDisplay];
264 [textView removeFromSuperviewWithoutNeedingDisplay];
266 unsigned i, count = [scrollbars count];
267 for (i = 0; i < count; ++i) {
268 MMScroller *sb = [scrollbars objectAtIndex:i];
269 [sb removeFromSuperviewWithoutNeedingDisplay];
272 [tabView removeAllTabViewItems];
274 [[self window] orderOut:self];
279 [[NSApp delegate] windowControllerWillOpen:self];
281 [self addNewTabViewItem];
285 [self updateResizeIncrements];
286 [self resizeWindowToFit:self];
287 [[self window] makeKeyAndOrderFront:self];
290 - (void)updateTabsWithData:(NSData *)data
292 const void *p = [data bytes];
293 const void *end = p + [data length];
296 // HACK! Current tab is first in the message. This way it is not
297 // necessary to guess which tab should be the selected one (this can be
298 // problematic for instance when new tabs are created).
299 int curtabIdx = *((int*)p); p += sizeof(int);
301 NSArray *tabViewItems = [tabBarControl representedTabViewItems];
304 //int wincount = *((int*)p); p += sizeof(int);
305 int length = *((int*)p); p += sizeof(int);
307 NSString *label = [[NSString alloc]
308 initWithBytesNoCopy:(void*)p
310 encoding:NSUTF8StringEncoding
314 // Set the label of the tab; add a new tab when needed.
315 NSTabViewItem *tvi = [tabView numberOfTabViewItems] <= tabIdx
316 ? [self addNewTabViewItem]
317 : [tabViewItems objectAtIndex:tabIdx];
319 [tvi setLabel:label];
326 // Remove unused tabs from the NSTabView. Note that when a tab is closed
327 // the NSTabView will automatically select another tab, but we want Vim to
328 // take care of which tab to select so set the vimTaskSelectedTab flag to
329 // prevent the tab selection message to be passed on to the VimTask.
330 vimTaskSelectedTab = YES;
331 int i, count = [tabView numberOfTabViewItems];
332 for (i = count-1; i >= tabIdx; --i) {
333 id tvi = [tabViewItems objectAtIndex:i];
334 //NSLog(@"Removing tab with index %d", i);
335 [tabView removeTabViewItem:tvi];
337 vimTaskSelectedTab = NO;
339 [self selectTabWithIndex:curtabIdx];
342 - (void)selectTabWithIndex:(int)idx
344 //NSLog(@"%s%d", _cmd, idx);
346 NSArray *tabViewItems = [tabBarControl representedTabViewItems];
347 if (idx < 0 || idx >= [tabViewItems count]) {
348 NSLog(@"WARNING: No tab with index %d exists.", idx);
352 // Do not try to select a tab if already selected.
353 NSTabViewItem *tvi = [tabViewItems objectAtIndex:idx];
354 if (tvi != [tabView selectedTabViewItem]) {
355 vimTaskSelectedTab = YES;
356 [tabView selectTabViewItem:tvi];
357 vimTaskSelectedTab = NO;
361 - (void)setTextDimensionsWithRows:(int)rows columns:(int)cols
363 //NSLog(@"setTextDimensionsWithRows:%d columns:%d", rows, cols);
365 [textStorage setMaxRows:rows columns:cols];
367 if (setupDone && ![textView inLiveResize])
368 shouldUpdateWindowSize = YES;
371 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
373 //NSLog(@"Create scroller %d of type %d", ident, type);
375 MMScroller *scroller = [[MMScroller alloc] initWithIdentifier:ident
377 [scroller setTarget:self];
378 [scroller setAction:@selector(scroll:)];
380 [[[self window] contentView] addSubview:scroller];
381 [scrollbars addObject:scroller];
385 - (void)destroyScrollbarWithIdentifier:(long)ident
387 //NSLog(@"Destroy scroller %d", ident);
390 MMScroller *scroller = [self scrollbarForIdentifier:ident index:&idx];
392 [scroller removeFromSuperview];
393 [scrollbars removeObjectAtIndex:idx];
395 if (![scroller isHidden]) {
396 // A visible scroller was removed, so the window must resize to
398 //NSLog(@"Visible scroller %d was destroyed, resizing window.",
400 shouldUpdateWindowSize = YES;
405 - (void)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
407 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
408 if (!scroller) return;
410 BOOL wasVisible = ![scroller isHidden];
411 //NSLog(@"%s scroller %d (was %svisible)", visible ? "Show" : "Hide",
412 // ident, wasVisible ? "" : "in");
413 [scroller setHidden:!visible];
415 if (wasVisible != visible) {
416 // A scroller was hidden or shown, so the window must resize to fit.
417 //NSLog(@"%s scroller %d and resize.", visible ? "Show" : "Hide",
419 shouldUpdateWindowSize = YES;
423 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
425 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
426 NSRange range = NSMakeRange(pos, len);
427 if (!NSEqualRanges(range, [scroller range])) {
428 //NSLog(@"Set range %@ for scroller %d",
429 // NSStringFromRange(range), ident);
430 [scroller setRange:range];
431 // TODO! Should only do this once per update.
432 [self placeScrollbars];
436 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
437 identifier:(long)ident
439 MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
440 //NSLog(@"Set thumb value %.2f proportion %.2f for scroller %d",
441 // val, prop, ident);
442 [scroller setFloatValue:val knobProportion:prop];
445 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
447 [textStorage setDefaultColorsBackground:back foreground:fore];
448 [textView setBackgroundColor:back];
451 - (void)setFont:(NSFont *)font
453 [[NSFontManager sharedFontManager] setSelectedFont:font isMultiple:NO];
454 [textStorage setFont:font];
455 [self updateResizeIncrements];
458 - (void)processCommandQueueDidFinish
460 if (shouldUpdateWindowSize) {
461 shouldUpdateWindowSize = NO;
462 [self resizeWindowToFit:self];
466 - (void)popupMenu:(NSMenu *)menu atRow:(int)row column:(int)col
468 if (!setupDone) return;
471 if (row >= 0 && col >= 0) {
472 NSSize cellSize = [textStorage cellSize];
473 NSPoint pt = { (col+1)*cellSize.width, [textView frame].size.height
474 - (row+1)*cellSize.height };
476 event = [NSEvent mouseEventWithType:NSRightMouseDown
480 windowNumber:[[self window] windowNumber]
486 event = [textView lastMouseDownEvent];
489 [NSMenu popUpContextMenu:menu withEvent:event forView:textView];
492 - (void)showTabBar:(BOOL)on
494 [tabBarControl setHidden:!on];
497 NSToolbar *toolbar = [[self window] toolbar];
498 [tablineSeparator setHidden:![toolbar isVisible]];
500 [tablineSeparator setHidden:on];
504 shouldUpdateWindowSize = YES;
507 - (void)showToolbar:(BOOL)on size:(int)size mode:(int)mode
509 NSToolbar *toolbar = [[self window] toolbar];
510 if (!toolbar) return;
512 [toolbar setSizeMode:size];
513 [toolbar setDisplayMode:mode];
514 [toolbar setVisible:on];
517 [tablineSeparator setHidden:YES];
519 [tablineSeparator setHidden:![tabBarControl isHidden]];
523 - (void)setMouseShape:(int)shape
525 // This switch should match mshape_names[] in misc2.c.
527 // TODO: Add missing cursor shapes.
529 case 2: [[NSCursor IBeamCursor] set]; break;
530 case 3: case 4: [[NSCursor resizeUpDownCursor] set]; break;
531 case 5: case 6: [[NSCursor resizeLeftRightCursor] set]; break;
532 case 9: [[NSCursor crosshairCursor] set]; break;
533 case 10: [[NSCursor pointingHandCursor] set]; break;
534 case 11: [[NSCursor openHandCursor] set]; break;
536 [[NSCursor arrowCursor] set]; break;
539 // Shape 1 indicates that the mouse cursor should be hidden.
541 [NSCursor setHiddenUntilMouseMoves:YES];
544 - (void)adjustLinespace:(int)linespace
547 [textStorage setLinespace:(float)linespace];
548 shouldUpdateWindowSize = YES;
552 - (IBAction)addNewTab:(id)sender
554 // NOTE! This can get called a lot if the user holds down the key
555 // equivalent for this action, which causes the ports to fill up. If we
556 // wait for the message to be sent then the app might become unresponsive.
557 [vimController sendMessage:AddNewTabMsgID data:nil wait:NO];
560 - (IBAction)toggleToolbar:(id)sender
562 [vimController sendMessage:ToggleToolbarMsgID data:nil wait:NO];
567 // -- PSMTabBarControl delegate ----------------------------------------------
570 - (BOOL)tabView:(NSTabView *)theTabView shouldSelectTabViewItem:
571 (NSTabViewItem *)tabViewItem
573 // NOTE: It would be reasonable to think that 'shouldSelect...' implies
574 // that this message only gets sent when the user clicks the tab.
575 // Unfortunately it is not so, which is why we need the
576 // 'vimTaskSelectedTab' flag.
578 // HACK! The selection message should not be propagated to the VimTask if
579 // the VimTask selected the tab (e.g. as opposed the user clicking the
580 // tab). The delegate method has no way of knowing who initiated the
581 // selection so a flag is set when the VimTask initiated the selection.
582 if (!vimTaskSelectedTab) {
583 // Propagate the selection message to the VimTask.
584 int idx = [self representedIndexOfTabViewItem:tabViewItem];
585 if (NSNotFound != idx) {
586 NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
587 [vimController sendMessage:SelectTabMsgID data:data wait:YES];
591 // Unless Vim selected the tab, return NO, and let Vim decide if the tab
592 // should get selected or not.
593 return vimTaskSelectedTab;
596 - (BOOL)tabView:(NSTabView *)theTabView shouldCloseTabViewItem:
597 (NSTabViewItem *)tabViewItem
599 // HACK! This method is only called when the user clicks the close button
600 // on the tab. Instead of letting the tab bar close the tab, we return NO
601 // and pass a message on to Vim to let it handle the closing.
602 int idx = [self representedIndexOfTabViewItem:tabViewItem];
603 //NSLog(@"Closing tab with index %d", idx);
604 NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
605 [vimController sendMessage:CloseTabMsgID data:data wait:YES];
610 - (void)tabView:(NSTabView *)theTabView didDragTabViewItem:
611 (NSTabViewItem *)tabViewItem toIndex:(int)idx
613 NSMutableData *data = [NSMutableData data];
614 [data appendBytes:&idx length:sizeof(int)];
616 [vimController sendMessage:DraggedTabMsgID data:data wait:YES];
622 // -- NSWindow delegate ------------------------------------------------------
624 - (void)windowDidBecomeMain:(NSNotification *)notification
626 [vimController sendMessage:GotFocusMsgID data:nil wait:NO];
629 [[NSFontManager sharedFontManager] setSelectedFont:[textStorage font]
633 - (void)windowDidResignMain:(NSNotification *)notification
635 [vimController sendMessage:LostFocusMsgID data:nil wait:NO];
638 [textView hideMarkedTextField];
641 - (BOOL)windowShouldClose:(id)sender
643 [vimController sendMessage:VimShouldCloseMsgID data:nil wait:YES];
647 - (void)windowDidMove:(NSNotification *)notification
649 if (setupDone && windowAutosaveKey) {
650 NSRect frame = [[self window] frame];
651 NSPoint topLeft = { frame.origin.x, NSMaxY(frame) };
652 NSString *topLeftString = NSStringFromPoint(topLeft);
654 [[NSUserDefaults standardUserDefaults]
655 setObject:topLeftString forKey:windowAutosaveKey];
659 - (void)windowDidResize:(id)sender
661 if (!setupDone) return;
665 - (NSRect)windowWillUseStandardFrame:(NSWindow *)win
666 defaultFrame:(NSRect)frame
668 // HACK! For some reason 'frame' is not always constrained to fit on the
669 // screen (e.g. it may overlap the menu bar), so first constrain it to the
670 // screen; otherwise the new frame we compute may be too large and this
671 // will mess up the display after the window resizes.
672 frame = [win constrainFrameRect:frame toScreen:[win screen]];
674 // HACK! If the top of 'frame' is lower than the current window frame,
675 // increase 'frame' so that their tops align. Really, 'frame' should
676 // already have its top at least as high as the current window frame, but
677 // for some reason this is not always the case.
678 // (See resizeWindowToFit: for a similar hack.)
679 NSRect cur = [win frame];
680 if (NSMaxY(cur) > NSMaxY(frame)) {
681 frame.size.height = cur.origin.y - frame.origin.y + cur.size.height;
684 frame = [self fitWindowToFrame:frame];
686 // Keep old width and horizontal position unless user clicked with the
687 // Command key is held down.
688 NSEvent *event = [NSApp currentEvent];
689 if (!([event type] == NSLeftMouseUp
690 && [event modifierFlags] & NSCommandKeyMask)) {
691 NSRect currentFrame = [win frame];
692 frame.size.width = currentFrame.size.width;
693 frame.origin.x = currentFrame.origin.x;
702 // -- Services menu delegate -------------------------------------------------
704 - (id)validRequestorForSendType:(NSString *)sendType
705 returnType:(NSString *)returnType
707 if ([sendType isEqual:NSStringPboardType]
708 && [self askBackendForStarRegister:nil])
711 return [super validRequestorForSendType:sendType returnType:returnType];
714 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
715 types:(NSArray *)types
717 if (![types containsObject:NSStringPboardType])
720 return [self askBackendForStarRegister:pboard];
723 @end // MMWindowController
727 @implementation MMWindowController (Private)
729 - (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize
731 NSSize size = textViewSize;
733 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
734 int right = [ud integerForKey:MMTextInsetRightKey];
735 int bot = [ud integerForKey:MMTextInsetBottomKey];
737 size.width += [textView textContainerOrigin].x + right;
738 size.height += [textView textContainerOrigin].y + bot;
740 if (![tablineSeparator isHidden])
742 if (![tabBarControl isHidden])
743 size.height += [tabBarControl frame].size.height;
745 if ([self bottomScrollbarVisible])
746 size.height += [NSScroller scrollerWidth];
747 if ([self leftScrollbarVisible])
748 size.width += [NSScroller scrollerWidth];
749 if ([self rightScrollbarVisible])
750 size.width += [NSScroller scrollerWidth];
755 - (NSRect)textViewRectForContentSize:(NSSize)contentSize
757 NSRect rect = { 0, 0, contentSize.width, contentSize.height };
759 if (![tablineSeparator isHidden])
761 if (![tabBarControl isHidden])
762 rect.size.height -= [tabBarControl frame].size.height;
764 if ([self bottomScrollbarVisible]) {
765 rect.size.height -= [NSScroller scrollerWidth];
766 rect.origin.y += [NSScroller scrollerWidth];
768 if ([self leftScrollbarVisible]) {
769 rect.size.width -= [NSScroller scrollerWidth];
770 rect.origin.x += [NSScroller scrollerWidth];
772 if ([self rightScrollbarVisible])
773 rect.size.width -= [NSScroller scrollerWidth];
778 - (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize
780 NSSize size = textViewSize;
782 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
783 int right = [ud integerForKey:MMTextInsetRightKey];
784 int bot = [ud integerForKey:MMTextInsetBottomKey];
786 size.width -= [textView textContainerOrigin].x + right;
787 size.height -= [textView textContainerOrigin].y + bot;
792 - (void)resizeWindowToFit:(id)sender
794 if (!setupDone) return;
796 NSWindow *win = [self window];
797 NSRect frame = [win frame];
798 NSRect contentRect = [win contentRectForFrameRect:frame];
799 NSSize newSize = [self contentSizeForTextStorageSize:[textStorage size]];
801 // Keep top-left corner of the window fixed when resizing.
802 contentRect.origin.y -= newSize.height - contentRect.size.height;
803 contentRect.size = newSize;
805 frame = [win frameRectForContentRect:contentRect];
806 NSRect maxFrame = [win constrainFrameRect:frame toScreen:[win screen]];
808 // HACK! Assuming the window frame cannot already be placed too high,
809 // adjust 'maxFrame' so that it at least as high up as the current frame.
810 // The reason for doing this is that constrainFrameRect:toScreen: does not
811 // always seem to utilize as much area as possible.
812 if (NSMaxY(frame) > NSMaxY(maxFrame)) {
813 maxFrame.size.height = frame.origin.y - maxFrame.origin.y
817 if (!NSEqualRects(maxFrame, frame)) {
818 // The new window frame is too big to fit on the screen, so fit the
819 // text storage to the biggest frame which will fit on the screen.
820 //NSLog(@"Proposed window frame does not fit on the screen!");
821 frame = [self fitWindowToFrame:maxFrame];
824 //NSLog(@"%s %@", _cmd, NSStringFromRect(frame));
826 // HACK! If the window does resize, then windowDidResize is called which in
827 // turn calls placeViews. In case the computed new size of the window is
828 // no different from the current size, then we need to call placeViews
830 if (NSEqualRects(frame, [win frame])) {
833 [win setFrame:frame display:YES];
837 - (NSRect)fitWindowToFrame:(NSRect)frame
839 if (!setupDone) return frame;
841 NSWindow *win = [self window];
842 NSRect contentRect = [win contentRectForFrameRect:frame];
843 NSSize size = [self textViewRectForContentSize:contentRect.size].size;
844 size = [self textStorageSizeForTextViewSize:size];
845 size = [textStorage fitToSize:size];
846 size = [self contentSizeForTextStorageSize:size];
848 // Keep top-left corner of 'frame' fixed.
849 contentRect.origin.y -= size.height - contentRect.size.height;
850 contentRect.size = size;
852 return [win frameRectForContentRect:contentRect];
855 - (void)updateResizeIncrements
857 if (!setupDone) return;
859 NSSize size = [textStorage cellSize];
860 [[self window] setContentResizeIncrements:size];
863 - (NSTabViewItem *)addNewTabViewItem
865 // NOTE! A newly created tab is not by selected by default; the VimTask
866 // decides which tab should be selected at all times. However, the AppKit
867 // will automatically select the first tab added to a tab view.
869 NSTabViewItem *tvi = [[NSTabViewItem alloc] initWithIdentifier:nil];
871 // NOTE: If this is the first tab it will be automatically selected.
872 vimTaskSelectedTab = YES;
873 [tabView addTabViewItem:tvi];
874 vimTaskSelectedTab = NO;
881 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
883 NSArray *tabViewItems = [tabBarControl representedTabViewItems];
884 return [tabViewItems indexOfObject:tvi];
887 - (IBAction)vimMenuItemAction:(id)sender
889 int tag = [sender tag];
891 NSMutableData *data = [NSMutableData data];
892 [data appendBytes:&tag length:sizeof(int)];
894 [vimController sendMessage:ExecuteMenuMsgID data:data wait:NO];
897 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx
899 unsigned i, count = [scrollbars count];
900 for (i = 0; i < count; ++i) {
901 MMScroller *scroller = [scrollbars objectAtIndex:i];
902 if ([scroller identifier] == ident) {
911 - (BOOL)bottomScrollbarVisible
913 unsigned i, count = [scrollbars count];
914 for (i = 0; i < count; ++i) {
915 MMScroller *scroller = [scrollbars objectAtIndex:i];
916 if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden])
923 - (BOOL)leftScrollbarVisible
925 unsigned i, count = [scrollbars count];
926 for (i = 0; i < count; ++i) {
927 MMScroller *scroller = [scrollbars objectAtIndex:i];
928 if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden])
935 - (BOOL)rightScrollbarVisible
937 unsigned i, count = [scrollbars count];
938 for (i = 0; i < count; ++i) {
939 MMScroller *scroller = [scrollbars objectAtIndex:i];
940 if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden])
947 - (void)placeScrollbars
949 if (!setupDone) return;
951 NSRect textViewFrame = [textView frame];
952 BOOL lsbVisible = [self leftScrollbarVisible];
954 // HACK! Find the lowest left&right vertical scrollbars, as well as the
955 // rightmost horizontal scrollbar. This hack continues further down.
957 // TODO! Can there be no more than one horizontal scrollbar? If so, the
958 // code can be simplified.
959 unsigned lowestLeftSbIdx = (unsigned)-1;
960 unsigned lowestRightSbIdx = (unsigned)-1;
961 unsigned rightmostSbIdx = (unsigned)-1;
962 unsigned rowMaxLeft = 0, rowMaxRight = 0, colMax = 0;
963 unsigned i, count = [scrollbars count];
964 for (i = 0; i < count; ++i) {
965 MMScroller *scroller = [scrollbars objectAtIndex:i];
966 if (![scroller isHidden]) {
967 NSRange range = [scroller range];
968 if ([scroller type] == MMScrollerTypeLeft
969 && range.location >= rowMaxLeft) {
970 rowMaxLeft = range.location;
972 } else if ([scroller type] == MMScrollerTypeRight
973 && range.location >= rowMaxRight) {
974 rowMaxRight = range.location;
975 lowestRightSbIdx = i;
976 } else if ([scroller type] == MMScrollerTypeBottom
977 && range.location >= colMax) {
978 colMax = range.location;
984 // Place the scrollbars.
985 for (i = 0; i < count; ++i) {
986 MMScroller *scroller = [scrollbars objectAtIndex:i];
987 if ([scroller isHidden])
991 if ([scroller type] == MMScrollerTypeBottom) {
992 rect = [textStorage rectForColumnsInRange:[scroller range]];
993 rect.size.height = [NSScroller scrollerWidth];
995 rect.origin.x += [NSScroller scrollerWidth];
997 // HACK! Make sure the rightmost horizontal scrollbar covers the
998 // text view all the way to the right, otherwise it looks ugly when
999 // the user drags the window to resize.
1000 if (i == rightmostSbIdx) {
1001 float w = NSMaxX(textViewFrame) - NSMaxX(rect);
1003 rect.size.width += w;
1006 // Make sure scrollbar rect is bounded by the text view frame.
1007 if (rect.origin.x < textViewFrame.origin.x)
1008 rect.origin.x = textViewFrame.origin.x;
1009 else if (rect.origin.x > NSMaxX(textViewFrame))
1010 rect.origin.x = NSMaxX(textViewFrame);
1011 if (NSMaxX(rect) > NSMaxX(textViewFrame))
1012 rect.size.width -= NSMaxX(rect) - NSMaxX(textViewFrame);
1013 if (rect.size.width < 0)
1014 rect.size.width = 0;
1016 rect = [textStorage rectForRowsInRange:[scroller range]];
1017 // Adjust for the fact that text layout is flipped.
1018 rect.origin.y = NSMaxY(textViewFrame) - rect.origin.y
1020 rect.size.width = [NSScroller scrollerWidth];
1021 if ([scroller type] == MMScrollerTypeRight)
1022 rect.origin.x = NSMaxX(textViewFrame);
1024 // HACK! Make sure the lowest vertical scrollbar covers the text
1025 // view all the way to the bottom. This is done because Vim only
1026 // makes the scrollbar cover the (vim-)window it is associated with
1027 // and this means there is always an empty gap in the scrollbar
1028 // region next to the command line.
1029 // TODO! Find a nicer way to do this.
1030 if (i == lowestLeftSbIdx || i == lowestRightSbIdx) {
1031 float h = rect.origin.y + rect.size.height
1032 - textViewFrame.origin.y;
1033 if (rect.size.height < h) {
1034 rect.origin.y = textViewFrame.origin.y;
1035 rect.size.height = h;
1039 // Vertical scrollers must not cover the resize box in the
1040 // bottom-right corner of the window.
1041 if (rect.origin.y < [NSScroller scrollerWidth]) {
1042 rect.size.height -= [NSScroller scrollerWidth] - rect.origin.y;
1043 rect.origin.y = [NSScroller scrollerWidth];
1046 // Make sure scrollbar rect is bounded by the text view frame.
1047 if (rect.origin.y < textViewFrame.origin.y) {
1048 rect.size.height -= textViewFrame.origin.y - rect.origin.y;
1049 rect.origin.y = textViewFrame.origin.y;
1050 } else if (rect.origin.y > NSMaxY(textViewFrame))
1051 rect.origin.y = NSMaxY(textViewFrame);
1052 if (NSMaxY(rect) > NSMaxY(textViewFrame))
1053 rect.size.height -= NSMaxY(rect) - NSMaxY(textViewFrame);
1054 if (rect.size.height < 0)
1055 rect.size.height = 0;
1058 //NSLog(@"set scroller #%d frame = %@", i, NSStringFromRect(rect));
1059 NSRect oldRect = [scroller frame];
1060 if (!NSEqualRects(oldRect, rect)) {
1061 [scroller setFrame:rect];
1062 // Clear behind the old scroller frame, or parts of the old
1063 // scroller might still be visible after setFrame:.
1064 [[[self window] contentView] setNeedsDisplayInRect:oldRect];
1065 [scroller setNeedsDisplay:YES];
1070 - (void)scroll:(id)sender
1072 NSMutableData *data = [NSMutableData data];
1073 long ident = [(MMScroller*)sender identifier];
1074 int hitPart = [sender hitPart];
1075 float value = [sender floatValue];
1077 [data appendBytes:&ident length:sizeof(long)];
1078 [data appendBytes:&hitPart length:sizeof(int)];
1079 [data appendBytes:&value length:sizeof(float)];
1081 [vimController sendMessage:ScrollbarEventMsgID data:data wait:NO];
1086 if (!setupDone) return;
1088 // NOTE! It is assumed that the window has been resized so that it will
1089 // exactly fit the text storage (possibly after resizing it). If this is
1090 // not the case the display might be messed up.
1091 NSWindow *win = [self window];
1092 NSRect contentRect = [win contentRectForFrameRect:[win frame]];
1093 NSRect textViewRect = [self textViewRectForContentSize:contentRect.size];
1094 NSSize tsSize = [self textStorageSizeForTextViewSize:textViewRect.size];
1096 int dim[2], rows, cols;
1097 [textStorage getMaxRows:&rows columns:&cols];
1098 [textStorage fitToSize:tsSize rows:&dim[0] columns:&dim[1]];
1100 if (dim[0] != rows || dim[1] != cols) {
1101 //NSLog(@"Notify Vim that text storage dimensions changed to %dx%d",
1103 NSData *data = [NSData dataWithBytes:dim length:2*sizeof(int)];
1105 // NOTE! This can get called a lot when in live resize, which causes
1106 // the connection buffers to fill up. If we wait for the message to be
1107 // sent then the app might become unresponsive.
1108 [vimController sendMessage:SetTextDimensionsMsgID data:data
1109 wait:![textView inLiveResize]];
1112 [textView setFrame:textViewRect];
1114 [self placeScrollbars];
1117 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb
1120 id backendProxy = [vimController backendProxy];
1124 reply = [backendProxy starRegisterToPasteboard:pb];
1126 @catch (NSException *e) {
1127 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1134 @end // MMWindowController (Private)
1138 @implementation NSTabView (MMExtras)
1140 - (void)removeAllTabViewItems
1142 NSArray *existingItems = [self tabViewItems];
1143 NSEnumerator *e = [existingItems objectEnumerator];
1144 NSTabViewItem *item;
1145 while (item = [e nextObject]){
1146 [self removeTabViewItem:item];
1150 @end // NSTabView (MMExtras)
1155 @implementation MMScroller
1157 - (id)initWithIdentifier:(long)ident type:(int)theType
1159 // HACK! NSScroller creates a horizontal scroller if it is init'ed with a
1160 // frame whose with exceeds its height; so create a bogus rect and pass it
1161 // to initWithFrame.
1162 NSRect frame = theType == MMScrollerTypeBottom
1163 ? NSMakeRect(0, 0, 1, 0)
1164 : NSMakeRect(0, 0, 0, 1);
1166 if ((self = [super initWithFrame:frame])) {
1169 [self setHidden:YES];
1170 [self setEnabled:YES];
1191 - (void)setRange:(NSRange)newRange
1196 - (void)scrollWheel:(NSEvent *)event
1198 // HACK! Pass message on to the text view.
1199 MMWindowController *wc = [[self window] windowController];
1200 [[wc textView] scrollWheel:event];