- Request/reply timeout set on root connection object
[MacVim/jjgod.git] / MMWindowController.m
blob46bf965a843b19e17806cf5a4ca7dd7d54ea3124
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
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.
9  */
11 #import "MMWindowController.h"
12 #import <PSMTabBarControl.h>
13 #import "MMTextView.h"
14 #import "MMTextStorage.h"
15 #import "MMVimController.h"
16 #import "MacVim.h"
17 #import "MMAppController.h"
18 #import "MMTypesetter.h"
21 // Scroller type; these must match SBAR_* in gui.h
22 enum {
23     MMScrollerTypeLeft = 0,
24     MMScrollerTypeRight,
25     MMScrollerTypeBottom
28 // NOTE!  This value must match the actual position of the status line
29 // separator in VimWindow.nib.
30 static float StatusLineHeight = 16.0f;
33 // TODO:  Move!
34 @interface NSTabView (MMExtras)
35 - (void)removeAllTabViewItems;
36 @end
39 // TODO:  Move!
40 @interface MMScroller : NSScroller {
41     long identifier;
42     int type;
43     NSRange range;
45 - (id)initWithIdentifier:(long)ident type:(int)type;
46 - (long)identifier;
47 - (int)type;
48 - (NSRange)range;
49 - (void)setRange:(NSRange)newRange;
50 @end
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;
68 - (void)placeViews;
69 @end
73 #if 0
74 NSString *buildMenuItemDescriptor(NSMenu *menu, NSString *tail)
76     return menu ? buildMenuItemDescriptor([menu supermenu], [[menu title]
77                     stringByAppendingString:tail])
78                 : tail;
81 NSMutableArray *buildMenuAddress(NSMenu *menu)
83     NSMutableArray *addr;
84     if (menu) {
85         addr = buildMenuAddress([menu supermenu]);
86         [addr addObject:[menu title]];
87     } else {
88         addr = [NSMutableArray array];
89     }
91     return addr;
93 #endif
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];
119         } else {
120             // Only MMTypesetter supports different cell width multipliers.
121             [[NSUserDefaults standardUserDefaults]
122                     setFloat:1.0 forKey:MMCellWidthMultiplierKey];
123         }
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]
134                                        textContainer:tc];
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.
145         [tc release];
146         [lm release];
148         // Create the tabline separator (which may be visible when the tabline
149         // is hidden).
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
184             | NSViewMinYMargin];
186         [contentView setAutoresizesSubviews:YES];
187         [contentView addSubview:tabBarControl];
188         [contentView addSubview:tablineSeparator];
190         [[self window] setDelegate:self];
191         [[self window] setInitialFirstResponder:textView];
192     }
194     return self;
197 - (void)dealloc
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;
209     [super dealloc];
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
224     return textView;
227 - (MMTextStorage *)textStorage
229     return textStorage;
232 - (NSString *)windowAutosaveKey
234     return windowAutosaveKey;
237 - (void)setWindowAutosaveKey:(NSString *)key
239     [windowAutosaveKey autorelease];
240     windowAutosaveKey = [key copy];
243 - (void)cleanup
245     //NSLog(@"%@ %s", [self className], _cmd);
247     setupDone = NO;
248     vimController = nil;
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
253     // memory leak.
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];
272     }
274     [tabView removeAllTabViewItems];
276     [[self window] orderOut:self];
279 - (void)openWindow
281     [[NSApp delegate] windowControllerWillOpen:self];
283     [self addNewTabViewItem];
285     setupDone = YES;
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];
296     int tabIdx = 0;
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];
305     while (p < end) {
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
311                              length:length
312                            encoding:NSUTF8StringEncoding
313                        freeWhenDone:NO];
314         p += length;
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];
323         [label release];
325         ++tabIdx;
326     }
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];
338     }
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);
351         return;
352     }
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;
360     }
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
378                                                              type:type];
379     [scroller setTarget:self];
380     [scroller setAction:@selector(scroll:)];
382     [[[self window] contentView] addSubview:scroller];
383     [scrollbars addObject:scroller];
384     [scroller release];
387 - (void)destroyScrollbarWithIdentifier:(long)ident
389     //NSLog(@"Destroy scroller %d", ident);
391     unsigned idx = 0;
392     MMScroller *scroller = [self scrollbarForIdentifier:ident index:&idx];
393     if (scroller) {
394         [scroller removeFromSuperview];
395         [scrollbars removeObjectAtIndex:idx];
397         if (![scroller isHidden]) {
398             // A visible scroller was removed, so the window must resize to
399             // fit.
400             //NSLog(@"Visible scroller %d was destroyed, resizing window.",
401             //        ident);
402             shouldUpdateWindowSize = YES;
403         }
404     }
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",
420         //        ident);
421         shouldUpdateWindowSize = YES;
422     }
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];
435     }
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];
464     }
467 - (void)popupMenu:(NSMenu *)menu atRow:(int)row column:(int)col
469     if (!setupDone) return;
471     NSEvent *event;
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
478                                    location:pt
479                               modifierFlags:0
480                                   timestamp:0
481                                windowNumber:[[self window] windowNumber]
482                                     context:nil
483                                 eventNumber:0
484                                  clickCount:0
485                                    pressure:1.0];
486     } else {
487         event = [textView lastMouseDownEvent];
488     }
490     [NSMenu popUpContextMenu:menu withEvent:event forView:textView];
493 - (void)showTabBar:(BOOL)on
495     [tabBarControl setHidden:!on];
497     if (!on) {
498         NSToolbar *toolbar = [[self window] toolbar]; 
499         [tablineSeparator setHidden:![toolbar isVisible]];
500     } else {
501         [tablineSeparator setHidden:on];
502     }
504     if (setupDone)
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];
517     if (!on) {
518         [tablineSeparator setHidden:YES];
519     } else {
520         [tablineSeparator setHidden:![tabBarControl isHidden]];
521     }
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];
555         }
556     }
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];
570     return NO;
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];
600     return NO;
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];
612     }
615 - (void)windowDidResize:(id)sender
617     if (!setupDone) return;
618     [self placeViews];
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;
638     }
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;
650     }
652     return frame;
658 // -- Services menu delegate -------------------------------------------------
660 - (id)validRequestorForSendType:(NSString *)sendType
661                      returnType:(NSString *)returnType
663     id backendProxy = [vimController backendProxy];
665     if (backendProxy && [sendType isEqual:NSStringPboardType]) {
666         @try {
667             if ([backendProxy starRegisterToPasteboard:nil])
668                 return self;
669         }
670         @catch (NSException *e) {
671             NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
672         }
673     }
675     return [super validRequestorForSendType:sendType returnType:returnType];
678 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
679                              types:(NSArray *)types
681     if (![types containsObject:NSStringPboardType])
682         return NO;
684     id backendProxy = [vimController backendProxy];
685     if (backendProxy) {
686         @try {
687             return [backendProxy starRegisterToPasteboard:pboard];
688         }
689         @catch (NSException *e) {
690             NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
691         }
692     }
694     return NO;
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])
715         ++size.height;
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];
729     return size;
732 - (NSRect)textViewRectForContentSize:(NSSize)contentSize
734     NSRect rect = { 0, 0, contentSize.width, contentSize.height };
736     if (![tablineSeparator isHidden])
737         --rect.size.height;
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;
745     }
747     if ([self bottomScrollbarVisible]) {
748         rect.size.height -= [NSScroller scrollerWidth];
749         rect.origin.y += [NSScroller scrollerWidth];
750     }
751     if ([self leftScrollbarVisible]) {
752         rect.size.width -= [NSScroller scrollerWidth];
753         rect.origin.x += [NSScroller scrollerWidth];
754     }
755     if ([self rightScrollbarVisible])
756         rect.size.width -= [NSScroller scrollerWidth];
758     return rect;
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;
772     return size;
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
797                 + frame.size.height;
798     }
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];
805     }
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
812     // manually.
813     if (NSEqualRects(frame, [win frame])) {
814         [self placeViews];
815     } else {
816         [win setFrame:frame display:YES];
817     }
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;
859     [tvi release];
861     return tvi;
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) {
886             if (idx) *idx = i;
887             return scroller;
888         }
889     }
891     return nil;
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])
900             return YES;
901     }
903     return NO;
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])
912             return YES;
913     }
915     return NO;
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])
924             return YES;
925     }
927     return NO;
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.
941     //
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;
956                 lowestLeftSbIdx = i;
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;
964                 rightmostSbIdx = i;
965             }
966         }
967     }
969     // Place the scrollbars.
970     for (i = 0; i < count; ++i) {
971         MMScroller *scroller = [scrollbars objectAtIndex:i];
972         if ([scroller isHidden])
973             continue;
975         NSRect rect;
976         if ([scroller type] == MMScrollerTypeBottom) {
977             rect = [textStorage rectForColumnsInRange:[scroller range]];
978             rect.size.height = [NSScroller scrollerWidth];
979             if (statusVisible)
980                 rect.origin.y += StatusLineHeight;
981             if (lsbVisible)
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);
989                 if (w > 0)
990                     rect.size.width += w;
991             }
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;
1002         } else {
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
1006                     - rect.size.height;
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;
1023                 }
1024             }
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];
1031             }
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;
1043         }
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];
1053         }
1054     }
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];
1071 - (void)placeViews
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",
1089         //        dim[0], dim[1]);
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]];
1097     }
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];
1117     }
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])) {
1137         identifier = ident;
1138         type = theType;
1139         [self setHidden:YES];
1140         [self setEnabled:YES];
1141     }
1143     return self;
1146 - (long)identifier
1148     return identifier;
1151 - (int)type
1153     return type;
1156 - (NSRange)range
1158     return range;
1161 - (void)setRange:(NSRange)newRange
1163     range = 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];
1173 @end // MMScroller