Added custom typesetter (better wide-font support, can typeset proportional
[MacVim/jjgod.git] / MMWindowController.m
blob2f3c863e126dba5c6047e24e80bb549f5f5aebd8
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 #define MM_USE_CUSTOM_TYPESETTER 1
13 #import "MMWindowController.h"
14 #import <PSMTabBarControl.h>
15 #import "MMTextView.h"
16 #import "MMTextStorage.h"
17 #import "MMVimController.h"
18 #import "MacVim.h"
19 #import "MMAppController.h"
21 #if MM_USE_CUSTOM_TYPESETTER
22 # import "MMTypesetter.h"
23 #endif
26 // Scroller type; these must match SBAR_* in gui.h
27 enum {
28     MMScrollerTypeLeft = 0,
29     MMScrollerTypeRight,
30     MMScrollerTypeBottom
33 // NOTE!  This value must match the actual position of the status line
34 // separator in VimWindow.nib.
35 static float StatusLineHeight = 16.0f;
38 // TODO:  Move!
39 @interface NSTabView (MMExtras)
40 - (void)removeAllTabViewItems;
41 @end
44 // TODO:  Move!
45 @interface MMScroller : NSScroller {
46     long identifier;
47     int type;
48     NSRange range;
50 - (id)initWithIdentifier:(long)ident type:(int)type;
51 - (long)identifier;
52 - (int)type;
53 - (NSRange)range;
54 - (void)setRange:(NSRange)newRange;
55 @end
57 @interface MMWindowController (Private)
58 - (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize;
59 - (NSRect)textViewRectForContentSize:(NSSize)contentSize;
60 - (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize;
61 - (void)resizeWindowToFit:(id)sender;
62 - (NSRect)fitWindowToFrame:(NSRect)frame;
63 - (void)updateResizeIncrements;
64 - (NSTabViewItem *)addNewTabViewItem;
65 - (void)statusTimerFired:(NSTimer *)timer;
66 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi;
67 - (IBAction)vimMenuItemAction:(id)sender;
68 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx;
69 - (BOOL)bottomScrollbarVisible;
70 - (BOOL)leftScrollbarVisible;
71 - (BOOL)rightScrollbarVisible;
72 - (void)placeScrollbars;
73 - (void)scroll:(id)sender;
74 - (void)placeViews;
75 @end
79 #if 0
80 NSString *buildMenuItemDescriptor(NSMenu *menu, NSString *tail)
82     return menu ? buildMenuItemDescriptor([menu supermenu], [[menu title]
83                     stringByAppendingString:tail])
84                 : tail;
87 NSMutableArray *buildMenuAddress(NSMenu *menu)
89     NSMutableArray *addr;
90     if (menu) {
91         addr = buildMenuAddress([menu supermenu]);
92         [addr addObject:[menu title]];
93     } else {
94         addr = [NSMutableArray array];
95     }
97     return addr;
99 #endif
102 @implementation MMWindowController
104 - (id)initWithVimController:(MMVimController *)controller
106     if ((self = [super initWithWindowNibName:@"VimWindow"])) {
107         vimController = controller;
108         scrollbars = [[NSMutableArray alloc] init];
110         // Setup a complete text system.
111         textStorage = [[MMTextStorage alloc] init];
112         NSLayoutManager *lm = [[NSLayoutManager alloc] init];
113         NSTextContainer *tc = [[NSTextContainer alloc] initWithContainerSize:
114                 NSMakeSize(1.0e7,1.0e7)];
116 #if MM_USE_CUSTOM_TYPESETTER
117         MMTypesetter *typesetter = [[MMTypesetter alloc] init];
118         [lm setTypesetter:typesetter];
119         [typesetter release];
120 #endif
122         [tc setWidthTracksTextView:NO];
123         [tc setHeightTracksTextView:NO];
124         [tc setLineFragmentPadding:0];
126         [textStorage addLayoutManager:lm];
127         [lm addTextContainer:tc];
129         textView = [[MMTextView alloc] initWithFrame:NSZeroRect
130                                        textContainer:tc];
132         NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
133         int left = [ud integerForKey:MMTextInsetLeft];
134         int top = [ud integerForKey:MMTextInsetTop];
135         [textView setTextContainerInset:NSMakeSize(left, top)];
137         // The text storage retains the layout manager which in turn retains
138         // the text container.
139         [tc release];
140         [lm release];
141     }
143     return self;
146 - (void)dealloc
148     //NSLog(@"%@ %s", [self className], _cmd);
150     // TODO: release tabBarControl and tabView?
152     vimController = nil;
154     [tabBarControl setDelegate:nil];
155     [[self window] setDelegate:nil];
157     [tabView removeAllTabViewItems];
159     [scrollbars release];
160     [textView release];
161     [textStorage release];
163     [super dealloc];
166 - (void)windowDidLoad
168     // Called after window nib file is loaded.
170     [tablineSeparator setHidden:NO];
171     [tabBarControl setHidden:YES];
173     // NOTE: Size to fit looks good, but not many tabs will fit and there are
174     // quite a few drawing bugs in this code, so it is disabled for now.
175     //[tabBarControl setSizeCellsToFit:YES];
177     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
178     [tabBarControl setCellMinWidth:[ud integerForKey:MMTabMinWidthKey]];
179     [tabBarControl setCellMaxWidth:[ud integerForKey:MMTabMaxWidthKey]];
180     [tabBarControl setCellOptimumWidth:[ud integerForKey:MMTabOptimumWidthKey]];
182     [tabBarControl setAllowsDragBetweenWindows:NO];
183     [tabBarControl setShowAddTabButton:YES];
184     [[tabBarControl addTabButton] setTarget:self];
185     [[tabBarControl addTabButton] setAction:@selector(addNewTab:)];
187     // HACK! remove any tabs present in the nib
188     [tabView removeAllTabViewItems];
190     // HACK!  These observers used to be set in the designated init of
191     // MMVimController, but this occasionally caused exceptions from within the
192     // AppKit to be raised.  The problem seemed related to the fact that the
193     // window got loaded 'too early'; adding the observers here seems to
194     // alleviate this problem.
195     [[NSNotificationCenter defaultCenter]
196             addObserver:vimController
197                selector:@selector(windowWillClose:)
198                    name:NSWindowWillCloseNotification
199                  object:[self window]];
200     [[NSNotificationCenter defaultCenter]
201             addObserver:vimController
202                selector:@selector(windowDidBecomeMain:)
203                    name:NSWindowDidBecomeMainNotification
204                  object:[self window]];
207 - (MMVimController *)vimController
209     return vimController;
212 - (MMTextView *)textView
214     return textView;
217 - (MMTextStorage *)textStorage
219     return textStorage;
222 - (void)openWindow
224     [self addNewTabViewItem];
226     // NOTE! This flag is set once the entire text system is set up.
227     setupDone = YES;
229     [self updateResizeIncrements];
230     [self resizeWindowToFit:self];
232     [[self window] makeKeyAndOrderFront:self];
234     BOOL statusOff = [[NSUserDefaults standardUserDefaults]
235                     boolForKey:MMStatuslineOffKey];
236     [statusTextField setHidden:statusOff];
237     [statusSeparator setHidden:statusOff];
238     [self flashStatusText:@"Welcome to MacVim!"];
241 - (void)updateTabsWithData:(NSData *)data
243     const void *p = [data bytes];
244     const void *end = p + [data length];
245     int tabIdx = 0;
247     // HACK!  Current tab is first in the message.  This way it is not
248     // necessary to guess which tab should be the selected one (this can be
249     // problematic for instance when new tabs are created).
250     int curtabIdx = *((int*)p);  p += sizeof(int);
252     NSArray *tabViewItems = [tabBarControl representedTabViewItems];
254     while (p < end) {
255         //int wincount = *((int*)p);  p += sizeof(int);
256         int length = *((int*)p);  p += sizeof(int);
258         NSString *label = [[NSString alloc]
259                 initWithBytesNoCopy:(void*)p
260                              length:length
261                            encoding:NSUTF8StringEncoding
262                        freeWhenDone:NO];
263         p += length;
265         // Set the label of the tab;  add a new tab when needed.
266         NSTabViewItem *tvi = [tabView numberOfTabViewItems] <= tabIdx
267                 ? [self addNewTabViewItem]
268                 : [tabViewItems objectAtIndex:tabIdx];
270         [tvi setLabel:label];
272         [label release];
274         ++tabIdx;
275     }
277     // Remove unused tabs from the NSTabView.  Note that when a tab is closed
278     // the NSTabView will automatically select another tab, but we want Vim to
279     // take care of which tab to select so set the vimTaskSelectedTab flag to
280     // prevent the tab selection message to be passed on to the VimTask.
281     vimTaskSelectedTab = YES;
282     int i, count = [tabView numberOfTabViewItems];
283     for (i = count-1; i >= tabIdx; --i) {
284         id tvi = [tabViewItems objectAtIndex:i];
285         //NSLog(@"Removing tab with index %d", i);
286         [tabView removeTabViewItem:tvi];
287     }
288     vimTaskSelectedTab = NO;
290     [self selectTabWithIndex:curtabIdx];
293 - (void)selectTabWithIndex:(int)idx
295     //NSLog(@"%s%d", _cmd, idx);
297     NSArray *tabViewItems = [tabBarControl representedTabViewItems];
298     if (idx < 0 || idx >= [tabViewItems count]) {
299         NSLog(@"WARNING: No tab with index %d exists.", idx);
300         return;
301     }
303     // Do not try to select a tab if already selected.
304     NSTabViewItem *tvi = [tabViewItems objectAtIndex:idx];
305     if (tvi != [tabView selectedTabViewItem]) {
306         vimTaskSelectedTab = YES;
307         [tabView selectTabViewItem:tvi];
308         vimTaskSelectedTab = NO;
309     }
312 - (void)setTextDimensionsWithRows:(int)rows columns:(int)cols
314     //NSLog(@"setTextDimensionsWithRows:%d columns:%d", rows, cols);
316     [textStorage setMaxRows:rows columns:cols];
318     if (setupDone && ![textView inLiveResize])
319         shouldUpdateWindowSize = YES;
322 - (void)setStatusText:(NSString *)text
324     if (text)
325         [statusTextField setStringValue:text];
326     else
327         [statusTextField setStringValue:@""];
330 - (void)flashStatusText:(NSString *)text
332     if ([[NSUserDefaults standardUserDefaults] boolForKey:MMStatuslineOffKey])
333         return;
335     [self setStatusText:text];
337     if (statusTimer) {
338         [statusTimer invalidate];
339         [statusTimer release];
340     }
342     statusTimer = [[NSTimer scheduledTimerWithTimeInterval:3
343                               target:self
344                             selector:@selector(statusTimerFired:)
345                             userInfo:nil
346                              repeats:NO] retain];
349 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
351     //NSLog(@"Create scroller %d of type %d", ident, type);
353     MMScroller *scroller = [[MMScroller alloc] initWithIdentifier:ident
354                                                              type:type];
355     [scroller setTarget:self];
356     [scroller setAction:@selector(scroll:)];
358     [[[self window] contentView] addSubview:scroller];
359     [scrollbars addObject:scroller];
360     [scroller release];
363 - (void)destroyScrollbarWithIdentifier:(long)ident
365     //NSLog(@"Destroy scroller %d", ident);
367     unsigned idx = 0;
368     MMScroller *scroller = [self scrollbarForIdentifier:ident index:&idx];
369     if (scroller) {
370         [scroller removeFromSuperview];
371         [scrollbars removeObjectAtIndex:idx];
373         if (![scroller isHidden]) {
374             // A visible scroller was removed, so the window must resize to
375             // fit.
376             //NSLog(@"Visible scroller %d was destroyed, resizing window.",
377             //        ident);
378             shouldUpdateWindowSize = YES;
379         }
380     }
383 - (void)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
385     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
386     if (!scroller) return;
388     BOOL wasVisible = ![scroller isHidden];
389     //NSLog(@"%s scroller %d (was %svisible)", visible ? "Show" : "Hide",
390     //      ident, wasVisible ? "" : "in");
391     [scroller setHidden:!visible];
393     if (wasVisible != visible) {
394         // A scroller was hidden or shown, so the window must resize to fit.
395         //NSLog(@"%s scroller %d and resize.", visible ? "Show" : "Hide",
396         //        ident);
397         shouldUpdateWindowSize = YES;
398     }
401 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
403     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
404     NSRange range = NSMakeRange(pos, len);
405     if (!NSEqualRanges(range, [scroller range])) {
406         //NSLog(@"Set range %@ for scroller %d",
407         //        NSStringFromRange(range), ident);
408         [scroller setRange:range];
409         // TODO!  Should only do this once per update.
410         [self placeScrollbars];
411     }
414 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
415                     identifier:(long)ident
417     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
418     //NSLog(@"Set thumb value %.2f proportion %.2f for scroller %d",
419     //        val, prop, ident);
420     [scroller setFloatValue:val knobProportion:prop];
423 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
425     [textStorage setDefaultColorsBackground:back foreground:fore];
426     [textView setBackgroundColor:back];
429 - (void)setFont:(NSFont *)font
431     [textStorage setFont:font];
432     [self updateResizeIncrements];
435 - (void)processCommandQueueDidFinish
437     if (shouldUpdateWindowSize) {
438         shouldUpdateWindowSize = NO;
439         [self resizeWindowToFit:self];
440     }
443 - (IBAction)addNewTab:(id)sender
445     // NOTE! This can get called a lot if the user holds down the key
446     // equivalent for this action, which causes the ports to fill up.  If we
447     // wait for the message to be sent then the app might become unresponsive.
448     [vimController sendMessage:AddNewTabMsgID data:nil wait:NO];
451 - (IBAction)showTabBar:(id)sender
453     [tablineSeparator setHidden:YES];
454     [tabBarControl setHidden:NO];
456     if (setupDone)
457         shouldUpdateWindowSize = YES;
460 - (IBAction)hideTabBar:(id)sender
462     [tablineSeparator setHidden:NO];
463     [tabBarControl setHidden:YES];
465     if (setupDone)
466         shouldUpdateWindowSize = YES;
472 // -- PSMTabBarControl delegate ----------------------------------------------
475 - (void)tabView:(NSTabView *)theTabView didSelectTabViewItem:
476         (NSTabViewItem *)tabViewItem
478     // HACK!  There seem to be a bug in NSTextView which results in the first
479     // responder not being set to the view of the tab item so it is done
480     // manually here.
481     [[self window] makeFirstResponder:[tabViewItem view]];
483     // HACK!  The selection message should not be propagated to the VimTask if
484     // the VimTask selected the tab (e.g. as opposed the user clicking the
485     // tab).  The delegate method has no way of knowing who initiated the
486     // selection so a flag is set when the VimTask initiated the selection.
487     if (!vimTaskSelectedTab) {
488         // Propagate the selection message to the VimTask.
489         int idx = [self representedIndexOfTabViewItem:tabViewItem];
490         if (NSNotFound != idx) {
491             NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
492             [vimController sendMessage:SelectTabMsgID data:data wait:YES];
493         }
494     }
497 - (BOOL)tabView:(NSTabView *)theTabView shouldCloseTabViewItem:
498         (NSTabViewItem *)tabViewItem
500     // HACK!  This method is only called when the user clicks the close button
501     // on the tab.  Instead of letting the tab bar close the tab, we return NO
502     // and pass a message on to Vim to let it handle the closing.
503     int idx = [self representedIndexOfTabViewItem:tabViewItem];
504     //NSLog(@"Closing tab with index %d", idx);
505     NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
506     [vimController sendMessage:CloseTabMsgID data:data wait:YES];
508     return NO;
511 - (void)tabView:(NSTabView *)theTabView didDragTabViewItem:
512         (NSTabViewItem *)tabViewItem toIndex:(int)idx
514     NSMutableData *data = [NSMutableData data];
515     [data appendBytes:&idx length:sizeof(int)];
517     [vimController sendMessage:DraggedTabMsgID data:data wait:YES];
523 // -- NSWindow delegate ------------------------------------------------------
526 - (BOOL)windowShouldClose:(id)sender
528     [vimController sendMessage:VimShouldCloseMsgID data:nil wait:YES];
529     return NO;
532 - (void)windowWillClose:(NSNotification *)notification
534     //NSLog(@"%@ %s", [self className], _cmd);
536     // NOTE! There is a bug in PSMTabBarControl in that it retains the delegate
537     // (which is the MMWindowController) so reset the delegate here, otherwise
538     // the MMWindowController never gets released resulting in a pretty serious
539     // memory leak.
540     [tabBarControl setDelegate:nil];
543 - (void)windowDidResize:(id)sender
545     if (!setupDone) return;
546     [self placeViews];
549 - (NSRect)windowWillUseStandardFrame:(NSWindow *)win
550                         defaultFrame:(NSRect)frame
552     // HACK!  For some reason 'frame' is not always constrained to fit on the
553     // screen (e.g. it may overlap the menu bar), so first constrain it to the
554     // screen; otherwise the new frame we compute may be too large and this
555     // will mess up the display after the window resizes.
556     frame = [win constrainFrameRect:frame toScreen:[win screen]];
558     // HACK!  If the top of 'frame' is lower than the current window frame,
559     // increase 'frame' so that their tops align.  Really, 'frame' should
560     // already have its top at least as high as the current window frame, but
561     // for some reason this is not always the case.
562     // (See resizeWindowToFit: for a similar hack.)
563     NSRect cur = [win frame];
564     if (NSMaxY(cur) > NSMaxY(frame)) {
565         frame.size.height = cur.origin.y - frame.origin.y + cur.size.height;
566     }
568     frame = [self fitWindowToFrame:frame];
570     // Keep old width and horizontal position unless user clicked with the
571     // Command key is held down.
572     NSEvent *event = [NSApp currentEvent];
573     if (!([event type] == NSLeftMouseUp
574             && [event modifierFlags] & NSCommandKeyMask)) {
575         NSRect currentFrame = [win frame];
576         frame.size.width = currentFrame.size.width;
577         frame.origin.x = currentFrame.origin.x;
578     }
580     return frame;
586 // -- Services menu delegate -------------------------------------------------
588 - (id)validRequestorForSendType:(NSString *)sendType
589                      returnType:(NSString *)returnType
591     //NSLog(@"validRequestorForSendType:%@ returnType:%@", sendType, returnType);
593     id backendProxy = [vimController backendProxy];
595     if ((!sendType || [sendType isEqual:NSStringPboardType])
596             && (!returnType || [returnType isEqual:NSStringPboardType]))
597     {
598         if ((!sendType || [backendProxy starRegisterToPasteboard:nil]) &&
599                 (!returnType || [backendProxy starRegisterFromPasteboard:nil]))
600         {
601             return self;
602         }
603     }
605     return [super validRequestorForSendType:sendType returnType:returnType];
608 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
609                              types:(NSArray *)types
611     //NSLog(@"writeSelectionToPasteboard:%@ types:%@", pboard, types);
613     if (![types containsObject:NSStringPboardType])
614         return NO;
616     id backendProxy = [vimController backendProxy];
617     return [backendProxy starRegisterToPasteboard:pboard];
620 @end // MMWindowController
624 @implementation MMWindowController (Private)
626 - (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize
628     NSSize size = textViewSize;
630     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
631     int right = [ud integerForKey:MMTextInsetRight];
632     int bot = [ud integerForKey:MMTextInsetBottom];
634     size.width += [textView textContainerOrigin].x + right;
635     size.height += [textView textContainerOrigin].y + bot;
637     // A one pixel high separator is shown if tabline is hidden.
638     if ([tabBarControl isHidden]) ++size.height;
639     else size.height += [tabBarControl frame].size.height;
641     if (![[NSUserDefaults standardUserDefaults] boolForKey:MMStatuslineOffKey])
642         size.height += StatusLineHeight;
644     if ([self bottomScrollbarVisible])
645         size.height += [NSScroller scrollerWidth];
646     if ([self leftScrollbarVisible])
647         size.width += [NSScroller scrollerWidth];
648     if ([self rightScrollbarVisible])
649         size.width += [NSScroller scrollerWidth];
651     return size;
654 - (NSRect)textViewRectForContentSize:(NSSize)contentSize
656     NSRect rect = { 0, 0, contentSize.width, contentSize.height };
658     // A one pixel high separator is shown if tabline is hidden.
659     if ([tabBarControl isHidden]) --rect.size.height;
660     else rect.size.height -= [tabBarControl frame].size.height;
662     if (![[NSUserDefaults standardUserDefaults]
663             boolForKey:MMStatuslineOffKey]) {
664         rect.size.height -= StatusLineHeight;
665         rect.origin.y += StatusLineHeight;
666     }
668     if ([self bottomScrollbarVisible]) {
669         rect.size.height -= [NSScroller scrollerWidth];
670         rect.origin.y += [NSScroller scrollerWidth];
671     }
672     if ([self leftScrollbarVisible]) {
673         rect.size.width -= [NSScroller scrollerWidth];
674         rect.origin.x += [NSScroller scrollerWidth];
675     }
676     if ([self rightScrollbarVisible])
677         rect.size.width -= [NSScroller scrollerWidth];
679     return rect;
682 - (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize
684     NSSize size = textViewSize;
686     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
687     int right = [ud integerForKey:MMTextInsetRight];
688     int bot = [ud integerForKey:MMTextInsetBottom];
690     size.width -= [textView textContainerOrigin].x + right;
691     size.height -= [textView textContainerOrigin].y + bot;
693     return size;
696 - (void)resizeWindowToFit:(id)sender
698     if (!setupDone) return;
700     NSWindow *win = [self window];
701     NSRect frame = [win frame];
702     NSRect contentRect = [win contentRectForFrameRect:frame];
703     NSSize newSize = [self contentSizeForTextStorageSize:[textStorage size]];
705     // Keep top-left corner of the window fixed when resizing.
706     contentRect.origin.y -= newSize.height - contentRect.size.height;
707     contentRect.size = newSize;
709     frame = [win frameRectForContentRect:contentRect];
710     NSRect maxFrame = [win constrainFrameRect:frame toScreen:[win screen]];
712     // HACK!  Assuming the window frame cannot already be placed too high,
713     // adjust 'maxFrame' so that it at least as high up as the current frame.
714     // The reason for doing this is that constrainFrameRect:toScreen: does not
715     // always seem to utilize as much area as possible.
716     if (NSMaxY(frame) > NSMaxY(maxFrame)) {
717         maxFrame.size.height = frame.origin.y - maxFrame.origin.y
718                 + frame.size.height;
719     }
721     if (!NSEqualRects(maxFrame, frame)) {
722         // The new window frame is too big to fit on the screen, so fit the
723         // text storage to the biggest frame which will fit on the screen.
724         //NSLog(@"Proposed window frame does not fit on the screen!");
725         frame = [self fitWindowToFrame:maxFrame];
726     }
728     //NSLog(@"%s %@", _cmd, NSStringFromRect(frame));
730     // HACK! If the window does resize, then windowDidResize is called which in
731     // turn calls placeViews.  In case the computed new size of the window is
732     // no different from the current size, then we need to call placeViews
733     // manually.
734     if (NSEqualRects(frame, [win frame])) {
735         [self placeViews];
736     } else {
737         [win setFrame:frame display:YES];
738     }
741 - (NSRect)fitWindowToFrame:(NSRect)frame
743     if (!setupDone) return frame;
745     NSWindow *win = [self window];
746     NSRect contentRect = [win contentRectForFrameRect:frame];
747     NSSize size = [self textViewRectForContentSize:contentRect.size].size;
748     size = [self textStorageSizeForTextViewSize:size];
749     size = [textStorage fitToSize:size];
750     size = [self contentSizeForTextStorageSize:size];
752     // Keep top-left corner of 'frame' fixed.
753     contentRect.origin.y -= size.height - contentRect.size.height;
754     contentRect.size = size;
756     return [win frameRectForContentRect:contentRect];
759 - (void)updateResizeIncrements
761     if (!setupDone) return;
763     NSSize size = [textStorage calculateAverageFontSize];
764     [[self window] setContentResizeIncrements:size];
767 - (NSTabViewItem *)addNewTabViewItem
769     // NOTE!  A newly created tab is not by selected by default; the VimTask
770     // decides which tab should be selected at all times.  However, the AppKit
771     // will automatically select the first tab added to a tab view.
773     NSTabViewItem *tvi = [[NSTabViewItem alloc] initWithIdentifier:nil];
774     [tvi setView:textView];
776     // BUG!  This call seems to have no effect; see comment in
777     // tabView:didSelectTabViewItem:.
778     //[tvi setInitialFirstResponder:textView];
780     // NOTE: If this is the first tab it will be automatically selected.
781     vimTaskSelectedTab = YES;
782     [tabView addTabViewItem:tvi];
783     vimTaskSelectedTab = NO;
785     [tvi release];
787     return tvi;
790 - (void)statusTimerFired:(NSTimer *)timer
792     [self setStatusText:@""];
793     [statusTimer release];
794     statusTimer = nil;
797 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
799     NSArray *tabViewItems = [tabBarControl representedTabViewItems];
800     return [tabViewItems indexOfObject:tvi];
803 - (IBAction)vimMenuItemAction:(id)sender
805     int tag = [sender tag];
807     NSMutableData *data = [NSMutableData data];
808     [data appendBytes:&tag length:sizeof(int)];
810     [vimController sendMessage:ExecuteMenuMsgID data:data wait:NO];
813 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx
815     unsigned i, count = [scrollbars count];
816     for (i = 0; i < count; ++i) {
817         MMScroller *scroller = [scrollbars objectAtIndex:i];
818         if ([scroller identifier] == ident) {
819             if (idx) *idx = i;
820             return scroller;
821         }
822     }
824     return nil;
827 - (BOOL)bottomScrollbarVisible
829     unsigned i, count = [scrollbars count];
830     for (i = 0; i < count; ++i) {
831         MMScroller *scroller = [scrollbars objectAtIndex:i];
832         if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden])
833             return YES;
834     }
836     return NO;
839 - (BOOL)leftScrollbarVisible
841     unsigned i, count = [scrollbars count];
842     for (i = 0; i < count; ++i) {
843         MMScroller *scroller = [scrollbars objectAtIndex:i];
844         if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden])
845             return YES;
846     }
848     return NO;
851 - (BOOL)rightScrollbarVisible
853     unsigned i, count = [scrollbars count];
854     for (i = 0; i < count; ++i) {
855         MMScroller *scroller = [scrollbars objectAtIndex:i];
856         if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden])
857             return YES;
858     }
860     return NO;
863 - (void)placeScrollbars
865     if (!setupDone) return;
867     NSRect tabViewFrame = [tabView frame];
868     NSView *contentView = [[self window] contentView];
869     BOOL lsbVisible = [self leftScrollbarVisible];
870     BOOL statusVisible = ![[NSUserDefaults standardUserDefaults]
871             boolForKey:MMStatuslineOffKey];
873     // HACK!  Find the lowest left&right vertical scrollbars, as well as the
874     // leftmost horizontal scrollbar.  This hack continues further down.
875     //
876     // TODO!  Can there be no more than one horizontal scrollbar?  If so, the
877     // code can be simplified.
878     unsigned lowestLeftSbIdx = (unsigned)-1;
879     unsigned lowestRightSbIdx = (unsigned)-1;
880     unsigned leftmostSbIdx = (unsigned)-1;
881     unsigned rowMaxLeft = 0, rowMaxRight = 0, colMax = 0;
882     unsigned i, count = [scrollbars count];
883     for (i = 0; i < count; ++i) {
884         MMScroller *scroller = [scrollbars objectAtIndex:i];
885         if (![scroller isHidden]) {
886             NSRange range = [scroller range];
887             if ([scroller type] == MMScrollerTypeLeft
888                     && range.location >= rowMaxLeft) {
889                 rowMaxLeft = range.location;
890                 lowestLeftSbIdx = i;
891             } else if ([scroller type] == MMScrollerTypeRight
892                     && range.location >= rowMaxRight) {
893                 rowMaxRight = range.location;
894                 lowestRightSbIdx = i;
895             } else if ([scroller type] == MMScrollerTypeBottom
896                     && range.location >= colMax) {
897                 colMax = range.location;
898                 leftmostSbIdx = i;
899             }
900         }
901     }
903     // Place the scrollbars.
904     for (i = 0; i < count; ++i) {
905         MMScroller *scroller = [scrollbars objectAtIndex:i];
906         if ([scroller isHidden])
907             continue;
909         NSRect rect;
910         if ([scroller type] == MMScrollerTypeBottom) {
911             rect = [textStorage rectForColumnsInRange:[scroller range]];
912             rect.size.height = [NSScroller scrollerWidth];
913             if (statusVisible)
914                 rect.origin.y += StatusLineHeight;
915             if (lsbVisible)
916                 rect.origin.x += [NSScroller scrollerWidth];
918             // HACK!  Make sure the leftmost horizontal scrollbar covers the
919             // text view all the way to the right, otherwise it looks ugly when
920             // the user drags the window to resize.
921             if (i == leftmostSbIdx) {
922                 float w = NSMaxX(tabViewFrame) - NSMaxX(rect);
923                 if (w > 0)
924                     rect.size.width += w;
925             }
927             // Make sure scrollbar rect is bounded by the tab view frame.
928             if (rect.origin.x < tabViewFrame.origin.x)
929                 rect.origin.x = tabViewFrame.origin.x;
930             else if (rect.origin.x > NSMaxX(tabViewFrame))
931                 rect.origin.x = NSMaxX(tabViewFrame);
932             if (NSMaxX(rect) > NSMaxX(tabViewFrame))
933                 rect.size.width -= NSMaxX(rect) - NSMaxX(tabViewFrame);
934             if (rect.size.width < 0)
935                 rect.size.width = 0;
936         } else {
937             rect = [textStorage rectForRowsInRange:[scroller range]];
938             // Adjust for the fact that text layout is flipped.
939             rect.origin.y = NSMaxY(tabViewFrame) - rect.origin.y
940                     - rect.size.height;
941             rect.size.width = [NSScroller scrollerWidth];
942             if ([scroller type] == MMScrollerTypeRight)
943                 rect.origin.x = NSMaxX(tabViewFrame);
945             // HACK!  Make sure the lowest vertical scrollbar covers the text
946             // view all the way to the bottom.  This is done because Vim only
947             // makes the scrollbar cover the (vim-)window it is associated with
948             // and this means there is always an empty gap in the scrollbar
949             // region next to the command line.
950             // TODO!  Find a nicer way to do this.
951             if (i == lowestLeftSbIdx || i == lowestRightSbIdx) {
952                 float h = rect.origin.y + rect.size.height
953                           - tabViewFrame.origin.y;
954                 if (rect.size.height < h) {
955                     rect.origin.y = tabViewFrame.origin.y;
956                     rect.size.height = h;
957                 }
958             }
960             // Vertical scrollers must not cover the resize box in the
961             // bottom-right corner of the window.
962             if (rect.origin.y < [NSScroller scrollerWidth]) {
963                 rect.size.height -= [NSScroller scrollerWidth] - rect.origin.y;
964                 rect.origin.y = [NSScroller scrollerWidth];
965             }
967             // Make sure scrollbar rect is bounded by the tab view frame.
968             if (rect.origin.y < tabViewFrame.origin.y) {
969                 rect.size.height -= tabViewFrame.origin.y - rect.origin.y;
970                 rect.origin.y = tabViewFrame.origin.y;
971             } else if (rect.origin.y > NSMaxY(tabViewFrame))
972                 rect.origin.y = NSMaxY(tabViewFrame);
973             if (NSMaxY(rect) > NSMaxY(tabViewFrame))
974                 rect.size.height -= NSMaxY(rect) - NSMaxY(tabViewFrame);
975             if (rect.size.height < 0)
976                 rect.size.height = 0;
977         }
979         //NSLog(@"set scroller #%d frame = %@", i, NSStringFromRect(rect));
980         NSRect oldRect = [scroller frame];
981         if (!NSEqualRects(oldRect, rect)) {
982             [scroller setFrame:rect];
983             // Clear behind the old scroller frame, or parts of the old
984             // scroller might still be visible after setFrame:.
985             [contentView setNeedsDisplayInRect:oldRect];
986             [scroller setNeedsDisplay:YES];
987         }
988     }
991 - (void)scroll:(id)sender
993     NSMutableData *data = [NSMutableData data];
994     long ident = [(MMScroller*)sender identifier];
995     int hitPart = [sender hitPart];
996     float value = [sender floatValue];
998     [data appendBytes:&ident length:sizeof(long)];
999     [data appendBytes:&hitPart length:sizeof(int)];
1000     [data appendBytes:&value length:sizeof(float)];
1002     [vimController sendMessage:ScrollbarEventMsgID data:data wait:NO];
1005 - (void)placeViews
1007     if (!setupDone) return;
1009     // NOTE!  It is assumed that the window has been resized so that it will
1010     // exactly fit the text storage (possibly after resizing it).  If this is
1011     // not the case the display might be messed up.
1012     NSWindow *win = [self window];
1013     NSRect contentRect = [win contentRectForFrameRect:[win frame]];
1014     NSRect textViewRect = [self textViewRectForContentSize:contentRect.size];
1015     NSSize tsSize = [self textStorageSizeForTextViewSize:textViewRect.size];
1017     int dim[2], rows, cols;
1018     [textStorage getMaxRows:&rows columns:&cols];
1019     [textStorage fitToSize:tsSize rows:&dim[0] columns:&dim[1]];
1021     if (dim[0] != rows || dim[1] != cols) {
1022         NSString *sdim = [NSString stringWithFormat:@"%dx%d", dim[1], dim[0]];
1023         [self flashStatusText:sdim];
1025         //NSLog(@"Notify Vim that text storage dimensions changed to %dx%d",
1026         //        dim[0], dim[1]);
1027         NSData *data = [NSData dataWithBytes:dim length:2*sizeof(int)];
1029         // NOTE! This can get called a lot when in live resize, which causes
1030         // the connection buffers to fill up.  If we wait for the message to be
1031         // sent then the app might become unresponsive.
1032         [vimController sendMessage:SetTextDimensionsMsgID data:data
1033                      wait:![textView inLiveResize]];
1034     }
1036     [tabView setFrame:textViewRect];
1038     [self placeScrollbars];
1041 @end // MMWindowController (Private)
1045 @implementation NSTabView (MMExtras)
1047 - (void)removeAllTabViewItems
1049     NSArray *existingItems = [self tabViewItems];
1050     NSEnumerator *e = [existingItems objectEnumerator];
1051     NSTabViewItem *item;
1052     while (item = [e nextObject]){
1053         [self removeTabViewItem:item];
1054     }
1057 @end // NSTabView (MMExtras)
1062 @implementation MMScroller
1064 - (id)initWithIdentifier:(long)ident type:(int)theType
1066     // HACK! NSScroller creates a horizontal scroller if it is init'ed with a
1067     // frame whose with exceeds its height; so create a bogus rect and pass it
1068     // to initWithFrame.
1069     NSRect frame = theType == MMScrollerTypeBottom
1070             ? NSMakeRect(0, 0, 1, 0)
1071             : NSMakeRect(0, 0, 0, 1);
1073     if ((self = [super initWithFrame:frame])) {
1074         identifier = ident;
1075         type = theType;
1076         [self setHidden:YES];
1077         [self setEnabled:YES];
1078     }
1080     return self;
1083 - (long)identifier
1085     return identifier;
1088 - (int)type
1090     return type;
1093 - (NSRange)range
1095     return range;
1098 - (void)setRange:(NSRange)newRange
1100     range = newRange;
1103 @end // MMScroller