- MMTextStorage ensures that all glyphs have the same width (or twice that, for
[MacVim/jjgod.git] / MMWindowController.m
blobaa511e60d69bda4481af534b43d1e3a23fbe3c08
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 - (void)statusTimerFired:(NSTimer *)timer;
61 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi;
62 - (IBAction)vimMenuItemAction:(id)sender;
63 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx;
64 - (BOOL)bottomScrollbarVisible;
65 - (BOOL)leftScrollbarVisible;
66 - (BOOL)rightScrollbarVisible;
67 - (void)placeScrollbars;
68 - (void)scroll:(id)sender;
69 - (void)placeViews;
70 - (NSDictionary *)windowAutosaveDict;
71 @end
75 #if 0
76 NSString *buildMenuItemDescriptor(NSMenu *menu, NSString *tail)
78     return menu ? buildMenuItemDescriptor([menu supermenu], [[menu title]
79                     stringByAppendingString:tail])
80                 : tail;
83 NSMutableArray *buildMenuAddress(NSMenu *menu)
85     NSMutableArray *addr;
86     if (menu) {
87         addr = buildMenuAddress([menu supermenu]);
88         [addr addObject:[menu title]];
89     } else {
90         addr = [NSMutableArray array];
91     }
93     return addr;
95 #endif
98 @implementation MMWindowController
100 - (id)initWithVimController:(MMVimController *)controller
102     if ((self = [super initWithWindowNibName:@"VimWindow"])) {
103         vimController = controller;
104         scrollbars = [[NSMutableArray alloc] init];
106         // Window cascading is handled by MMAppController.
107         [self setShouldCascadeWindows:NO];
109         // Setup a complete text system.
110         textStorage = [[MMTextStorage alloc] init];
111         NSLayoutManager *lm = [[NSLayoutManager alloc] init];
112         NSTextContainer *tc = [[NSTextContainer alloc] initWithContainerSize:
113                 NSMakeSize(1.0e7,1.0e7)];
115         NSString *typesetterString = [[NSUserDefaults standardUserDefaults]
116                 stringForKey:MMTypesetterKey];
117         if (![typesetterString isEqual:@"NSTypesetter"]) {
118             MMTypesetter *typesetter = [[MMTypesetter alloc] init];
119             [lm setTypesetter:typesetter];
120             [typesetter release];
121         } else {
122             // Only MMTypesetter supports different cell width multipliers.
123             [[NSUserDefaults standardUserDefaults]
124                     setFloat:1.0 forKey:MMCellWidthMultiplierKey];
125         }
127         [tc setWidthTracksTextView:NO];
128         [tc setHeightTracksTextView:NO];
129         [tc setLineFragmentPadding:0];
131         [textStorage addLayoutManager:lm];
132         [lm addTextContainer:tc];
134         textView = [[MMTextView alloc] initWithFrame:NSZeroRect
135                                        textContainer:tc];
137         NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
138         int left = [ud integerForKey:MMTextInsetLeftKey];
139         int top = [ud integerForKey:MMTextInsetTopKey];
140         [textView setTextContainerInset:NSMakeSize(left, top)];
142         // The text storage retains the layout manager which in turn retains
143         // the text container.
144         [tc release];
145         [lm release];
146     }
148     return self;
151 - (void)dealloc
153     //NSLog(@"%@ %s", [self className], _cmd);
155     // TODO: release tabBarControl and tabView?
157     vimController = nil;
159     [tabBarControl setDelegate:nil];
160     [[self window] setDelegate:nil];
162     [tabView removeAllTabViewItems];
164     [scrollbars release];
165     [textView release];
166     [textStorage release];
168     [super dealloc];
171 - (void)windowDidLoad
173     // Called after window nib file is loaded.
175     [tablineSeparator setHidden:NO];
176     [tabBarControl setHidden:YES];
178     // NOTE: Size to fit looks good, but not many tabs will fit and there are
179     // quite a few drawing bugs in this code, so it is disabled for now.
180     //[tabBarControl setSizeCellsToFit:YES];
182     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
183     [tabBarControl setCellMinWidth:[ud integerForKey:MMTabMinWidthKey]];
184     [tabBarControl setCellMaxWidth:[ud integerForKey:MMTabMaxWidthKey]];
185     [tabBarControl setCellOptimumWidth:[ud integerForKey:MMTabOptimumWidthKey]];
187     [tabBarControl setAllowsDragBetweenWindows:NO];
188     [tabBarControl setShowAddTabButton:YES];
189     [[tabBarControl addTabButton] setTarget:self];
190     [[tabBarControl addTabButton] setAction:@selector(addNewTab:)];
192     // HACK! remove any tabs present in the nib
193     [tabView removeAllTabViewItems];
195     // HACK!  These observers used to be set in the designated init of
196     // MMVimController, but this occasionally caused exceptions from within the
197     // AppKit to be raised.  The problem seemed related to the fact that the
198     // window got loaded 'too early'; adding the observers here seems to
199     // alleviate this problem.
200     [[NSNotificationCenter defaultCenter]
201             addObserver:vimController
202                selector:@selector(windowWillClose:)
203                    name:NSWindowWillCloseNotification
204                  object:[self window]];
205     [[NSNotificationCenter defaultCenter]
206             addObserver:vimController
207                selector:@selector(windowDidBecomeMain:)
208                    name:NSWindowDidBecomeMainNotification
209                  object:[self window]];
212 - (MMVimController *)vimController
214     return vimController;
217 - (MMTextView *)textView
219     return textView;
222 - (MMTextStorage *)textStorage
224     return textStorage;
227 - (NSString *)windowAutosaveKey
229     return windowAutosaveKey;
232 - (void)setWindowAutosaveKey:(NSString *)key
234     [windowAutosaveKey autorelease];
235     windowAutosaveKey = [key copy];
238 - (void)openWindow
240     [[NSApp delegate] windowControllerWillOpen:self];
242     [self addNewTabViewItem];
244     // NOTE! This flag is set once the entire text system is set up.
245     setupDone = YES;
247     [self updateResizeIncrements];
248     [self resizeWindowToFit:self];
249     [[self window] makeKeyAndOrderFront:self];
251     BOOL statusOff = [[NSUserDefaults standardUserDefaults]
252                     boolForKey:MMStatuslineOffKey];
253     [statusTextField setHidden:statusOff];
254     [statusSeparator setHidden:statusOff];
255     [self flashStatusText:@"Welcome to MacVim!"];
258 - (void)updateTabsWithData:(NSData *)data
260     const void *p = [data bytes];
261     const void *end = p + [data length];
262     int tabIdx = 0;
264     // HACK!  Current tab is first in the message.  This way it is not
265     // necessary to guess which tab should be the selected one (this can be
266     // problematic for instance when new tabs are created).
267     int curtabIdx = *((int*)p);  p += sizeof(int);
269     NSArray *tabViewItems = [tabBarControl representedTabViewItems];
271     while (p < end) {
272         //int wincount = *((int*)p);  p += sizeof(int);
273         int length = *((int*)p);  p += sizeof(int);
275         NSString *label = [[NSString alloc]
276                 initWithBytesNoCopy:(void*)p
277                              length:length
278                            encoding:NSUTF8StringEncoding
279                        freeWhenDone:NO];
280         p += length;
282         // Set the label of the tab;  add a new tab when needed.
283         NSTabViewItem *tvi = [tabView numberOfTabViewItems] <= tabIdx
284                 ? [self addNewTabViewItem]
285                 : [tabViewItems objectAtIndex:tabIdx];
287         [tvi setLabel:label];
289         [label release];
291         ++tabIdx;
292     }
294     // Remove unused tabs from the NSTabView.  Note that when a tab is closed
295     // the NSTabView will automatically select another tab, but we want Vim to
296     // take care of which tab to select so set the vimTaskSelectedTab flag to
297     // prevent the tab selection message to be passed on to the VimTask.
298     vimTaskSelectedTab = YES;
299     int i, count = [tabView numberOfTabViewItems];
300     for (i = count-1; i >= tabIdx; --i) {
301         id tvi = [tabViewItems objectAtIndex:i];
302         //NSLog(@"Removing tab with index %d", i);
303         [tabView removeTabViewItem:tvi];
304     }
305     vimTaskSelectedTab = NO;
307     [self selectTabWithIndex:curtabIdx];
310 - (void)selectTabWithIndex:(int)idx
312     //NSLog(@"%s%d", _cmd, idx);
314     NSArray *tabViewItems = [tabBarControl representedTabViewItems];
315     if (idx < 0 || idx >= [tabViewItems count]) {
316         NSLog(@"WARNING: No tab with index %d exists.", idx);
317         return;
318     }
320     // Do not try to select a tab if already selected.
321     NSTabViewItem *tvi = [tabViewItems objectAtIndex:idx];
322     if (tvi != [tabView selectedTabViewItem]) {
323         vimTaskSelectedTab = YES;
324         [tabView selectTabViewItem:tvi];
325         vimTaskSelectedTab = NO;
326     }
329 - (void)setTextDimensionsWithRows:(int)rows columns:(int)cols
331     //NSLog(@"setTextDimensionsWithRows:%d columns:%d", rows, cols);
333     [textStorage setMaxRows:rows columns:cols];
335     if (setupDone && ![textView inLiveResize])
336         shouldUpdateWindowSize = YES;
339 - (void)setStatusText:(NSString *)text
341     if (text)
342         [statusTextField setStringValue:text];
343     else
344         [statusTextField setStringValue:@""];
347 - (void)flashStatusText:(NSString *)text
349     if ([[NSUserDefaults standardUserDefaults] boolForKey:MMStatuslineOffKey])
350         return;
352     [self setStatusText:text];
354     if (statusTimer) {
355         [statusTimer invalidate];
356         [statusTimer release];
357     }
359     statusTimer = [[NSTimer scheduledTimerWithTimeInterval:3
360                               target:self
361                             selector:@selector(statusTimerFired:)
362                             userInfo:nil
363                              repeats:NO] retain];
366 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
368     //NSLog(@"Create scroller %d of type %d", ident, type);
370     MMScroller *scroller = [[MMScroller alloc] initWithIdentifier:ident
371                                                              type:type];
372     [scroller setTarget:self];
373     [scroller setAction:@selector(scroll:)];
375     [[[self window] contentView] addSubview:scroller];
376     [scrollbars addObject:scroller];
377     [scroller release];
380 - (void)destroyScrollbarWithIdentifier:(long)ident
382     //NSLog(@"Destroy scroller %d", ident);
384     unsigned idx = 0;
385     MMScroller *scroller = [self scrollbarForIdentifier:ident index:&idx];
386     if (scroller) {
387         [scroller removeFromSuperview];
388         [scrollbars removeObjectAtIndex:idx];
390         if (![scroller isHidden]) {
391             // A visible scroller was removed, so the window must resize to
392             // fit.
393             //NSLog(@"Visible scroller %d was destroyed, resizing window.",
394             //        ident);
395             shouldUpdateWindowSize = YES;
396         }
397     }
400 - (void)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
402     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
403     if (!scroller) return;
405     BOOL wasVisible = ![scroller isHidden];
406     //NSLog(@"%s scroller %d (was %svisible)", visible ? "Show" : "Hide",
407     //      ident, wasVisible ? "" : "in");
408     [scroller setHidden:!visible];
410     if (wasVisible != visible) {
411         // A scroller was hidden or shown, so the window must resize to fit.
412         //NSLog(@"%s scroller %d and resize.", visible ? "Show" : "Hide",
413         //        ident);
414         shouldUpdateWindowSize = YES;
415     }
418 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
420     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
421     NSRange range = NSMakeRange(pos, len);
422     if (!NSEqualRanges(range, [scroller range])) {
423         //NSLog(@"Set range %@ for scroller %d",
424         //        NSStringFromRange(range), ident);
425         [scroller setRange:range];
426         // TODO!  Should only do this once per update.
427         [self placeScrollbars];
428     }
431 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
432                     identifier:(long)ident
434     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
435     //NSLog(@"Set thumb value %.2f proportion %.2f for scroller %d",
436     //        val, prop, ident);
437     [scroller setFloatValue:val knobProportion:prop];
440 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
442     [textStorage setDefaultColorsBackground:back foreground:fore];
443     [textView setBackgroundColor:back];
446 - (void)setFont:(NSFont *)font
448     [textStorage setFont:font];
449     [self updateResizeIncrements];
452 - (void)processCommandQueueDidFinish
454     if (shouldUpdateWindowSize) {
455         shouldUpdateWindowSize = NO;
456         [self resizeWindowToFit:self];
457     }
460 - (IBAction)addNewTab:(id)sender
462     // NOTE! This can get called a lot if the user holds down the key
463     // equivalent for this action, which causes the ports to fill up.  If we
464     // wait for the message to be sent then the app might become unresponsive.
465     [vimController sendMessage:AddNewTabMsgID data:nil wait:NO];
468 - (IBAction)showTabBar:(id)sender
470     [tablineSeparator setHidden:YES];
471     [tabBarControl setHidden:NO];
473     if (setupDone)
474         shouldUpdateWindowSize = YES;
477 - (IBAction)hideTabBar:(id)sender
479     [tablineSeparator setHidden:NO];
480     [tabBarControl setHidden:YES];
482     if (setupDone)
483         shouldUpdateWindowSize = YES;
489 // -- PSMTabBarControl delegate ----------------------------------------------
492 - (void)tabView:(NSTabView *)theTabView didSelectTabViewItem:
493         (NSTabViewItem *)tabViewItem
495     // HACK!  There seem to be a bug in NSTextView which results in the first
496     // responder not being set to the view of the tab item so it is done
497     // manually here.
498     [[self window] makeFirstResponder:[tabViewItem view]];
500     // HACK!  The selection message should not be propagated to the VimTask if
501     // the VimTask selected the tab (e.g. as opposed the user clicking the
502     // tab).  The delegate method has no way of knowing who initiated the
503     // selection so a flag is set when the VimTask initiated the selection.
504     if (!vimTaskSelectedTab) {
505         // Propagate the selection message to the VimTask.
506         int idx = [self representedIndexOfTabViewItem:tabViewItem];
507         if (NSNotFound != idx) {
508             NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
509             [vimController sendMessage:SelectTabMsgID data:data wait:YES];
510         }
511     }
514 - (BOOL)tabView:(NSTabView *)theTabView shouldCloseTabViewItem:
515         (NSTabViewItem *)tabViewItem
517     // HACK!  This method is only called when the user clicks the close button
518     // on the tab.  Instead of letting the tab bar close the tab, we return NO
519     // and pass a message on to Vim to let it handle the closing.
520     int idx = [self representedIndexOfTabViewItem:tabViewItem];
521     //NSLog(@"Closing tab with index %d", idx);
522     NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
523     [vimController sendMessage:CloseTabMsgID data:data wait:YES];
525     return NO;
528 - (void)tabView:(NSTabView *)theTabView didDragTabViewItem:
529         (NSTabViewItem *)tabViewItem toIndex:(int)idx
531     NSMutableData *data = [NSMutableData data];
532     [data appendBytes:&idx length:sizeof(int)];
534     [vimController sendMessage:DraggedTabMsgID data:data wait:YES];
540 // -- NSWindow delegate ------------------------------------------------------
543 - (BOOL)windowShouldClose:(id)sender
545     [vimController sendMessage:VimShouldCloseMsgID data:nil wait:YES];
546     return NO;
549 - (void)windowWillClose:(NSNotification *)notification
551     //NSLog(@"%@ %s", [self className], _cmd);
553     // NOTE! There is a bug in PSMTabBarControl in that it retains the delegate
554     // (which is the MMWindowController) so reset the delegate here, otherwise
555     // the MMWindowController never gets released resulting in a pretty serious
556     // memory leak.
557     [tabBarControl setDelegate:nil];
560 - (void)windowDidMove:(NSNotification *)notification
562     if (windowAutosaveKey) {
563         NSDictionary *dict = [self windowAutosaveDict];
564         if (dict)
565             [[NSUserDefaults standardUserDefaults]
566                     setObject:dict forKey:windowAutosaveKey];
567     }
570 - (void)windowDidResize:(id)sender
572     if (!setupDone) return;
573     [self placeViews];
576 - (NSRect)windowWillUseStandardFrame:(NSWindow *)win
577                         defaultFrame:(NSRect)frame
579     // HACK!  For some reason 'frame' is not always constrained to fit on the
580     // screen (e.g. it may overlap the menu bar), so first constrain it to the
581     // screen; otherwise the new frame we compute may be too large and this
582     // will mess up the display after the window resizes.
583     frame = [win constrainFrameRect:frame toScreen:[win screen]];
585     // HACK!  If the top of 'frame' is lower than the current window frame,
586     // increase 'frame' so that their tops align.  Really, 'frame' should
587     // already have its top at least as high as the current window frame, but
588     // for some reason this is not always the case.
589     // (See resizeWindowToFit: for a similar hack.)
590     NSRect cur = [win frame];
591     if (NSMaxY(cur) > NSMaxY(frame)) {
592         frame.size.height = cur.origin.y - frame.origin.y + cur.size.height;
593     }
595     frame = [self fitWindowToFrame:frame];
597     // Keep old width and horizontal position unless user clicked with the
598     // Command key is held down.
599     NSEvent *event = [NSApp currentEvent];
600     if (!([event type] == NSLeftMouseUp
601             && [event modifierFlags] & NSCommandKeyMask)) {
602         NSRect currentFrame = [win frame];
603         frame.size.width = currentFrame.size.width;
604         frame.origin.x = currentFrame.origin.x;
605     }
607     return frame;
613 // -- Services menu delegate -------------------------------------------------
615 - (id)validRequestorForSendType:(NSString *)sendType
616                      returnType:(NSString *)returnType
618     //NSLog(@"validRequestorForSendType:%@ returnType:%@", sendType, returnType);
620     id backendProxy = [vimController backendProxy];
622     if ((!sendType || [sendType isEqual:NSStringPboardType])
623             && (!returnType || [returnType isEqual:NSStringPboardType]))
624     {
625         if ((!sendType || [backendProxy starRegisterToPasteboard:nil]) &&
626                 (!returnType || [backendProxy starRegisterFromPasteboard:nil]))
627         {
628             return self;
629         }
630     }
632     return [super validRequestorForSendType:sendType returnType:returnType];
635 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
636                              types:(NSArray *)types
638     //NSLog(@"writeSelectionToPasteboard:%@ types:%@", pboard, types);
640     if (![types containsObject:NSStringPboardType])
641         return NO;
643     id backendProxy = [vimController backendProxy];
644     return [backendProxy starRegisterToPasteboard:pboard];
647 @end // MMWindowController
651 @implementation MMWindowController (Private)
653 - (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize
655     NSSize size = textViewSize;
657     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
658     int right = [ud integerForKey:MMTextInsetRightKey];
659     int bot = [ud integerForKey:MMTextInsetBottomKey];
661     size.width += [textView textContainerOrigin].x + right;
662     size.height += [textView textContainerOrigin].y + bot;
664     // A one pixel high separator is shown if tabline is hidden.
665     if ([tabBarControl isHidden]) ++size.height;
666     else size.height += [tabBarControl frame].size.height;
668     if (![ud boolForKey:MMStatuslineOffKey])
669         size.height += StatusLineHeight;
671     if ([self bottomScrollbarVisible])
672         size.height += [NSScroller scrollerWidth];
673     if ([self leftScrollbarVisible])
674         size.width += [NSScroller scrollerWidth];
675     if ([self rightScrollbarVisible])
676         size.width += [NSScroller scrollerWidth];
678     return size;
681 - (NSRect)textViewRectForContentSize:(NSSize)contentSize
683     NSRect rect = { 0, 0, contentSize.width, contentSize.height };
685     // A one pixel high separator is shown if tabline is hidden.
686     if ([tabBarControl isHidden]) --rect.size.height;
687     else rect.size.height -= [tabBarControl frame].size.height;
689     if (![[NSUserDefaults standardUserDefaults]
690             boolForKey:MMStatuslineOffKey]) {
691         rect.size.height -= StatusLineHeight;
692         rect.origin.y += StatusLineHeight;
693     }
695     if ([self bottomScrollbarVisible]) {
696         rect.size.height -= [NSScroller scrollerWidth];
697         rect.origin.y += [NSScroller scrollerWidth];
698     }
699     if ([self leftScrollbarVisible]) {
700         rect.size.width -= [NSScroller scrollerWidth];
701         rect.origin.x += [NSScroller scrollerWidth];
702     }
703     if ([self rightScrollbarVisible])
704         rect.size.width -= [NSScroller scrollerWidth];
706     return rect;
709 - (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize
711     NSSize size = textViewSize;
713     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
714     int right = [ud integerForKey:MMTextInsetRightKey];
715     int bot = [ud integerForKey:MMTextInsetBottomKey];
717     size.width -= [textView textContainerOrigin].x + right;
718     size.height -= [textView textContainerOrigin].y + bot;
720     return size;
723 - (void)resizeWindowToFit:(id)sender
725     if (!setupDone) return;
727     NSWindow *win = [self window];
728     NSRect frame = [win frame];
729     NSRect contentRect = [win contentRectForFrameRect:frame];
730     NSSize newSize = [self contentSizeForTextStorageSize:[textStorage size]];
732     // Keep top-left corner of the window fixed when resizing.
733     contentRect.origin.y -= newSize.height - contentRect.size.height;
734     contentRect.size = newSize;
736     frame = [win frameRectForContentRect:contentRect];
737     NSRect maxFrame = [win constrainFrameRect:frame toScreen:[win screen]];
739     // HACK!  Assuming the window frame cannot already be placed too high,
740     // adjust 'maxFrame' so that it at least as high up as the current frame.
741     // The reason for doing this is that constrainFrameRect:toScreen: does not
742     // always seem to utilize as much area as possible.
743     if (NSMaxY(frame) > NSMaxY(maxFrame)) {
744         maxFrame.size.height = frame.origin.y - maxFrame.origin.y
745                 + frame.size.height;
746     }
748     if (!NSEqualRects(maxFrame, frame)) {
749         // The new window frame is too big to fit on the screen, so fit the
750         // text storage to the biggest frame which will fit on the screen.
751         //NSLog(@"Proposed window frame does not fit on the screen!");
752         frame = [self fitWindowToFrame:maxFrame];
753     }
755     //NSLog(@"%s %@", _cmd, NSStringFromRect(frame));
757     // HACK! If the window does resize, then windowDidResize is called which in
758     // turn calls placeViews.  In case the computed new size of the window is
759     // no different from the current size, then we need to call placeViews
760     // manually.
761     if (NSEqualRects(frame, [win frame])) {
762         [self placeViews];
763     } else {
764         [win setFrame:frame display:YES];
765     }
768 - (NSRect)fitWindowToFrame:(NSRect)frame
770     if (!setupDone) return frame;
772     NSWindow *win = [self window];
773     NSRect contentRect = [win contentRectForFrameRect:frame];
774     NSSize size = [self textViewRectForContentSize:contentRect.size].size;
775     size = [self textStorageSizeForTextViewSize:size];
776     size = [textStorage fitToSize:size];
777     size = [self contentSizeForTextStorageSize:size];
779     // Keep top-left corner of 'frame' fixed.
780     contentRect.origin.y -= size.height - contentRect.size.height;
781     contentRect.size = size;
783     return [win frameRectForContentRect:contentRect];
786 - (void)updateResizeIncrements
788     if (!setupDone) return;
790     NSSize size = [textStorage cellSize];
791     [[self window] setContentResizeIncrements:size];
794 - (NSTabViewItem *)addNewTabViewItem
796     // NOTE!  A newly created tab is not by selected by default; the VimTask
797     // decides which tab should be selected at all times.  However, the AppKit
798     // will automatically select the first tab added to a tab view.
800     NSTabViewItem *tvi = [[NSTabViewItem alloc] initWithIdentifier:nil];
801     [tvi setView:textView];
803     // BUG!  This call seems to have no effect; see comment in
804     // tabView:didSelectTabViewItem:.
805     //[tvi setInitialFirstResponder:textView];
807     // NOTE: If this is the first tab it will be automatically selected.
808     vimTaskSelectedTab = YES;
809     [tabView addTabViewItem:tvi];
810     vimTaskSelectedTab = NO;
812     [tvi release];
814     return tvi;
817 - (void)statusTimerFired:(NSTimer *)timer
819     [self setStatusText:@""];
820     [statusTimer release];
821     statusTimer = nil;
824 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
826     NSArray *tabViewItems = [tabBarControl representedTabViewItems];
827     return [tabViewItems indexOfObject:tvi];
830 - (IBAction)vimMenuItemAction:(id)sender
832     int tag = [sender tag];
834     NSMutableData *data = [NSMutableData data];
835     [data appendBytes:&tag length:sizeof(int)];
837     [vimController sendMessage:ExecuteMenuMsgID data:data wait:NO];
840 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx
842     unsigned i, count = [scrollbars count];
843     for (i = 0; i < count; ++i) {
844         MMScroller *scroller = [scrollbars objectAtIndex:i];
845         if ([scroller identifier] == ident) {
846             if (idx) *idx = i;
847             return scroller;
848         }
849     }
851     return nil;
854 - (BOOL)bottomScrollbarVisible
856     unsigned i, count = [scrollbars count];
857     for (i = 0; i < count; ++i) {
858         MMScroller *scroller = [scrollbars objectAtIndex:i];
859         if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden])
860             return YES;
861     }
863     return NO;
866 - (BOOL)leftScrollbarVisible
868     unsigned i, count = [scrollbars count];
869     for (i = 0; i < count; ++i) {
870         MMScroller *scroller = [scrollbars objectAtIndex:i];
871         if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden])
872             return YES;
873     }
875     return NO;
878 - (BOOL)rightScrollbarVisible
880     unsigned i, count = [scrollbars count];
881     for (i = 0; i < count; ++i) {
882         MMScroller *scroller = [scrollbars objectAtIndex:i];
883         if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden])
884             return YES;
885     }
887     return NO;
890 - (void)placeScrollbars
892     if (!setupDone) return;
894     NSRect tabViewFrame = [tabView frame];
895     NSView *contentView = [[self window] contentView];
896     BOOL lsbVisible = [self leftScrollbarVisible];
897     BOOL statusVisible = ![[NSUserDefaults standardUserDefaults]
898             boolForKey:MMStatuslineOffKey];
900     // HACK!  Find the lowest left&right vertical scrollbars, as well as the
901     // leftmost horizontal scrollbar.  This hack continues further down.
902     //
903     // TODO!  Can there be no more than one horizontal scrollbar?  If so, the
904     // code can be simplified.
905     unsigned lowestLeftSbIdx = (unsigned)-1;
906     unsigned lowestRightSbIdx = (unsigned)-1;
907     unsigned leftmostSbIdx = (unsigned)-1;
908     unsigned rowMaxLeft = 0, rowMaxRight = 0, colMax = 0;
909     unsigned i, count = [scrollbars count];
910     for (i = 0; i < count; ++i) {
911         MMScroller *scroller = [scrollbars objectAtIndex:i];
912         if (![scroller isHidden]) {
913             NSRange range = [scroller range];
914             if ([scroller type] == MMScrollerTypeLeft
915                     && range.location >= rowMaxLeft) {
916                 rowMaxLeft = range.location;
917                 lowestLeftSbIdx = i;
918             } else if ([scroller type] == MMScrollerTypeRight
919                     && range.location >= rowMaxRight) {
920                 rowMaxRight = range.location;
921                 lowestRightSbIdx = i;
922             } else if ([scroller type] == MMScrollerTypeBottom
923                     && range.location >= colMax) {
924                 colMax = range.location;
925                 leftmostSbIdx = i;
926             }
927         }
928     }
930     // Place the scrollbars.
931     for (i = 0; i < count; ++i) {
932         MMScroller *scroller = [scrollbars objectAtIndex:i];
933         if ([scroller isHidden])
934             continue;
936         NSRect rect;
937         if ([scroller type] == MMScrollerTypeBottom) {
938             rect = [textStorage rectForColumnsInRange:[scroller range]];
939             rect.size.height = [NSScroller scrollerWidth];
940             if (statusVisible)
941                 rect.origin.y += StatusLineHeight;
942             if (lsbVisible)
943                 rect.origin.x += [NSScroller scrollerWidth];
945             // HACK!  Make sure the leftmost horizontal scrollbar covers the
946             // text view all the way to the right, otherwise it looks ugly when
947             // the user drags the window to resize.
948             if (i == leftmostSbIdx) {
949                 float w = NSMaxX(tabViewFrame) - NSMaxX(rect);
950                 if (w > 0)
951                     rect.size.width += w;
952             }
954             // Make sure scrollbar rect is bounded by the tab view frame.
955             if (rect.origin.x < tabViewFrame.origin.x)
956                 rect.origin.x = tabViewFrame.origin.x;
957             else if (rect.origin.x > NSMaxX(tabViewFrame))
958                 rect.origin.x = NSMaxX(tabViewFrame);
959             if (NSMaxX(rect) > NSMaxX(tabViewFrame))
960                 rect.size.width -= NSMaxX(rect) - NSMaxX(tabViewFrame);
961             if (rect.size.width < 0)
962                 rect.size.width = 0;
963         } else {
964             rect = [textStorage rectForRowsInRange:[scroller range]];
965             // Adjust for the fact that text layout is flipped.
966             rect.origin.y = NSMaxY(tabViewFrame) - rect.origin.y
967                     - rect.size.height;
968             rect.size.width = [NSScroller scrollerWidth];
969             if ([scroller type] == MMScrollerTypeRight)
970                 rect.origin.x = NSMaxX(tabViewFrame);
972             // HACK!  Make sure the lowest vertical scrollbar covers the text
973             // view all the way to the bottom.  This is done because Vim only
974             // makes the scrollbar cover the (vim-)window it is associated with
975             // and this means there is always an empty gap in the scrollbar
976             // region next to the command line.
977             // TODO!  Find a nicer way to do this.
978             if (i == lowestLeftSbIdx || i == lowestRightSbIdx) {
979                 float h = rect.origin.y + rect.size.height
980                           - tabViewFrame.origin.y;
981                 if (rect.size.height < h) {
982                     rect.origin.y = tabViewFrame.origin.y;
983                     rect.size.height = h;
984                 }
985             }
987             // Vertical scrollers must not cover the resize box in the
988             // bottom-right corner of the window.
989             if (rect.origin.y < [NSScroller scrollerWidth]) {
990                 rect.size.height -= [NSScroller scrollerWidth] - rect.origin.y;
991                 rect.origin.y = [NSScroller scrollerWidth];
992             }
994             // Make sure scrollbar rect is bounded by the tab view frame.
995             if (rect.origin.y < tabViewFrame.origin.y) {
996                 rect.size.height -= tabViewFrame.origin.y - rect.origin.y;
997                 rect.origin.y = tabViewFrame.origin.y;
998             } else if (rect.origin.y > NSMaxY(tabViewFrame))
999                 rect.origin.y = NSMaxY(tabViewFrame);
1000             if (NSMaxY(rect) > NSMaxY(tabViewFrame))
1001                 rect.size.height -= NSMaxY(rect) - NSMaxY(tabViewFrame);
1002             if (rect.size.height < 0)
1003                 rect.size.height = 0;
1004         }
1006         //NSLog(@"set scroller #%d frame = %@", i, NSStringFromRect(rect));
1007         NSRect oldRect = [scroller frame];
1008         if (!NSEqualRects(oldRect, rect)) {
1009             [scroller setFrame:rect];
1010             // Clear behind the old scroller frame, or parts of the old
1011             // scroller might still be visible after setFrame:.
1012             [contentView setNeedsDisplayInRect:oldRect];
1013             [scroller setNeedsDisplay:YES];
1014         }
1015     }
1018 - (void)scroll:(id)sender
1020     NSMutableData *data = [NSMutableData data];
1021     long ident = [(MMScroller*)sender identifier];
1022     int hitPart = [sender hitPart];
1023     float value = [sender floatValue];
1025     [data appendBytes:&ident length:sizeof(long)];
1026     [data appendBytes:&hitPart length:sizeof(int)];
1027     [data appendBytes:&value length:sizeof(float)];
1029     [vimController sendMessage:ScrollbarEventMsgID data:data wait:NO];
1032 - (void)placeViews
1034     if (!setupDone) return;
1036     // NOTE!  It is assumed that the window has been resized so that it will
1037     // exactly fit the text storage (possibly after resizing it).  If this is
1038     // not the case the display might be messed up.
1039     NSWindow *win = [self window];
1040     NSRect contentRect = [win contentRectForFrameRect:[win frame]];
1041     NSRect textViewRect = [self textViewRectForContentSize:contentRect.size];
1042     NSSize tsSize = [self textStorageSizeForTextViewSize:textViewRect.size];
1044     int dim[2], rows, cols;
1045     [textStorage getMaxRows:&rows columns:&cols];
1046     [textStorage fitToSize:tsSize rows:&dim[0] columns:&dim[1]];
1048     if (dim[0] != rows || dim[1] != cols) {
1049         NSString *sdim = [NSString stringWithFormat:@"%dx%d", dim[1], dim[0]];
1050         [self flashStatusText:sdim];
1052         //NSLog(@"Notify Vim that text storage dimensions changed to %dx%d",
1053         //        dim[0], dim[1]);
1054         NSData *data = [NSData dataWithBytes:dim length:2*sizeof(int)];
1056         // NOTE! This can get called a lot when in live resize, which causes
1057         // the connection buffers to fill up.  If we wait for the message to be
1058         // sent then the app might become unresponsive.
1059         [vimController sendMessage:SetTextDimensionsMsgID data:data
1060                      wait:![textView inLiveResize]];
1061     }
1063     [tabView setFrame:textViewRect];
1065     [self placeScrollbars];
1068 - (NSDictionary *)windowAutosaveDict
1070     if (!setupDone)
1071         return nil;
1073     int rows = 0, cols = 0;
1074     if (textStorage)
1075         [textStorage getMaxRows:&rows columns:&cols];
1077     NSPoint origin = NSZeroPoint;
1078     if (setupDone) {
1079         NSRect frame = [[self window] frame];
1080         origin = NSMakePoint(frame.origin.x, NSMaxY(frame));
1081     }
1083     NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
1084         [NSNumber numberWithInt:rows], @"Rows",
1085         [NSNumber numberWithInt:cols], @"Columns",
1086         [NSNumber numberWithFloat:origin.x], @"x",
1087         [NSNumber numberWithFloat:origin.y], @"y", nil];
1089     return dict;
1092 @end // MMWindowController (Private)
1096 @implementation NSTabView (MMExtras)
1098 - (void)removeAllTabViewItems
1100     NSArray *existingItems = [self tabViewItems];
1101     NSEnumerator *e = [existingItems objectEnumerator];
1102     NSTabViewItem *item;
1103     while (item = [e nextObject]){
1104         [self removeTabViewItem:item];
1105     }
1108 @end // NSTabView (MMExtras)
1113 @implementation MMScroller
1115 - (id)initWithIdentifier:(long)ident type:(int)theType
1117     // HACK! NSScroller creates a horizontal scroller if it is init'ed with a
1118     // frame whose with exceeds its height; so create a bogus rect and pass it
1119     // to initWithFrame.
1120     NSRect frame = theType == MMScrollerTypeBottom
1121             ? NSMakeRect(0, 0, 1, 0)
1122             : NSMakeRect(0, 0, 0, 1);
1124     if ((self = [super initWithFrame:frame])) {
1125         identifier = ident;
1126         type = theType;
1127         [self setHidden:YES];
1128         [self setEnabled:YES];
1129     }
1131     return self;
1134 - (long)identifier
1136     return identifier;
1139 - (int)type
1141     return type;
1144 - (NSRange)range
1146     return range;
1149 - (void)setRange:(NSRange)newRange
1151     range = newRange;
1154 @end // MMScroller