Marked text field is hidden whenever window loses focus
[MacVim/jjgod.git] / MMWindowController.m
blob67c2d98846c7f472e21212380a4767f7de02f951
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
30 // TODO:  Move!
31 @interface NSTabView (MMExtras)
32 - (void)removeAllTabViewItems;
33 @end
36 // TODO:  Move!
37 @interface MMScroller : NSScroller {
38     long identifier;
39     int type;
40     NSRange range;
42 - (id)initWithIdentifier:(long)ident type:(int)type;
43 - (long)identifier;
44 - (int)type;
45 - (NSRange)range;
46 - (void)setRange:(NSRange)newRange;
47 @end
49 @interface MMWindowController (Private)
50 - (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize;
51 - (NSRect)textViewRectForContentSize:(NSSize)contentSize;
52 - (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize;
53 - (void)resizeWindowToFit:(id)sender;
54 - (NSRect)fitWindowToFrame:(NSRect)frame;
55 - (void)updateResizeIncrements;
56 - (NSTabViewItem *)addNewTabViewItem;
57 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi;
58 - (IBAction)vimMenuItemAction:(id)sender;
59 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx;
60 - (BOOL)bottomScrollbarVisible;
61 - (BOOL)leftScrollbarVisible;
62 - (BOOL)rightScrollbarVisible;
63 - (void)placeScrollbars;
64 - (void)scroll:(id)sender;
65 - (void)placeViews;
66 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb;
67 @end
71 #if 0
72 NSString *buildMenuItemDescriptor(NSMenu *menu, NSString *tail)
74     return menu ? buildMenuItemDescriptor([menu supermenu], [[menu title]
75                     stringByAppendingString:tail])
76                 : tail;
79 NSMutableArray *buildMenuAddress(NSMenu *menu)
81     NSMutableArray *addr;
82     if (menu) {
83         addr = buildMenuAddress([menu supermenu]);
84         [addr addObject:[menu title]];
85     } else {
86         addr = [NSMutableArray array];
87     }
89     return addr;
91 #endif
94 @implementation MMWindowController
96 - (id)initWithVimController:(MMVimController *)controller
98     if ((self = [super initWithWindowNibName:@"EmptyWindow"])) {
99         vimController = controller;
100         scrollbars = [[NSMutableArray alloc] init];
102         // Window cascading is handled by MMAppController.
103         [self setShouldCascadeWindows:NO];
105         // Setup a complete text system.
106         textStorage = [[MMTextStorage alloc] init];
107         NSLayoutManager *lm = [[NSLayoutManager alloc] init];
108         NSTextContainer *tc = [[NSTextContainer alloc] initWithContainerSize:
109                 NSMakeSize(1.0e7,1.0e7)];
111         NSString *typesetterString = [[NSUserDefaults standardUserDefaults]
112                 stringForKey:MMTypesetterKey];
113         if (![typesetterString isEqual:@"NSTypesetter"]) {
114             MMTypesetter *typesetter = [[MMTypesetter alloc] init];
115             [lm setTypesetter:typesetter];
116             [typesetter release];
117         } else {
118             // Only MMTypesetter supports different cell width multipliers.
119             [[NSUserDefaults standardUserDefaults]
120                     setFloat:1.0 forKey:MMCellWidthMultiplierKey];
121         }
123         [tc setWidthTracksTextView:NO];
124         [tc setHeightTracksTextView:NO];
125         [tc setLineFragmentPadding:0];
127         [textStorage addLayoutManager:lm];
128         [lm addTextContainer:tc];
130         NSView *contentView = [[self window] contentView];
131         textView = [[MMTextView alloc] initWithFrame:[contentView frame]
132                                        textContainer:tc];
134         NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
135         int left = [ud integerForKey:MMTextInsetLeftKey];
136         int top = [ud integerForKey:MMTextInsetTopKey];
137         [textView setTextContainerInset:NSMakeSize(left, top)];
139         [contentView addSubview:textView];
141         // The text storage retains the layout manager which in turn retains
142         // the text container.
143         [tc release];
144         [lm release];
146         // Create the tabline separator (which may be visible when the tabline
147         // is hidden).
148         NSRect tabSepRect = [contentView frame];
149         tabSepRect.origin.y = NSMaxY(tabSepRect)-1;
150         tabSepRect.size.height = 1;
151         tablineSeparator = [[NSBox alloc] initWithFrame:tabSepRect];
153         // Create the tab view (which is never visible, but the tab bar control
154         // needs it to function).
155         tabView = [[NSTabView alloc] initWithFrame:NSZeroRect];
157         // Create the tab bar control (which is responsible for actually
158         // drawing the tabline and tabs).
159         NSRect tabFrame = [contentView frame];
160         tabFrame.origin.y = NSMaxY(tabFrame) - 22;
161         tabFrame.size.height = 22;
162         tabBarControl = [[PSMTabBarControl alloc] initWithFrame:tabFrame];
164         [tabView setDelegate:tabBarControl];
166         [tabBarControl setTabView:tabView];
167         [tabBarControl setDelegate:self];
168         [tabBarControl setHidden:YES];
169         [tabBarControl setAutoresizingMask:NSViewWidthSizable|NSViewMinYMargin];
170         [tabBarControl setCellMinWidth:[ud integerForKey:MMTabMinWidthKey]];
171         [tabBarControl setCellMaxWidth:[ud integerForKey:MMTabMaxWidthKey]];
172         [tabBarControl setCellOptimumWidth:
173                          [ud integerForKey:MMTabOptimumWidthKey]];
174         [tabBarControl setShowAddTabButton:YES];
175         [[tabBarControl addTabButton] setTarget:self];
176         [[tabBarControl addTabButton] setAction:@selector(addNewTab:)];
177         [tabBarControl setAllowsDragBetweenWindows:NO];
179         [tablineSeparator setBoxType:NSBoxSeparator];
180         [tablineSeparator setHidden:NO];
181         [tablineSeparator setAutoresizingMask:NSViewWidthSizable
182             | NSViewMinYMargin];
184         [contentView setAutoresizesSubviews:YES];
185         [contentView addSubview:tabBarControl];
186         [contentView addSubview:tablineSeparator];
188         [[self window] setDelegate:self];
189         [[self window] setInitialFirstResponder:textView];
190     }
192     return self;
195 - (void)dealloc
197     //NSLog(@"%@ %s", [self className], _cmd);
199     [tabBarControl release];  tabBarControl = nil;
200     [tabView release];  tabView = nil;
201     [tablineSeparator release];  tablineSeparator = nil;
202     [windowAutosaveKey release];  windowAutosaveKey = nil;
203     [scrollbars release];  scrollbars = nil;
204     [textView release];  textView = nil;
205     [textStorage release];  textStorage = nil;
207     [super dealloc];
210 - (NSString *)description
212     return [NSString stringWithFormat:@"%@ : setupDone=%d windowAutosaveKey=%@ vimController=%@", [self className], setupDone, windowAutosaveKey, vimController];
215 - (MMVimController *)vimController
217     return vimController;
220 - (MMTextView *)textView
222     return textView;
225 - (MMTextStorage *)textStorage
227     return textStorage;
230 - (NSString *)windowAutosaveKey
232     return windowAutosaveKey;
235 - (void)setWindowAutosaveKey:(NSString *)key
237     [windowAutosaveKey autorelease];
238     windowAutosaveKey = [key copy];
241 - (void)cleanup
243     //NSLog(@"%@ %s", [self className], _cmd);
245     setupDone = NO;
246     vimController = nil;
248     // NOTE! There is a bug in PSMTabBarControl in that it retains the delegate
249     // (which is the MMWindowController) so reset the delegate here, otherwise
250     // the MMWindowController never gets released resulting in a pretty serious
251     // memory leak.
252     [tabView setDelegate:nil];
253     [tabBarControl setDelegate:nil];
254     [tabBarControl setTabView:nil];
255     [[self window] setDelegate:nil];
257     // NOTE! There is another bug in PSMTabBarControl where the control is not
258     // removed as an observer, so remove it here (else lots of evil nasty bugs
259     // will come and gnaw at your feet while you are sleeping).
260     [[NSNotificationCenter defaultCenter] removeObserver:tabBarControl];
262     [tabBarControl removeFromSuperviewWithoutNeedingDisplay];
263     [tablineSeparator removeFromSuperviewWithoutNeedingDisplay];
264     [textView removeFromSuperviewWithoutNeedingDisplay];
266     unsigned i, count = [scrollbars count];
267     for (i = 0; i < count; ++i) {
268         MMScroller *sb = [scrollbars objectAtIndex:i];
269         [sb removeFromSuperviewWithoutNeedingDisplay];
270     }
272     [tabView removeAllTabViewItems];
274     [[self window] orderOut:self];
277 - (void)openWindow
279     [[NSApp delegate] windowControllerWillOpen:self];
281     [self addNewTabViewItem];
283     //[[self window] setAcceptsMouseMovedEvents:YES];
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     [[NSFontManager sharedFontManager] setSelectedFont:font isMultiple:NO];
456     [textStorage setFont:font];
457     [self updateResizeIncrements];
460 - (void)processCommandQueueDidFinish
462     if (shouldUpdateWindowSize) {
463         shouldUpdateWindowSize = NO;
464         [self resizeWindowToFit:self];
465     }
468 - (void)popupMenu:(NSMenu *)menu atRow:(int)row column:(int)col
470     if (!setupDone) return;
472     NSEvent *event;
473     if (row >= 0 && col >= 0) {
474         NSSize cellSize = [textStorage cellSize];
475         NSPoint pt = { (col+1)*cellSize.width, [textView frame].size.height
476             - (row+1)*cellSize.height };
478         event = [NSEvent mouseEventWithType:NSRightMouseDown
479                                    location:pt
480                               modifierFlags:0
481                                   timestamp:0
482                                windowNumber:[[self window] windowNumber]
483                                     context:nil
484                                 eventNumber:0
485                                  clickCount:0
486                                    pressure:1.0];
487     } else {
488         event = [textView lastMouseDownEvent];
489     }
491     [NSMenu popUpContextMenu:menu withEvent:event forView:textView];
494 - (void)showTabBar:(BOOL)on
496     [tabBarControl setHidden:!on];
498     if (!on) {
499         NSToolbar *toolbar = [[self window] toolbar]; 
500         [tablineSeparator setHidden:![toolbar isVisible]];
501     } else {
502         [tablineSeparator setHidden:on];
503     }
505     if (setupDone)
506         shouldUpdateWindowSize = YES;
509 - (void)showToolbar:(BOOL)on size:(int)size mode:(int)mode
511     NSToolbar *toolbar = [[self window] toolbar];
512     if (!toolbar) return;
514     [toolbar setSizeMode:size];
515     [toolbar setDisplayMode:mode];
516     [toolbar setVisible:on];
518     if (!on) {
519         [tablineSeparator setHidden:YES];
520     } else {
521         [tablineSeparator setHidden:![tabBarControl isHidden]];
522     }
525 - (void)setMouseShape:(int)shape
527     // This switch should match mshape_names[] in misc2.c.
528     //
529     // TODO: Add missing cursor shapes.
530     switch (shape) {
531         case 2: [[NSCursor IBeamCursor] set]; break;
532         case 3: case 4: [[NSCursor resizeUpDownCursor] set]; break;
533         case 5: case 6: [[NSCursor resizeLeftRightCursor] set]; break;
534         case 9: [[NSCursor crosshairCursor] set]; break;
535         case 10: [[NSCursor pointingHandCursor] set]; break;
536         case 11: [[NSCursor openHandCursor] set]; break;
537         default:
538             [[NSCursor arrowCursor] set]; break;
539     }
541     // Shape 1 indicates that the mouse cursor should be hidden.
542     if (1 == shape)
543         [NSCursor setHiddenUntilMouseMoves:YES];
546 - (IBAction)addNewTab:(id)sender
548     // NOTE! This can get called a lot if the user holds down the key
549     // equivalent for this action, which causes the ports to fill up.  If we
550     // wait for the message to be sent then the app might become unresponsive.
551     [vimController sendMessage:AddNewTabMsgID data:nil wait:NO];
554 - (IBAction)toggleToolbar:(id)sender
556     [vimController sendMessage:ToggleToolbarMsgID data:nil wait:NO];
561 // -- PSMTabBarControl delegate ----------------------------------------------
564 - (BOOL)tabView:(NSTabView *)theTabView shouldSelectTabViewItem:
565     (NSTabViewItem *)tabViewItem
567     // NOTE: It would be reasonable to think that 'shouldSelect...' implies
568     // that this message only gets sent when the user clicks the tab.
569     // Unfortunately it is not so, which is why we need the
570     // 'vimTaskSelectedTab' flag.
571     //
572     // HACK!  The selection message should not be propagated to the VimTask if
573     // the VimTask selected the tab (e.g. as opposed the user clicking the
574     // tab).  The delegate method has no way of knowing who initiated the
575     // selection so a flag is set when the VimTask initiated the selection.
576     if (!vimTaskSelectedTab) {
577         // Propagate the selection message to the VimTask.
578         int idx = [self representedIndexOfTabViewItem:tabViewItem];
579         if (NSNotFound != idx) {
580             NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
581             [vimController sendMessage:SelectTabMsgID data:data wait:YES];
582         }
583     }
585     // Unless Vim selected the tab, return NO, and let Vim decide if the tab
586     // should get selected or not.
587     return vimTaskSelectedTab;
590 - (BOOL)tabView:(NSTabView *)theTabView shouldCloseTabViewItem:
591         (NSTabViewItem *)tabViewItem
593     // HACK!  This method is only called when the user clicks the close button
594     // on the tab.  Instead of letting the tab bar close the tab, we return NO
595     // and pass a message on to Vim to let it handle the closing.
596     int idx = [self representedIndexOfTabViewItem:tabViewItem];
597     //NSLog(@"Closing tab with index %d", idx);
598     NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
599     [vimController sendMessage:CloseTabMsgID data:data wait:YES];
601     return NO;
604 - (void)tabView:(NSTabView *)theTabView didDragTabViewItem:
605         (NSTabViewItem *)tabViewItem toIndex:(int)idx
607     NSMutableData *data = [NSMutableData data];
608     [data appendBytes:&idx length:sizeof(int)];
610     [vimController sendMessage:DraggedTabMsgID data:data wait:YES];
616 // -- NSWindow delegate ------------------------------------------------------
618 - (void)windowDidBecomeMain:(NSNotification *)notification
620     [vimController sendMessage:GotFocusMsgID data:nil wait:NO];
622     if (textStorage)
623         [[NSFontManager sharedFontManager] setSelectedFont:[textStorage font]
624                                                 isMultiple:NO];
627 - (void)windowDidResignMain:(NSNotification *)notification
629     [vimController sendMessage:LostFocusMsgID data:nil wait:NO];
631     if (textView)
632         [textView hideMarkedTextField];
635 - (BOOL)windowShouldClose:(id)sender
637     [vimController sendMessage:VimShouldCloseMsgID data:nil wait:YES];
638     return NO;
641 - (void)windowDidMove:(NSNotification *)notification
643     if (setupDone && windowAutosaveKey) {
644         NSRect frame = [[self window] frame];
645         NSPoint topLeft = { frame.origin.x, NSMaxY(frame) };
646         NSString *topLeftString = NSStringFromPoint(topLeft);
648         [[NSUserDefaults standardUserDefaults]
649             setObject:topLeftString forKey:windowAutosaveKey];
650     }
653 - (void)windowDidResize:(id)sender
655     if (!setupDone) return;
656     [self placeViews];
659 - (NSRect)windowWillUseStandardFrame:(NSWindow *)win
660                         defaultFrame:(NSRect)frame
662     // HACK!  For some reason 'frame' is not always constrained to fit on the
663     // screen (e.g. it may overlap the menu bar), so first constrain it to the
664     // screen; otherwise the new frame we compute may be too large and this
665     // will mess up the display after the window resizes.
666     frame = [win constrainFrameRect:frame toScreen:[win screen]];
668     // HACK!  If the top of 'frame' is lower than the current window frame,
669     // increase 'frame' so that their tops align.  Really, 'frame' should
670     // already have its top at least as high as the current window frame, but
671     // for some reason this is not always the case.
672     // (See resizeWindowToFit: for a similar hack.)
673     NSRect cur = [win frame];
674     if (NSMaxY(cur) > NSMaxY(frame)) {
675         frame.size.height = cur.origin.y - frame.origin.y + cur.size.height;
676     }
678     frame = [self fitWindowToFrame:frame];
680     // Keep old width and horizontal position unless user clicked with the
681     // Command key is held down.
682     NSEvent *event = [NSApp currentEvent];
683     if (!([event type] == NSLeftMouseUp
684             && [event modifierFlags] & NSCommandKeyMask)) {
685         NSRect currentFrame = [win frame];
686         frame.size.width = currentFrame.size.width;
687         frame.origin.x = currentFrame.origin.x;
688     }
690     return frame;
696 // -- Services menu delegate -------------------------------------------------
698 - (id)validRequestorForSendType:(NSString *)sendType
699                      returnType:(NSString *)returnType
701     if ([sendType isEqual:NSStringPboardType]
702             && [self askBackendForStarRegister:nil])
703         return self;
705     return [super validRequestorForSendType:sendType returnType:returnType];
708 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
709                              types:(NSArray *)types
711     if (![types containsObject:NSStringPboardType])
712         return NO;
714     return [self askBackendForStarRegister:pboard];
717 @end // MMWindowController
721 @implementation MMWindowController (Private)
723 - (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize
725     NSSize size = textViewSize;
727     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
728     int right = [ud integerForKey:MMTextInsetRightKey];
729     int bot = [ud integerForKey:MMTextInsetBottomKey];
731     size.width += [textView textContainerOrigin].x + right;
732     size.height += [textView textContainerOrigin].y + bot;
734     if (![tablineSeparator isHidden])
735         ++size.height;
736     if (![tabBarControl isHidden])
737         size.height += [tabBarControl frame].size.height;
739     if ([self bottomScrollbarVisible])
740         size.height += [NSScroller scrollerWidth];
741     if ([self leftScrollbarVisible])
742         size.width += [NSScroller scrollerWidth];
743     if ([self rightScrollbarVisible])
744         size.width += [NSScroller scrollerWidth];
746     return size;
749 - (NSRect)textViewRectForContentSize:(NSSize)contentSize
751     NSRect rect = { 0, 0, contentSize.width, contentSize.height };
753     if (![tablineSeparator isHidden])
754         --rect.size.height;
755     if (![tabBarControl isHidden])
756         rect.size.height -= [tabBarControl frame].size.height;
758     if ([self bottomScrollbarVisible]) {
759         rect.size.height -= [NSScroller scrollerWidth];
760         rect.origin.y += [NSScroller scrollerWidth];
761     }
762     if ([self leftScrollbarVisible]) {
763         rect.size.width -= [NSScroller scrollerWidth];
764         rect.origin.x += [NSScroller scrollerWidth];
765     }
766     if ([self rightScrollbarVisible])
767         rect.size.width -= [NSScroller scrollerWidth];
769     return rect;
772 - (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize
774     NSSize size = textViewSize;
776     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
777     int right = [ud integerForKey:MMTextInsetRightKey];
778     int bot = [ud integerForKey:MMTextInsetBottomKey];
780     size.width -= [textView textContainerOrigin].x + right;
781     size.height -= [textView textContainerOrigin].y + bot;
783     return size;
786 - (void)resizeWindowToFit:(id)sender
788     if (!setupDone) return;
790     NSWindow *win = [self window];
791     NSRect frame = [win frame];
792     NSRect contentRect = [win contentRectForFrameRect:frame];
793     NSSize newSize = [self contentSizeForTextStorageSize:[textStorage size]];
795     // Keep top-left corner of the window fixed when resizing.
796     contentRect.origin.y -= newSize.height - contentRect.size.height;
797     contentRect.size = newSize;
799     frame = [win frameRectForContentRect:contentRect];
800     NSRect maxFrame = [win constrainFrameRect:frame toScreen:[win screen]];
802     // HACK!  Assuming the window frame cannot already be placed too high,
803     // adjust 'maxFrame' so that it at least as high up as the current frame.
804     // The reason for doing this is that constrainFrameRect:toScreen: does not
805     // always seem to utilize as much area as possible.
806     if (NSMaxY(frame) > NSMaxY(maxFrame)) {
807         maxFrame.size.height = frame.origin.y - maxFrame.origin.y
808                 + frame.size.height;
809     }
811     if (!NSEqualRects(maxFrame, frame)) {
812         // The new window frame is too big to fit on the screen, so fit the
813         // text storage to the biggest frame which will fit on the screen.
814         //NSLog(@"Proposed window frame does not fit on the screen!");
815         frame = [self fitWindowToFrame:maxFrame];
816     }
818     //NSLog(@"%s %@", _cmd, NSStringFromRect(frame));
820     // HACK! If the window does resize, then windowDidResize is called which in
821     // turn calls placeViews.  In case the computed new size of the window is
822     // no different from the current size, then we need to call placeViews
823     // manually.
824     if (NSEqualRects(frame, [win frame])) {
825         [self placeViews];
826     } else {
827         [win setFrame:frame display:YES];
828     }
831 - (NSRect)fitWindowToFrame:(NSRect)frame
833     if (!setupDone) return frame;
835     NSWindow *win = [self window];
836     NSRect contentRect = [win contentRectForFrameRect:frame];
837     NSSize size = [self textViewRectForContentSize:contentRect.size].size;
838     size = [self textStorageSizeForTextViewSize:size];
839     size = [textStorage fitToSize:size];
840     size = [self contentSizeForTextStorageSize:size];
842     // Keep top-left corner of 'frame' fixed.
843     contentRect.origin.y -= size.height - contentRect.size.height;
844     contentRect.size = size;
846     return [win frameRectForContentRect:contentRect];
849 - (void)updateResizeIncrements
851     if (!setupDone) return;
853     NSSize size = [textStorage cellSize];
854     [[self window] setContentResizeIncrements:size];
857 - (NSTabViewItem *)addNewTabViewItem
859     // NOTE!  A newly created tab is not by selected by default; the VimTask
860     // decides which tab should be selected at all times.  However, the AppKit
861     // will automatically select the first tab added to a tab view.
863     NSTabViewItem *tvi = [[NSTabViewItem alloc] initWithIdentifier:nil];
865     // NOTE: If this is the first tab it will be automatically selected.
866     vimTaskSelectedTab = YES;
867     [tabView addTabViewItem:tvi];
868     vimTaskSelectedTab = NO;
870     [tvi release];
872     return tvi;
875 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
877     NSArray *tabViewItems = [tabBarControl representedTabViewItems];
878     return [tabViewItems indexOfObject:tvi];
881 - (IBAction)vimMenuItemAction:(id)sender
883     int tag = [sender tag];
885     NSMutableData *data = [NSMutableData data];
886     [data appendBytes:&tag length:sizeof(int)];
888     [vimController sendMessage:ExecuteMenuMsgID data:data wait:NO];
891 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx
893     unsigned i, count = [scrollbars count];
894     for (i = 0; i < count; ++i) {
895         MMScroller *scroller = [scrollbars objectAtIndex:i];
896         if ([scroller identifier] == ident) {
897             if (idx) *idx = i;
898             return scroller;
899         }
900     }
902     return nil;
905 - (BOOL)bottomScrollbarVisible
907     unsigned i, count = [scrollbars count];
908     for (i = 0; i < count; ++i) {
909         MMScroller *scroller = [scrollbars objectAtIndex:i];
910         if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden])
911             return YES;
912     }
914     return NO;
917 - (BOOL)leftScrollbarVisible
919     unsigned i, count = [scrollbars count];
920     for (i = 0; i < count; ++i) {
921         MMScroller *scroller = [scrollbars objectAtIndex:i];
922         if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden])
923             return YES;
924     }
926     return NO;
929 - (BOOL)rightScrollbarVisible
931     unsigned i, count = [scrollbars count];
932     for (i = 0; i < count; ++i) {
933         MMScroller *scroller = [scrollbars objectAtIndex:i];
934         if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden])
935             return YES;
936     }
938     return NO;
941 - (void)placeScrollbars
943     if (!setupDone) return;
945     NSRect textViewFrame = [textView frame];
946     BOOL lsbVisible = [self leftScrollbarVisible];
948     // HACK!  Find the lowest left&right vertical scrollbars, as well as the
949     // rightmost horizontal scrollbar.  This hack continues further down.
950     //
951     // TODO!  Can there be no more than one horizontal scrollbar?  If so, the
952     // code can be simplified.
953     unsigned lowestLeftSbIdx = (unsigned)-1;
954     unsigned lowestRightSbIdx = (unsigned)-1;
955     unsigned rightmostSbIdx = (unsigned)-1;
956     unsigned rowMaxLeft = 0, rowMaxRight = 0, colMax = 0;
957     unsigned i, count = [scrollbars count];
958     for (i = 0; i < count; ++i) {
959         MMScroller *scroller = [scrollbars objectAtIndex:i];
960         if (![scroller isHidden]) {
961             NSRange range = [scroller range];
962             if ([scroller type] == MMScrollerTypeLeft
963                     && range.location >= rowMaxLeft) {
964                 rowMaxLeft = range.location;
965                 lowestLeftSbIdx = i;
966             } else if ([scroller type] == MMScrollerTypeRight
967                     && range.location >= rowMaxRight) {
968                 rowMaxRight = range.location;
969                 lowestRightSbIdx = i;
970             } else if ([scroller type] == MMScrollerTypeBottom
971                     && range.location >= colMax) {
972                 colMax = range.location;
973                 rightmostSbIdx = i;
974             }
975         }
976     }
978     // Place the scrollbars.
979     for (i = 0; i < count; ++i) {
980         MMScroller *scroller = [scrollbars objectAtIndex:i];
981         if ([scroller isHidden])
982             continue;
984         NSRect rect;
985         if ([scroller type] == MMScrollerTypeBottom) {
986             rect = [textStorage rectForColumnsInRange:[scroller range]];
987             rect.size.height = [NSScroller scrollerWidth];
988             if (lsbVisible)
989                 rect.origin.x += [NSScroller scrollerWidth];
991             // HACK!  Make sure the rightmost horizontal scrollbar covers the
992             // text view all the way to the right, otherwise it looks ugly when
993             // the user drags the window to resize.
994             if (i == rightmostSbIdx) {
995                 float w = NSMaxX(textViewFrame) - NSMaxX(rect);
996                 if (w > 0)
997                     rect.size.width += w;
998             }
1000             // Make sure scrollbar rect is bounded by the text view frame.
1001             if (rect.origin.x < textViewFrame.origin.x)
1002                 rect.origin.x = textViewFrame.origin.x;
1003             else if (rect.origin.x > NSMaxX(textViewFrame))
1004                 rect.origin.x = NSMaxX(textViewFrame);
1005             if (NSMaxX(rect) > NSMaxX(textViewFrame))
1006                 rect.size.width -= NSMaxX(rect) - NSMaxX(textViewFrame);
1007             if (rect.size.width < 0)
1008                 rect.size.width = 0;
1009         } else {
1010             rect = [textStorage rectForRowsInRange:[scroller range]];
1011             // Adjust for the fact that text layout is flipped.
1012             rect.origin.y = NSMaxY(textViewFrame) - rect.origin.y
1013                     - rect.size.height;
1014             rect.size.width = [NSScroller scrollerWidth];
1015             if ([scroller type] == MMScrollerTypeRight)
1016                 rect.origin.x = NSMaxX(textViewFrame);
1018             // HACK!  Make sure the lowest vertical scrollbar covers the text
1019             // view all the way to the bottom.  This is done because Vim only
1020             // makes the scrollbar cover the (vim-)window it is associated with
1021             // and this means there is always an empty gap in the scrollbar
1022             // region next to the command line.
1023             // TODO!  Find a nicer way to do this.
1024             if (i == lowestLeftSbIdx || i == lowestRightSbIdx) {
1025                 float h = rect.origin.y + rect.size.height
1026                           - textViewFrame.origin.y;
1027                 if (rect.size.height < h) {
1028                     rect.origin.y = textViewFrame.origin.y;
1029                     rect.size.height = h;
1030                 }
1031             }
1033             // Vertical scrollers must not cover the resize box in the
1034             // bottom-right corner of the window.
1035             if (rect.origin.y < [NSScroller scrollerWidth]) {
1036                 rect.size.height -= [NSScroller scrollerWidth] - rect.origin.y;
1037                 rect.origin.y = [NSScroller scrollerWidth];
1038             }
1040             // Make sure scrollbar rect is bounded by the text view frame.
1041             if (rect.origin.y < textViewFrame.origin.y) {
1042                 rect.size.height -= textViewFrame.origin.y - rect.origin.y;
1043                 rect.origin.y = textViewFrame.origin.y;
1044             } else if (rect.origin.y > NSMaxY(textViewFrame))
1045                 rect.origin.y = NSMaxY(textViewFrame);
1046             if (NSMaxY(rect) > NSMaxY(textViewFrame))
1047                 rect.size.height -= NSMaxY(rect) - NSMaxY(textViewFrame);
1048             if (rect.size.height < 0)
1049                 rect.size.height = 0;
1050         }
1052         //NSLog(@"set scroller #%d frame = %@", i, NSStringFromRect(rect));
1053         NSRect oldRect = [scroller frame];
1054         if (!NSEqualRects(oldRect, rect)) {
1055             [scroller setFrame:rect];
1056             // Clear behind the old scroller frame, or parts of the old
1057             // scroller might still be visible after setFrame:.
1058             [[[self window] contentView] setNeedsDisplayInRect:oldRect];
1059             [scroller setNeedsDisplay:YES];
1060         }
1061     }
1064 - (void)scroll:(id)sender
1066     NSMutableData *data = [NSMutableData data];
1067     long ident = [(MMScroller*)sender identifier];
1068     int hitPart = [sender hitPart];
1069     float value = [sender floatValue];
1071     [data appendBytes:&ident length:sizeof(long)];
1072     [data appendBytes:&hitPart length:sizeof(int)];
1073     [data appendBytes:&value length:sizeof(float)];
1075     [vimController sendMessage:ScrollbarEventMsgID data:data wait:NO];
1078 - (void)placeViews
1080     if (!setupDone) return;
1082     // NOTE!  It is assumed that the window has been resized so that it will
1083     // exactly fit the text storage (possibly after resizing it).  If this is
1084     // not the case the display might be messed up.
1085     NSWindow *win = [self window];
1086     NSRect contentRect = [win contentRectForFrameRect:[win frame]];
1087     NSRect textViewRect = [self textViewRectForContentSize:contentRect.size];
1088     NSSize tsSize = [self textStorageSizeForTextViewSize:textViewRect.size];
1090     int dim[2], rows, cols;
1091     [textStorage getMaxRows:&rows columns:&cols];
1092     [textStorage fitToSize:tsSize rows:&dim[0] columns:&dim[1]];
1094     if (dim[0] != rows || dim[1] != cols) {
1095         //NSLog(@"Notify Vim that text storage dimensions changed to %dx%d",
1096         //        dim[0], dim[1]);
1097         NSData *data = [NSData dataWithBytes:dim length:2*sizeof(int)];
1099         // NOTE! This can get called a lot when in live resize, which causes
1100         // the connection buffers to fill up.  If we wait for the message to be
1101         // sent then the app might become unresponsive.
1102         [vimController sendMessage:SetTextDimensionsMsgID data:data
1103                      wait:![textView inLiveResize]];
1104     }
1106     [textView setFrame:textViewRect];
1108     [self placeScrollbars];
1111 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb
1113     BOOL reply = NO;
1114     id backendProxy = [vimController backendProxy];
1116     if (backendProxy) {
1117         @try {
1118             reply = [backendProxy starRegisterToPasteboard:pb];
1119         }
1120         @catch (NSException *e) {
1121             NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1122         }
1123     }
1125     return reply;
1128 @end // MMWindowController (Private)
1132 @implementation NSTabView (MMExtras)
1134 - (void)removeAllTabViewItems
1136     NSArray *existingItems = [self tabViewItems];
1137     NSEnumerator *e = [existingItems objectEnumerator];
1138     NSTabViewItem *item;
1139     while (item = [e nextObject]){
1140         [self removeTabViewItem:item];
1141     }
1144 @end // NSTabView (MMExtras)
1149 @implementation MMScroller
1151 - (id)initWithIdentifier:(long)ident type:(int)theType
1153     // HACK! NSScroller creates a horizontal scroller if it is init'ed with a
1154     // frame whose with exceeds its height; so create a bogus rect and pass it
1155     // to initWithFrame.
1156     NSRect frame = theType == MMScrollerTypeBottom
1157             ? NSMakeRect(0, 0, 1, 0)
1158             : NSMakeRect(0, 0, 0, 1);
1160     if ((self = [super initWithFrame:frame])) {
1161         identifier = ident;
1162         type = theType;
1163         [self setHidden:YES];
1164         [self setEnabled:YES];
1165     }
1167     return self;
1170 - (long)identifier
1172     return identifier;
1175 - (int)type
1177     return type;
1180 - (NSRange)range
1182     return range;
1185 - (void)setRange:(NSRange)newRange
1187     range = newRange;
1190 - (void)scrollWheel:(NSEvent *)event
1192     // HACK! Pass message on to the text view.
1193     MMWindowController *wc = [[self window] windowController];
1194     [[wc textView] scrollWheel:event];
1197 @end // MMScroller