Added defaultForegroundColor
[MacVim/jjgod.git] / MMWindowController.m
blobf653eae78b0732e80d923c8b2d964a107aeb1933
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];
632 - (BOOL)windowShouldClose:(id)sender
634     [vimController sendMessage:VimShouldCloseMsgID data:nil wait:YES];
635     return NO;
638 - (void)windowDidMove:(NSNotification *)notification
640     if (setupDone && windowAutosaveKey) {
641         NSRect frame = [[self window] frame];
642         NSPoint topLeft = { frame.origin.x, NSMaxY(frame) };
643         NSString *topLeftString = NSStringFromPoint(topLeft);
645         [[NSUserDefaults standardUserDefaults]
646             setObject:topLeftString forKey:windowAutosaveKey];
647     }
650 - (void)windowDidResize:(id)sender
652     if (!setupDone) return;
653     [self placeViews];
656 - (NSRect)windowWillUseStandardFrame:(NSWindow *)win
657                         defaultFrame:(NSRect)frame
659     // HACK!  For some reason 'frame' is not always constrained to fit on the
660     // screen (e.g. it may overlap the menu bar), so first constrain it to the
661     // screen; otherwise the new frame we compute may be too large and this
662     // will mess up the display after the window resizes.
663     frame = [win constrainFrameRect:frame toScreen:[win screen]];
665     // HACK!  If the top of 'frame' is lower than the current window frame,
666     // increase 'frame' so that their tops align.  Really, 'frame' should
667     // already have its top at least as high as the current window frame, but
668     // for some reason this is not always the case.
669     // (See resizeWindowToFit: for a similar hack.)
670     NSRect cur = [win frame];
671     if (NSMaxY(cur) > NSMaxY(frame)) {
672         frame.size.height = cur.origin.y - frame.origin.y + cur.size.height;
673     }
675     frame = [self fitWindowToFrame:frame];
677     // Keep old width and horizontal position unless user clicked with the
678     // Command key is held down.
679     NSEvent *event = [NSApp currentEvent];
680     if (!([event type] == NSLeftMouseUp
681             && [event modifierFlags] & NSCommandKeyMask)) {
682         NSRect currentFrame = [win frame];
683         frame.size.width = currentFrame.size.width;
684         frame.origin.x = currentFrame.origin.x;
685     }
687     return frame;
693 // -- Services menu delegate -------------------------------------------------
695 - (id)validRequestorForSendType:(NSString *)sendType
696                      returnType:(NSString *)returnType
698     if ([sendType isEqual:NSStringPboardType]
699             && [self askBackendForStarRegister:nil])
700         return self;
702     return [super validRequestorForSendType:sendType returnType:returnType];
705 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
706                              types:(NSArray *)types
708     if (![types containsObject:NSStringPboardType])
709         return NO;
711     return [self askBackendForStarRegister:pboard];
714 @end // MMWindowController
718 @implementation MMWindowController (Private)
720 - (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize
722     NSSize size = textViewSize;
724     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
725     int right = [ud integerForKey:MMTextInsetRightKey];
726     int bot = [ud integerForKey:MMTextInsetBottomKey];
728     size.width += [textView textContainerOrigin].x + right;
729     size.height += [textView textContainerOrigin].y + bot;
731     if (![tablineSeparator isHidden])
732         ++size.height;
733     if (![tabBarControl isHidden])
734         size.height += [tabBarControl frame].size.height;
736     if ([self bottomScrollbarVisible])
737         size.height += [NSScroller scrollerWidth];
738     if ([self leftScrollbarVisible])
739         size.width += [NSScroller scrollerWidth];
740     if ([self rightScrollbarVisible])
741         size.width += [NSScroller scrollerWidth];
743     return size;
746 - (NSRect)textViewRectForContentSize:(NSSize)contentSize
748     NSRect rect = { 0, 0, contentSize.width, contentSize.height };
750     if (![tablineSeparator isHidden])
751         --rect.size.height;
752     if (![tabBarControl isHidden])
753         rect.size.height -= [tabBarControl frame].size.height;
755     if ([self bottomScrollbarVisible]) {
756         rect.size.height -= [NSScroller scrollerWidth];
757         rect.origin.y += [NSScroller scrollerWidth];
758     }
759     if ([self leftScrollbarVisible]) {
760         rect.size.width -= [NSScroller scrollerWidth];
761         rect.origin.x += [NSScroller scrollerWidth];
762     }
763     if ([self rightScrollbarVisible])
764         rect.size.width -= [NSScroller scrollerWidth];
766     return rect;
769 - (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize
771     NSSize size = textViewSize;
773     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
774     int right = [ud integerForKey:MMTextInsetRightKey];
775     int bot = [ud integerForKey:MMTextInsetBottomKey];
777     size.width -= [textView textContainerOrigin].x + right;
778     size.height -= [textView textContainerOrigin].y + bot;
780     return size;
783 - (void)resizeWindowToFit:(id)sender
785     if (!setupDone) return;
787     NSWindow *win = [self window];
788     NSRect frame = [win frame];
789     NSRect contentRect = [win contentRectForFrameRect:frame];
790     NSSize newSize = [self contentSizeForTextStorageSize:[textStorage size]];
792     // Keep top-left corner of the window fixed when resizing.
793     contentRect.origin.y -= newSize.height - contentRect.size.height;
794     contentRect.size = newSize;
796     frame = [win frameRectForContentRect:contentRect];
797     NSRect maxFrame = [win constrainFrameRect:frame toScreen:[win screen]];
799     // HACK!  Assuming the window frame cannot already be placed too high,
800     // adjust 'maxFrame' so that it at least as high up as the current frame.
801     // The reason for doing this is that constrainFrameRect:toScreen: does not
802     // always seem to utilize as much area as possible.
803     if (NSMaxY(frame) > NSMaxY(maxFrame)) {
804         maxFrame.size.height = frame.origin.y - maxFrame.origin.y
805                 + frame.size.height;
806     }
808     if (!NSEqualRects(maxFrame, frame)) {
809         // The new window frame is too big to fit on the screen, so fit the
810         // text storage to the biggest frame which will fit on the screen.
811         //NSLog(@"Proposed window frame does not fit on the screen!");
812         frame = [self fitWindowToFrame:maxFrame];
813     }
815     //NSLog(@"%s %@", _cmd, NSStringFromRect(frame));
817     // HACK! If the window does resize, then windowDidResize is called which in
818     // turn calls placeViews.  In case the computed new size of the window is
819     // no different from the current size, then we need to call placeViews
820     // manually.
821     if (NSEqualRects(frame, [win frame])) {
822         [self placeViews];
823     } else {
824         [win setFrame:frame display:YES];
825     }
828 - (NSRect)fitWindowToFrame:(NSRect)frame
830     if (!setupDone) return frame;
832     NSWindow *win = [self window];
833     NSRect contentRect = [win contentRectForFrameRect:frame];
834     NSSize size = [self textViewRectForContentSize:contentRect.size].size;
835     size = [self textStorageSizeForTextViewSize:size];
836     size = [textStorage fitToSize:size];
837     size = [self contentSizeForTextStorageSize:size];
839     // Keep top-left corner of 'frame' fixed.
840     contentRect.origin.y -= size.height - contentRect.size.height;
841     contentRect.size = size;
843     return [win frameRectForContentRect:contentRect];
846 - (void)updateResizeIncrements
848     if (!setupDone) return;
850     NSSize size = [textStorage cellSize];
851     [[self window] setContentResizeIncrements:size];
854 - (NSTabViewItem *)addNewTabViewItem
856     // NOTE!  A newly created tab is not by selected by default; the VimTask
857     // decides which tab should be selected at all times.  However, the AppKit
858     // will automatically select the first tab added to a tab view.
860     NSTabViewItem *tvi = [[NSTabViewItem alloc] initWithIdentifier:nil];
862     // NOTE: If this is the first tab it will be automatically selected.
863     vimTaskSelectedTab = YES;
864     [tabView addTabViewItem:tvi];
865     vimTaskSelectedTab = NO;
867     [tvi release];
869     return tvi;
872 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
874     NSArray *tabViewItems = [tabBarControl representedTabViewItems];
875     return [tabViewItems indexOfObject:tvi];
878 - (IBAction)vimMenuItemAction:(id)sender
880     int tag = [sender tag];
882     NSMutableData *data = [NSMutableData data];
883     [data appendBytes:&tag length:sizeof(int)];
885     [vimController sendMessage:ExecuteMenuMsgID data:data wait:NO];
888 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx
890     unsigned i, count = [scrollbars count];
891     for (i = 0; i < count; ++i) {
892         MMScroller *scroller = [scrollbars objectAtIndex:i];
893         if ([scroller identifier] == ident) {
894             if (idx) *idx = i;
895             return scroller;
896         }
897     }
899     return nil;
902 - (BOOL)bottomScrollbarVisible
904     unsigned i, count = [scrollbars count];
905     for (i = 0; i < count; ++i) {
906         MMScroller *scroller = [scrollbars objectAtIndex:i];
907         if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden])
908             return YES;
909     }
911     return NO;
914 - (BOOL)leftScrollbarVisible
916     unsigned i, count = [scrollbars count];
917     for (i = 0; i < count; ++i) {
918         MMScroller *scroller = [scrollbars objectAtIndex:i];
919         if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden])
920             return YES;
921     }
923     return NO;
926 - (BOOL)rightScrollbarVisible
928     unsigned i, count = [scrollbars count];
929     for (i = 0; i < count; ++i) {
930         MMScroller *scroller = [scrollbars objectAtIndex:i];
931         if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden])
932             return YES;
933     }
935     return NO;
938 - (void)placeScrollbars
940     if (!setupDone) return;
942     NSRect textViewFrame = [textView frame];
943     BOOL lsbVisible = [self leftScrollbarVisible];
945     // HACK!  Find the lowest left&right vertical scrollbars, as well as the
946     // rightmost horizontal scrollbar.  This hack continues further down.
947     //
948     // TODO!  Can there be no more than one horizontal scrollbar?  If so, the
949     // code can be simplified.
950     unsigned lowestLeftSbIdx = (unsigned)-1;
951     unsigned lowestRightSbIdx = (unsigned)-1;
952     unsigned rightmostSbIdx = (unsigned)-1;
953     unsigned rowMaxLeft = 0, rowMaxRight = 0, colMax = 0;
954     unsigned i, count = [scrollbars count];
955     for (i = 0; i < count; ++i) {
956         MMScroller *scroller = [scrollbars objectAtIndex:i];
957         if (![scroller isHidden]) {
958             NSRange range = [scroller range];
959             if ([scroller type] == MMScrollerTypeLeft
960                     && range.location >= rowMaxLeft) {
961                 rowMaxLeft = range.location;
962                 lowestLeftSbIdx = i;
963             } else if ([scroller type] == MMScrollerTypeRight
964                     && range.location >= rowMaxRight) {
965                 rowMaxRight = range.location;
966                 lowestRightSbIdx = i;
967             } else if ([scroller type] == MMScrollerTypeBottom
968                     && range.location >= colMax) {
969                 colMax = range.location;
970                 rightmostSbIdx = i;
971             }
972         }
973     }
975     // Place the scrollbars.
976     for (i = 0; i < count; ++i) {
977         MMScroller *scroller = [scrollbars objectAtIndex:i];
978         if ([scroller isHidden])
979             continue;
981         NSRect rect;
982         if ([scroller type] == MMScrollerTypeBottom) {
983             rect = [textStorage rectForColumnsInRange:[scroller range]];
984             rect.size.height = [NSScroller scrollerWidth];
985             if (lsbVisible)
986                 rect.origin.x += [NSScroller scrollerWidth];
988             // HACK!  Make sure the rightmost horizontal scrollbar covers the
989             // text view all the way to the right, otherwise it looks ugly when
990             // the user drags the window to resize.
991             if (i == rightmostSbIdx) {
992                 float w = NSMaxX(textViewFrame) - NSMaxX(rect);
993                 if (w > 0)
994                     rect.size.width += w;
995             }
997             // Make sure scrollbar rect is bounded by the text view frame.
998             if (rect.origin.x < textViewFrame.origin.x)
999                 rect.origin.x = textViewFrame.origin.x;
1000             else if (rect.origin.x > NSMaxX(textViewFrame))
1001                 rect.origin.x = NSMaxX(textViewFrame);
1002             if (NSMaxX(rect) > NSMaxX(textViewFrame))
1003                 rect.size.width -= NSMaxX(rect) - NSMaxX(textViewFrame);
1004             if (rect.size.width < 0)
1005                 rect.size.width = 0;
1006         } else {
1007             rect = [textStorage rectForRowsInRange:[scroller range]];
1008             // Adjust for the fact that text layout is flipped.
1009             rect.origin.y = NSMaxY(textViewFrame) - rect.origin.y
1010                     - rect.size.height;
1011             rect.size.width = [NSScroller scrollerWidth];
1012             if ([scroller type] == MMScrollerTypeRight)
1013                 rect.origin.x = NSMaxX(textViewFrame);
1015             // HACK!  Make sure the lowest vertical scrollbar covers the text
1016             // view all the way to the bottom.  This is done because Vim only
1017             // makes the scrollbar cover the (vim-)window it is associated with
1018             // and this means there is always an empty gap in the scrollbar
1019             // region next to the command line.
1020             // TODO!  Find a nicer way to do this.
1021             if (i == lowestLeftSbIdx || i == lowestRightSbIdx) {
1022                 float h = rect.origin.y + rect.size.height
1023                           - textViewFrame.origin.y;
1024                 if (rect.size.height < h) {
1025                     rect.origin.y = textViewFrame.origin.y;
1026                     rect.size.height = h;
1027                 }
1028             }
1030             // Vertical scrollers must not cover the resize box in the
1031             // bottom-right corner of the window.
1032             if (rect.origin.y < [NSScroller scrollerWidth]) {
1033                 rect.size.height -= [NSScroller scrollerWidth] - rect.origin.y;
1034                 rect.origin.y = [NSScroller scrollerWidth];
1035             }
1037             // Make sure scrollbar rect is bounded by the text view frame.
1038             if (rect.origin.y < textViewFrame.origin.y) {
1039                 rect.size.height -= textViewFrame.origin.y - rect.origin.y;
1040                 rect.origin.y = textViewFrame.origin.y;
1041             } else if (rect.origin.y > NSMaxY(textViewFrame))
1042                 rect.origin.y = NSMaxY(textViewFrame);
1043             if (NSMaxY(rect) > NSMaxY(textViewFrame))
1044                 rect.size.height -= NSMaxY(rect) - NSMaxY(textViewFrame);
1045             if (rect.size.height < 0)
1046                 rect.size.height = 0;
1047         }
1049         //NSLog(@"set scroller #%d frame = %@", i, NSStringFromRect(rect));
1050         NSRect oldRect = [scroller frame];
1051         if (!NSEqualRects(oldRect, rect)) {
1052             [scroller setFrame:rect];
1053             // Clear behind the old scroller frame, or parts of the old
1054             // scroller might still be visible after setFrame:.
1055             [[[self window] contentView] setNeedsDisplayInRect:oldRect];
1056             [scroller setNeedsDisplay:YES];
1057         }
1058     }
1061 - (void)scroll:(id)sender
1063     NSMutableData *data = [NSMutableData data];
1064     long ident = [(MMScroller*)sender identifier];
1065     int hitPart = [sender hitPart];
1066     float value = [sender floatValue];
1068     [data appendBytes:&ident length:sizeof(long)];
1069     [data appendBytes:&hitPart length:sizeof(int)];
1070     [data appendBytes:&value length:sizeof(float)];
1072     [vimController sendMessage:ScrollbarEventMsgID data:data wait:NO];
1075 - (void)placeViews
1077     if (!setupDone) return;
1079     // NOTE!  It is assumed that the window has been resized so that it will
1080     // exactly fit the text storage (possibly after resizing it).  If this is
1081     // not the case the display might be messed up.
1082     NSWindow *win = [self window];
1083     NSRect contentRect = [win contentRectForFrameRect:[win frame]];
1084     NSRect textViewRect = [self textViewRectForContentSize:contentRect.size];
1085     NSSize tsSize = [self textStorageSizeForTextViewSize:textViewRect.size];
1087     int dim[2], rows, cols;
1088     [textStorage getMaxRows:&rows columns:&cols];
1089     [textStorage fitToSize:tsSize rows:&dim[0] columns:&dim[1]];
1091     if (dim[0] != rows || dim[1] != cols) {
1092         //NSLog(@"Notify Vim that text storage dimensions changed to %dx%d",
1093         //        dim[0], dim[1]);
1094         NSData *data = [NSData dataWithBytes:dim length:2*sizeof(int)];
1096         // NOTE! This can get called a lot when in live resize, which causes
1097         // the connection buffers to fill up.  If we wait for the message to be
1098         // sent then the app might become unresponsive.
1099         [vimController sendMessage:SetTextDimensionsMsgID data:data
1100                      wait:![textView inLiveResize]];
1101     }
1103     [textView setFrame:textViewRect];
1105     [self placeScrollbars];
1108 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb
1110     BOOL reply = NO;
1111     id backendProxy = [vimController backendProxy];
1113     if (backendProxy) {
1114         @try {
1115             reply = [backendProxy starRegisterToPasteboard:pb];
1116         }
1117         @catch (NSException *e) {
1118             NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1119         }
1120     }
1122     return reply;
1125 @end // MMWindowController (Private)
1129 @implementation NSTabView (MMExtras)
1131 - (void)removeAllTabViewItems
1133     NSArray *existingItems = [self tabViewItems];
1134     NSEnumerator *e = [existingItems objectEnumerator];
1135     NSTabViewItem *item;
1136     while (item = [e nextObject]){
1137         [self removeTabViewItem:item];
1138     }
1141 @end // NSTabView (MMExtras)
1146 @implementation MMScroller
1148 - (id)initWithIdentifier:(long)ident type:(int)theType
1150     // HACK! NSScroller creates a horizontal scroller if it is init'ed with a
1151     // frame whose with exceeds its height; so create a bogus rect and pass it
1152     // to initWithFrame.
1153     NSRect frame = theType == MMScrollerTypeBottom
1154             ? NSMakeRect(0, 0, 1, 0)
1155             : NSMakeRect(0, 0, 0, 1);
1157     if ((self = [super initWithFrame:frame])) {
1158         identifier = ident;
1159         type = theType;
1160         [self setHidden:YES];
1161         [self setEnabled:YES];
1162     }
1164     return self;
1167 - (long)identifier
1169     return identifier;
1172 - (int)type
1174     return type;
1177 - (NSRange)range
1179     return range;
1182 - (void)setRange:(NSRange)newRange
1184     range = newRange;
1187 - (void)scrollWheel:(NSEvent *)event
1189     // HACK! Pass message on to the text view.
1190     MMWindowController *wc = [[self window] windowController];
1191     [[wc textView] scrollWheel:event];
1194 @end // MMScroller