Changed date
[MacVim/jjgod.git] / MMWindowController.m
blob85143b9ef411df3b2433a636cb690e92398b626c
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 - (NSRect)fitWindowToFrame:(NSRect)frame;
54 - (void)updateResizeIncrements;
55 - (NSTabViewItem *)addNewTabViewItem;
56 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi;
57 - (IBAction)vimMenuItemAction:(id)sender;
58 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx;
59 - (BOOL)bottomScrollbarVisible;
60 - (BOOL)leftScrollbarVisible;
61 - (BOOL)rightScrollbarVisible;
62 - (void)placeScrollbars;
63 - (void)scroll:(id)sender;
64 - (void)placeViews;
65 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb;
66 @end
70 #if 0
71 NSString *buildMenuItemDescriptor(NSMenu *menu, NSString *tail)
73     return menu ? buildMenuItemDescriptor([menu supermenu], [[menu title]
74                     stringByAppendingString:tail])
75                 : tail;
78 NSMutableArray *buildMenuAddress(NSMenu *menu)
80     NSMutableArray *addr;
81     if (menu) {
82         addr = buildMenuAddress([menu supermenu]);
83         [addr addObject:[menu title]];
84     } else {
85         addr = [NSMutableArray array];
86     }
88     return addr;
90 #endif
93 @implementation MMWindowController
95 - (id)initWithVimController:(MMVimController *)controller
97     if ((self = [super initWithWindowNibName:@"EmptyWindow"])) {
98         vimController = controller;
99         scrollbars = [[NSMutableArray alloc] init];
101         // Window cascading is handled by MMAppController.
102         [self setShouldCascadeWindows:NO];
104         // Setup a complete text system.
105         textStorage = [[MMTextStorage alloc] init];
106         NSLayoutManager *lm = [[NSLayoutManager alloc] init];
107         NSTextContainer *tc = [[NSTextContainer alloc] initWithContainerSize:
108                 NSMakeSize(1.0e7,1.0e7)];
110         NSString *typesetterString = [[NSUserDefaults standardUserDefaults]
111                 stringForKey:MMTypesetterKey];
112         if (![typesetterString isEqual:@"NSTypesetter"]) {
113             MMTypesetter *typesetter = [[MMTypesetter alloc] init];
114             [lm setTypesetter:typesetter];
115             [typesetter release];
116         } else {
117             // Only MMTypesetter supports different cell width multipliers.
118             [[NSUserDefaults standardUserDefaults]
119                     setFloat:1.0 forKey:MMCellWidthMultiplierKey];
120         }
122         [tc setWidthTracksTextView:NO];
123         [tc setHeightTracksTextView:NO];
124         [tc setLineFragmentPadding:0];
126         [textStorage addLayoutManager:lm];
127         [lm addTextContainer:tc];
129         NSView *contentView = [[self window] contentView];
130         textView = [[MMTextView alloc] initWithFrame:[contentView frame]
131                                        textContainer:tc];
133         NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
134         int left = [ud integerForKey:MMTextInsetLeftKey];
135         int top = [ud integerForKey:MMTextInsetTopKey];
136         [textView setTextContainerInset:NSMakeSize(left, top)];
138         [contentView addSubview:textView];
140         // The text storage retains the layout manager which in turn retains
141         // the text container.
142         [tc release];
143         [lm release];
145         // Create the tabline separator (which may be visible when the tabline
146         // is hidden).
147         NSRect tabSepRect = [contentView frame];
148         tabSepRect.origin.y = NSMaxY(tabSepRect)-1;
149         tabSepRect.size.height = 1;
150         tablineSeparator = [[NSBox alloc] initWithFrame:tabSepRect];
152         // Create the tab view (which is never visible, but the tab bar control
153         // needs it to function).
154         tabView = [[NSTabView alloc] initWithFrame:NSZeroRect];
156         // Create the tab bar control (which is responsible for actually
157         // drawing the tabline and tabs).
158         NSRect tabFrame = [contentView frame];
159         tabFrame.origin.y = NSMaxY(tabFrame) - 22;
160         tabFrame.size.height = 22;
161         tabBarControl = [[PSMTabBarControl alloc] initWithFrame:tabFrame];
163         [tabView setDelegate:tabBarControl];
165         [tabBarControl setTabView:tabView];
166         [tabBarControl setDelegate:self];
167         [tabBarControl setHidden:YES];
168         [tabBarControl setAutoresizingMask:NSViewWidthSizable|NSViewMinYMargin];
169         [tabBarControl setCellMinWidth:[ud integerForKey:MMTabMinWidthKey]];
170         [tabBarControl setCellMaxWidth:[ud integerForKey:MMTabMaxWidthKey]];
171         [tabBarControl setCellOptimumWidth:
172                          [ud integerForKey:MMTabOptimumWidthKey]];
173         [tabBarControl setShowAddTabButton:YES];
174         [[tabBarControl addTabButton] setTarget:self];
175         [[tabBarControl addTabButton] setAction:@selector(addNewTab:)];
176         [tabBarControl setAllowsDragBetweenWindows:NO];
178         [tablineSeparator setBoxType:NSBoxSeparator];
179         [tablineSeparator setHidden:NO];
180         [tablineSeparator setAutoresizingMask:NSViewWidthSizable
181             | NSViewMinYMargin];
183         [contentView setAutoresizesSubviews:YES];
184         [contentView addSubview:tabBarControl];
185         [contentView addSubview:tablineSeparator];
187         [[self window] setDelegate:self];
188         [[self window] setInitialFirstResponder:textView];
189     }
191     return self;
194 - (void)dealloc
196     //NSLog(@"%@ %s", [self className], _cmd);
198     [tabBarControl release];  tabBarControl = nil;
199     [tabView release];  tabView = nil;
200     [tablineSeparator release];  tablineSeparator = nil;
201     [windowAutosaveKey release];  windowAutosaveKey = nil;
202     [scrollbars release];  scrollbars = nil;
203     [textView release];  textView = nil;
204     [textStorage release];  textStorage = nil;
206     [super dealloc];
209 - (NSString *)description
211     return [NSString stringWithFormat:@"%@ : setupDone=%d windowAutosaveKey=%@ vimController=%@", [self className], setupDone, windowAutosaveKey, vimController];
214 - (MMVimController *)vimController
216     return vimController;
219 - (MMTextView *)textView
221     return textView;
224 - (MMTextStorage *)textStorage
226     return textStorage;
229 - (NSString *)windowAutosaveKey
231     return windowAutosaveKey;
234 - (void)setWindowAutosaveKey:(NSString *)key
236     [windowAutosaveKey autorelease];
237     windowAutosaveKey = [key copy];
240 - (void)cleanup
242     //NSLog(@"%@ %s", [self className], _cmd);
244     setupDone = NO;
245     vimController = nil;
247     // NOTE! There is a bug in PSMTabBarControl in that it retains the delegate
248     // (which is the MMWindowController) so reset the delegate here, otherwise
249     // the MMWindowController never gets released resulting in a pretty serious
250     // memory leak.
251     [tabView setDelegate:nil];
252     [tabBarControl setDelegate:nil];
253     [tabBarControl setTabView:nil];
254     [[self window] setDelegate:nil];
256     // NOTE! There is another bug in PSMTabBarControl where the control is not
257     // removed as an observer, so remove it here (else lots of evil nasty bugs
258     // will come and gnaw at your feet while you are sleeping).
259     [[NSNotificationCenter defaultCenter] removeObserver:tabBarControl];
261     [tabBarControl removeFromSuperviewWithoutNeedingDisplay];
262     [tablineSeparator removeFromSuperviewWithoutNeedingDisplay];
263     [textView removeFromSuperviewWithoutNeedingDisplay];
265     unsigned i, count = [scrollbars count];
266     for (i = 0; i < count; ++i) {
267         MMScroller *sb = [scrollbars objectAtIndex:i];
268         [sb removeFromSuperviewWithoutNeedingDisplay];
269     }
271     [tabView removeAllTabViewItems];
273     [[self window] orderOut:self];
276 - (void)openWindow
278     [[NSApp delegate] windowControllerWillOpen:self];
280     [self addNewTabViewItem];
282     setupDone = YES;
284     [self updateResizeIncrements];
285     [self resizeWindowToFit:self];
286     [[self window] makeKeyAndOrderFront:self];
289 - (void)updateTabsWithData:(NSData *)data
291     const void *p = [data bytes];
292     const void *end = p + [data length];
293     int tabIdx = 0;
295     // HACK!  Current tab is first in the message.  This way it is not
296     // necessary to guess which tab should be the selected one (this can be
297     // problematic for instance when new tabs are created).
298     int curtabIdx = *((int*)p);  p += sizeof(int);
300     NSArray *tabViewItems = [tabBarControl representedTabViewItems];
302     while (p < end) {
303         //int wincount = *((int*)p);  p += sizeof(int);
304         int length = *((int*)p);  p += sizeof(int);
306         NSString *label = [[NSString alloc]
307                 initWithBytesNoCopy:(void*)p
308                              length:length
309                            encoding:NSUTF8StringEncoding
310                        freeWhenDone:NO];
311         p += length;
313         // Set the label of the tab;  add a new tab when needed.
314         NSTabViewItem *tvi = [tabView numberOfTabViewItems] <= tabIdx
315                 ? [self addNewTabViewItem]
316                 : [tabViewItems objectAtIndex:tabIdx];
318         [tvi setLabel:label];
320         [label release];
322         ++tabIdx;
323     }
325     // Remove unused tabs from the NSTabView.  Note that when a tab is closed
326     // the NSTabView will automatically select another tab, but we want Vim to
327     // take care of which tab to select so set the vimTaskSelectedTab flag to
328     // prevent the tab selection message to be passed on to the VimTask.
329     vimTaskSelectedTab = YES;
330     int i, count = [tabView numberOfTabViewItems];
331     for (i = count-1; i >= tabIdx; --i) {
332         id tvi = [tabViewItems objectAtIndex:i];
333         //NSLog(@"Removing tab with index %d", i);
334         [tabView removeTabViewItem:tvi];
335     }
336     vimTaskSelectedTab = NO;
338     [self selectTabWithIndex:curtabIdx];
341 - (void)selectTabWithIndex:(int)idx
343     //NSLog(@"%s%d", _cmd, idx);
345     NSArray *tabViewItems = [tabBarControl representedTabViewItems];
346     if (idx < 0 || idx >= [tabViewItems count]) {
347         NSLog(@"WARNING: No tab with index %d exists.", idx);
348         return;
349     }
351     // Do not try to select a tab if already selected.
352     NSTabViewItem *tvi = [tabViewItems objectAtIndex:idx];
353     if (tvi != [tabView selectedTabViewItem]) {
354         vimTaskSelectedTab = YES;
355         [tabView selectTabViewItem:tvi];
356         vimTaskSelectedTab = NO;
357     }
360 - (void)setTextDimensionsWithRows:(int)rows columns:(int)cols
362     //NSLog(@"setTextDimensionsWithRows:%d columns:%d", rows, cols);
364     [textStorage setMaxRows:rows columns:cols];
366     if (setupDone && ![textView inLiveResize])
367         shouldUpdateWindowSize = YES;
370 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
372     //NSLog(@"Create scroller %d of type %d", ident, type);
374     MMScroller *scroller = [[MMScroller alloc] initWithIdentifier:ident
375                                                              type:type];
376     [scroller setTarget:self];
377     [scroller setAction:@selector(scroll:)];
379     [[[self window] contentView] addSubview:scroller];
380     [scrollbars addObject:scroller];
381     [scroller release];
384 - (void)destroyScrollbarWithIdentifier:(long)ident
386     //NSLog(@"Destroy scroller %d", ident);
388     unsigned idx = 0;
389     MMScroller *scroller = [self scrollbarForIdentifier:ident index:&idx];
390     if (scroller) {
391         [scroller removeFromSuperview];
392         [scrollbars removeObjectAtIndex:idx];
394         if (![scroller isHidden]) {
395             // A visible scroller was removed, so the window must resize to
396             // fit.
397             //NSLog(@"Visible scroller %d was destroyed, resizing window.",
398             //        ident);
399             shouldUpdateWindowSize = YES;
400         }
401     }
404 - (void)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
406     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
407     if (!scroller) return;
409     BOOL wasVisible = ![scroller isHidden];
410     //NSLog(@"%s scroller %d (was %svisible)", visible ? "Show" : "Hide",
411     //      ident, wasVisible ? "" : "in");
412     [scroller setHidden:!visible];
414     if (wasVisible != visible) {
415         // A scroller was hidden or shown, so the window must resize to fit.
416         //NSLog(@"%s scroller %d and resize.", visible ? "Show" : "Hide",
417         //        ident);
418         shouldUpdateWindowSize = YES;
419     }
422 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
424     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
425     NSRange range = NSMakeRange(pos, len);
426     if (!NSEqualRanges(range, [scroller range])) {
427         //NSLog(@"Set range %@ for scroller %d",
428         //        NSStringFromRange(range), ident);
429         [scroller setRange:range];
430         // TODO!  Should only do this once per update.
431         [self placeScrollbars];
432     }
435 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
436                     identifier:(long)ident
438     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
439     //NSLog(@"Set thumb value %.2f proportion %.2f for scroller %d",
440     //        val, prop, ident);
441     [scroller setFloatValue:val knobProportion:prop];
444 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
446     [textStorage setDefaultColorsBackground:back foreground:fore];
447     [textView setBackgroundColor:back];
450 - (void)setFont:(NSFont *)font
452     [[NSFontManager sharedFontManager] setSelectedFont:font isMultiple:NO];
453     [textStorage setFont:font];
454     [self updateResizeIncrements];
457 - (void)processCommandQueueDidFinish
459     if (shouldUpdateWindowSize) {
460         shouldUpdateWindowSize = NO;
461         [self resizeWindowToFit:self];
462     }
465 - (void)popupMenu:(NSMenu *)menu atRow:(int)row column:(int)col
467     if (!setupDone) return;
469     NSEvent *event;
470     if (row >= 0 && col >= 0) {
471         NSSize cellSize = [textStorage cellSize];
472         NSPoint pt = { (col+1)*cellSize.width, [textView frame].size.height
473             - (row+1)*cellSize.height };
475         event = [NSEvent mouseEventWithType:NSRightMouseDown
476                                    location:pt
477                               modifierFlags:0
478                                   timestamp:0
479                                windowNumber:[[self window] windowNumber]
480                                     context:nil
481                                 eventNumber:0
482                                  clickCount:0
483                                    pressure:1.0];
484     } else {
485         event = [textView lastMouseDownEvent];
486     }
488     [NSMenu popUpContextMenu:menu withEvent:event forView:textView];
491 - (void)showTabBar:(BOOL)on
493     [tabBarControl setHidden:!on];
495     if (!on) {
496         NSToolbar *toolbar = [[self window] toolbar]; 
497         [tablineSeparator setHidden:![toolbar isVisible]];
498     } else {
499         [tablineSeparator setHidden:on];
500     }
502     if (setupDone)
503         shouldUpdateWindowSize = YES;
506 - (void)showToolbar:(BOOL)on size:(int)size mode:(int)mode
508     NSToolbar *toolbar = [[self window] toolbar];
509     if (!toolbar) return;
511     [toolbar setSizeMode:size];
512     [toolbar setDisplayMode:mode];
513     [toolbar setVisible:on];
515     if (!on) {
516         [tablineSeparator setHidden:YES];
517     } else {
518         [tablineSeparator setHidden:![tabBarControl isHidden]];
519     }
522 - (void)setMouseShape:(int)shape
524     // This switch should match mshape_names[] in misc2.c.
525     //
526     // TODO: Add missing cursor shapes.
527     switch (shape) {
528         case 2: [[NSCursor IBeamCursor] set]; break;
529         case 3: case 4: [[NSCursor resizeUpDownCursor] set]; break;
530         case 5: case 6: [[NSCursor resizeLeftRightCursor] set]; break;
531         case 9: [[NSCursor crosshairCursor] set]; break;
532         case 10: [[NSCursor pointingHandCursor] set]; break;
533         case 11: [[NSCursor openHandCursor] set]; break;
534         default:
535             [[NSCursor arrowCursor] set]; break;
536     }
538     // Shape 1 indicates that the mouse cursor should be hidden.
539     if (1 == shape)
540         [NSCursor setHiddenUntilMouseMoves:YES];
543 - (void)adjustLinespace:(int)linespace
545     if (textStorage) {
546         [textStorage setLinespace:(float)linespace];
547         shouldUpdateWindowSize = YES;
548     }
551 - (IBAction)addNewTab:(id)sender
553     // NOTE! This can get called a lot if the user holds down the key
554     // equivalent for this action, which causes the ports to fill up.  If we
555     // wait for the message to be sent then the app might become unresponsive.
556     [vimController sendMessage:AddNewTabMsgID data:nil];
559 - (IBAction)toggleToolbar:(id)sender
561     [vimController sendMessage:ToggleToolbarMsgID data:nil];
566 // -- PSMTabBarControl delegate ----------------------------------------------
569 - (BOOL)tabView:(NSTabView *)theTabView shouldSelectTabViewItem:
570     (NSTabViewItem *)tabViewItem
572     // NOTE: It would be reasonable to think that 'shouldSelect...' implies
573     // that this message only gets sent when the user clicks the tab.
574     // Unfortunately it is not so, which is why we need the
575     // 'vimTaskSelectedTab' flag.
576     //
577     // HACK!  The selection message should not be propagated to the VimTask if
578     // the VimTask selected the tab (e.g. as opposed the user clicking the
579     // tab).  The delegate method has no way of knowing who initiated the
580     // selection so a flag is set when the VimTask initiated the selection.
581     if (!vimTaskSelectedTab) {
582         // Propagate the selection message to the VimTask.
583         int idx = [self representedIndexOfTabViewItem:tabViewItem];
584         if (NSNotFound != idx) {
585             NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
586             [vimController sendMessage:SelectTabMsgID data:data];
587         }
588     }
590     // Unless Vim selected the tab, return NO, and let Vim decide if the tab
591     // should get selected or not.
592     return vimTaskSelectedTab;
595 - (BOOL)tabView:(NSTabView *)theTabView shouldCloseTabViewItem:
596         (NSTabViewItem *)tabViewItem
598     // HACK!  This method is only called when the user clicks the close button
599     // on the tab.  Instead of letting the tab bar close the tab, we return NO
600     // and pass a message on to Vim to let it handle the closing.
601     int idx = [self representedIndexOfTabViewItem:tabViewItem];
602     //NSLog(@"Closing tab with index %d", idx);
603     NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
604     [vimController sendMessage:CloseTabMsgID data:data];
606     return NO;
609 - (void)tabView:(NSTabView *)theTabView didDragTabViewItem:
610         (NSTabViewItem *)tabViewItem toIndex:(int)idx
612     NSMutableData *data = [NSMutableData data];
613     [data appendBytes:&idx length:sizeof(int)];
615     [vimController sendMessage:DraggedTabMsgID data:data];
621 // -- NSWindow delegate ------------------------------------------------------
623 - (void)windowDidBecomeMain:(NSNotification *)notification
625     [vimController sendMessage:GotFocusMsgID data:nil];
627     if (textStorage)
628         [[NSFontManager sharedFontManager] setSelectedFont:[textStorage font]
629                                                 isMultiple:NO];
632 - (void)windowDidResignMain:(NSNotification *)notification
634     [vimController sendMessage:LostFocusMsgID data:nil];
636     if (textView)
637         [textView hideMarkedTextField];
640 - (BOOL)windowShouldClose:(id)sender
642     [vimController sendMessage:VimShouldCloseMsgID data:nil];
643     return NO;
646 - (void)windowDidMove:(NSNotification *)notification
648     if (setupDone && windowAutosaveKey) {
649         NSRect frame = [[self window] frame];
650         NSPoint topLeft = { frame.origin.x, NSMaxY(frame) };
651         NSString *topLeftString = NSStringFromPoint(topLeft);
653         [[NSUserDefaults standardUserDefaults]
654             setObject:topLeftString forKey:windowAutosaveKey];
655     }
658 - (void)windowDidResize:(id)sender
660     if (!setupDone) return;
661     [self placeViews];
664 - (NSRect)windowWillUseStandardFrame:(NSWindow *)win
665                         defaultFrame:(NSRect)frame
667     // HACK!  For some reason 'frame' is not always constrained to fit on the
668     // screen (e.g. it may overlap the menu bar), so first constrain it to the
669     // screen; otherwise the new frame we compute may be too large and this
670     // will mess up the display after the window resizes.
671     frame = [win constrainFrameRect:frame toScreen:[win screen]];
673     // HACK!  If the top of 'frame' is lower than the current window frame,
674     // increase 'frame' so that their tops align.  Really, 'frame' should
675     // already have its top at least as high as the current window frame, but
676     // for some reason this is not always the case.
677     // (See resizeWindowToFit: for a similar hack.)
678     NSRect cur = [win frame];
679     if (NSMaxY(cur) > NSMaxY(frame)) {
680         frame.size.height = cur.origin.y - frame.origin.y + cur.size.height;
681     }
683     frame = [self fitWindowToFrame:frame];
685     // Keep old width and horizontal position unless user clicked with the
686     // Command key is held down.
687     NSEvent *event = [NSApp currentEvent];
688     if (!([event type] == NSLeftMouseUp
689             && [event modifierFlags] & NSCommandKeyMask)) {
690         NSRect currentFrame = [win frame];
691         frame.size.width = currentFrame.size.width;
692         frame.origin.x = currentFrame.origin.x;
693     }
695     return frame;
701 // -- Services menu delegate -------------------------------------------------
703 - (id)validRequestorForSendType:(NSString *)sendType
704                      returnType:(NSString *)returnType
706     if ([sendType isEqual:NSStringPboardType]
707             && [self askBackendForStarRegister:nil])
708         return self;
710     return [super validRequestorForSendType:sendType returnType:returnType];
713 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
714                              types:(NSArray *)types
716     if (![types containsObject:NSStringPboardType])
717         return NO;
719     return [self askBackendForStarRegister:pboard];
722 - (void)resizeWindowToFit:(id)sender
724     // NOTE: Be very careful when you call this method!  Do not call while
725     // processing command queue, instead set 'shouldUpdateWindowSize' to YES.
726     // The only other place it is currently called is when live resize ends.
727     // This is done to ensure that the text view and window sizes match up
728     // (they may become out of sync if a SetTextDimensionsMsgID message to the
729     // backend is dropped).
731     if (!setupDone) return;
733     NSWindow *win = [self window];
734     NSRect frame = [win frame];
735     NSRect contentRect = [win contentRectForFrameRect:frame];
736     NSSize newSize = [self contentSizeForTextStorageSize:[textStorage size]];
738     // Keep top-left corner of the window fixed when resizing.
739     contentRect.origin.y -= newSize.height - contentRect.size.height;
740     contentRect.size = newSize;
742     frame = [win frameRectForContentRect:contentRect];
743     NSRect maxFrame = [win constrainFrameRect:frame toScreen:[win screen]];
745     // HACK!  Assuming the window frame cannot already be placed too high,
746     // adjust 'maxFrame' so that it at least as high up as the current frame.
747     // The reason for doing this is that constrainFrameRect:toScreen: does not
748     // always seem to utilize as much area as possible.
749     if (NSMaxY(frame) > NSMaxY(maxFrame)) {
750         maxFrame.size.height = frame.origin.y - maxFrame.origin.y
751                 + frame.size.height;
752     }
754     if (!NSEqualRects(maxFrame, frame)) {
755         // The new window frame is too big to fit on the screen, so fit the
756         // text storage to the biggest frame which will fit on the screen.
757         //NSLog(@"Proposed window frame does not fit on the screen!");
758         frame = [self fitWindowToFrame:maxFrame];
759     }
761     //NSLog(@"%s %@", _cmd, NSStringFromRect(frame));
763     // HACK! If the window does resize, then windowDidResize is called which in
764     // turn calls placeViews.  In case the computed new size of the window is
765     // no different from the current size, then we need to call placeViews
766     // manually.
767     if (NSEqualRects(frame, [win frame])) {
768         [self placeViews];
769     } else {
770         [win setFrame:frame display:YES];
771     }
774 @end // MMWindowController
778 @implementation MMWindowController (Private)
780 - (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize
782     NSSize size = textViewSize;
784     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
785     int right = [ud integerForKey:MMTextInsetRightKey];
786     int bot = [ud integerForKey:MMTextInsetBottomKey];
788     size.width += [textView textContainerOrigin].x + right;
789     size.height += [textView textContainerOrigin].y + bot;
791     if (![tablineSeparator isHidden])
792         ++size.height;
793     if (![tabBarControl isHidden])
794         size.height += [tabBarControl frame].size.height;
796     if ([self bottomScrollbarVisible])
797         size.height += [NSScroller scrollerWidth];
798     if ([self leftScrollbarVisible])
799         size.width += [NSScroller scrollerWidth];
800     if ([self rightScrollbarVisible])
801         size.width += [NSScroller scrollerWidth];
803     return size;
806 - (NSRect)textViewRectForContentSize:(NSSize)contentSize
808     NSRect rect = { 0, 0, contentSize.width, contentSize.height };
810     if (![tablineSeparator isHidden])
811         --rect.size.height;
812     if (![tabBarControl isHidden])
813         rect.size.height -= [tabBarControl frame].size.height;
815     if ([self bottomScrollbarVisible]) {
816         rect.size.height -= [NSScroller scrollerWidth];
817         rect.origin.y += [NSScroller scrollerWidth];
818     }
819     if ([self leftScrollbarVisible]) {
820         rect.size.width -= [NSScroller scrollerWidth];
821         rect.origin.x += [NSScroller scrollerWidth];
822     }
823     if ([self rightScrollbarVisible])
824         rect.size.width -= [NSScroller scrollerWidth];
826     return rect;
829 - (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize
831     NSSize size = textViewSize;
833     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
834     int right = [ud integerForKey:MMTextInsetRightKey];
835     int bot = [ud integerForKey:MMTextInsetBottomKey];
837     size.width -= [textView textContainerOrigin].x + right;
838     size.height -= [textView textContainerOrigin].y + bot;
840     return size;
843 - (NSRect)fitWindowToFrame:(NSRect)frame
845     if (!setupDone) return frame;
847     NSWindow *win = [self window];
848     NSRect contentRect = [win contentRectForFrameRect:frame];
849     NSSize size = [self textViewRectForContentSize:contentRect.size].size;
850     size = [self textStorageSizeForTextViewSize:size];
851     size = [textStorage fitToSize:size];
852     size = [self contentSizeForTextStorageSize:size];
854     // Keep top-left corner of 'frame' fixed.
855     contentRect.origin.y -= size.height - contentRect.size.height;
856     contentRect.size = size;
858     return [win frameRectForContentRect:contentRect];
861 - (void)updateResizeIncrements
863     if (!setupDone) return;
865     NSSize size = [textStorage cellSize];
866     [[self window] setContentResizeIncrements:size];
869 - (NSTabViewItem *)addNewTabViewItem
871     // NOTE!  A newly created tab is not by selected by default; the VimTask
872     // decides which tab should be selected at all times.  However, the AppKit
873     // will automatically select the first tab added to a tab view.
875     NSTabViewItem *tvi = [[NSTabViewItem alloc] initWithIdentifier:nil];
877     // NOTE: If this is the first tab it will be automatically selected.
878     vimTaskSelectedTab = YES;
879     [tabView addTabViewItem:tvi];
880     vimTaskSelectedTab = NO;
882     [tvi release];
884     return tvi;
887 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
889     NSArray *tabViewItems = [tabBarControl representedTabViewItems];
890     return [tabViewItems indexOfObject:tvi];
893 - (IBAction)vimMenuItemAction:(id)sender
895     int tag = [sender tag];
897     NSMutableData *data = [NSMutableData data];
898     [data appendBytes:&tag length:sizeof(int)];
900     [vimController sendMessage:ExecuteMenuMsgID data:data];
903 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx
905     unsigned i, count = [scrollbars count];
906     for (i = 0; i < count; ++i) {
907         MMScroller *scroller = [scrollbars objectAtIndex:i];
908         if ([scroller identifier] == ident) {
909             if (idx) *idx = i;
910             return scroller;
911         }
912     }
914     return nil;
917 - (BOOL)bottomScrollbarVisible
919     unsigned i, count = [scrollbars count];
920     for (i = 0; i < count; ++i) {
921         MMScroller *scroller = [scrollbars objectAtIndex:i];
922         if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden])
923             return YES;
924     }
926     return NO;
929 - (BOOL)leftScrollbarVisible
931     unsigned i, count = [scrollbars count];
932     for (i = 0; i < count; ++i) {
933         MMScroller *scroller = [scrollbars objectAtIndex:i];
934         if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden])
935             return YES;
936     }
938     return NO;
941 - (BOOL)rightScrollbarVisible
943     unsigned i, count = [scrollbars count];
944     for (i = 0; i < count; ++i) {
945         MMScroller *scroller = [scrollbars objectAtIndex:i];
946         if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden])
947             return YES;
948     }
950     return NO;
953 - (void)placeScrollbars
955     if (!setupDone) return;
957     NSRect textViewFrame = [textView frame];
958     BOOL lsbVisible = [self leftScrollbarVisible];
960     // HACK!  Find the lowest left&right vertical scrollbars, as well as the
961     // rightmost horizontal scrollbar.  This hack continues further down.
962     //
963     // TODO!  Can there be no more than one horizontal scrollbar?  If so, the
964     // code can be simplified.
965     unsigned lowestLeftSbIdx = (unsigned)-1;
966     unsigned lowestRightSbIdx = (unsigned)-1;
967     unsigned rightmostSbIdx = (unsigned)-1;
968     unsigned rowMaxLeft = 0, rowMaxRight = 0, colMax = 0;
969     unsigned i, count = [scrollbars count];
970     for (i = 0; i < count; ++i) {
971         MMScroller *scroller = [scrollbars objectAtIndex:i];
972         if (![scroller isHidden]) {
973             NSRange range = [scroller range];
974             if ([scroller type] == MMScrollerTypeLeft
975                     && range.location >= rowMaxLeft) {
976                 rowMaxLeft = range.location;
977                 lowestLeftSbIdx = i;
978             } else if ([scroller type] == MMScrollerTypeRight
979                     && range.location >= rowMaxRight) {
980                 rowMaxRight = range.location;
981                 lowestRightSbIdx = i;
982             } else if ([scroller type] == MMScrollerTypeBottom
983                     && range.location >= colMax) {
984                 colMax = range.location;
985                 rightmostSbIdx = i;
986             }
987         }
988     }
990     // Place the scrollbars.
991     for (i = 0; i < count; ++i) {
992         MMScroller *scroller = [scrollbars objectAtIndex:i];
993         if ([scroller isHidden])
994             continue;
996         NSRect rect;
997         if ([scroller type] == MMScrollerTypeBottom) {
998             rect = [textStorage rectForColumnsInRange:[scroller range]];
999             rect.size.height = [NSScroller scrollerWidth];
1000             if (lsbVisible)
1001                 rect.origin.x += [NSScroller scrollerWidth];
1003             // HACK!  Make sure the rightmost horizontal scrollbar covers the
1004             // text view all the way to the right, otherwise it looks ugly when
1005             // the user drags the window to resize.
1006             if (i == rightmostSbIdx) {
1007                 float w = NSMaxX(textViewFrame) - NSMaxX(rect);
1008                 if (w > 0)
1009                     rect.size.width += w;
1010             }
1012             // Make sure scrollbar rect is bounded by the text view frame.
1013             if (rect.origin.x < textViewFrame.origin.x)
1014                 rect.origin.x = textViewFrame.origin.x;
1015             else if (rect.origin.x > NSMaxX(textViewFrame))
1016                 rect.origin.x = NSMaxX(textViewFrame);
1017             if (NSMaxX(rect) > NSMaxX(textViewFrame))
1018                 rect.size.width -= NSMaxX(rect) - NSMaxX(textViewFrame);
1019             if (rect.size.width < 0)
1020                 rect.size.width = 0;
1021         } else {
1022             rect = [textStorage rectForRowsInRange:[scroller range]];
1023             // Adjust for the fact that text layout is flipped.
1024             rect.origin.y = NSMaxY(textViewFrame) - rect.origin.y
1025                     - rect.size.height;
1026             rect.size.width = [NSScroller scrollerWidth];
1027             if ([scroller type] == MMScrollerTypeRight)
1028                 rect.origin.x = NSMaxX(textViewFrame);
1030             // HACK!  Make sure the lowest vertical scrollbar covers the text
1031             // view all the way to the bottom.  This is done because Vim only
1032             // makes the scrollbar cover the (vim-)window it is associated with
1033             // and this means there is always an empty gap in the scrollbar
1034             // region next to the command line.
1035             // TODO!  Find a nicer way to do this.
1036             if (i == lowestLeftSbIdx || i == lowestRightSbIdx) {
1037                 float h = rect.origin.y + rect.size.height
1038                           - textViewFrame.origin.y;
1039                 if (rect.size.height < h) {
1040                     rect.origin.y = textViewFrame.origin.y;
1041                     rect.size.height = h;
1042                 }
1043             }
1045             // Vertical scrollers must not cover the resize box in the
1046             // bottom-right corner of the window.
1047             if (rect.origin.y < [NSScroller scrollerWidth]) {
1048                 rect.size.height -= [NSScroller scrollerWidth] - rect.origin.y;
1049                 rect.origin.y = [NSScroller scrollerWidth];
1050             }
1052             // Make sure scrollbar rect is bounded by the text view frame.
1053             if (rect.origin.y < textViewFrame.origin.y) {
1054                 rect.size.height -= textViewFrame.origin.y - rect.origin.y;
1055                 rect.origin.y = textViewFrame.origin.y;
1056             } else if (rect.origin.y > NSMaxY(textViewFrame))
1057                 rect.origin.y = NSMaxY(textViewFrame);
1058             if (NSMaxY(rect) > NSMaxY(textViewFrame))
1059                 rect.size.height -= NSMaxY(rect) - NSMaxY(textViewFrame);
1060             if (rect.size.height < 0)
1061                 rect.size.height = 0;
1062         }
1064         //NSLog(@"set scroller #%d frame = %@", i, NSStringFromRect(rect));
1065         NSRect oldRect = [scroller frame];
1066         if (!NSEqualRects(oldRect, rect)) {
1067             [scroller setFrame:rect];
1068             // Clear behind the old scroller frame, or parts of the old
1069             // scroller might still be visible after setFrame:.
1070             [[[self window] contentView] setNeedsDisplayInRect:oldRect];
1071             [scroller setNeedsDisplay:YES];
1072         }
1073     }
1076 - (void)scroll:(id)sender
1078     NSMutableData *data = [NSMutableData data];
1079     long ident = [(MMScroller*)sender identifier];
1080     int hitPart = [sender hitPart];
1081     float value = [sender floatValue];
1083     [data appendBytes:&ident length:sizeof(long)];
1084     [data appendBytes:&hitPart length:sizeof(int)];
1085     [data appendBytes:&value length:sizeof(float)];
1087     [vimController sendMessage:ScrollbarEventMsgID data:data];
1090 - (void)placeViews
1092     if (!setupDone) return;
1094     // NOTE!  It is assumed that the window has been resized so that it will
1095     // exactly fit the text storage (possibly after resizing it).  If this is
1096     // not the case the display might be messed up.
1097     NSWindow *win = [self window];
1098     NSRect contentRect = [win contentRectForFrameRect:[win frame]];
1099     NSRect textViewRect = [self textViewRectForContentSize:contentRect.size];
1100     NSSize tsSize = [self textStorageSizeForTextViewSize:textViewRect.size];
1102     int dim[2], rows, cols;
1103     [textStorage getMaxRows:&rows columns:&cols];
1104     [textStorage fitToSize:tsSize rows:&dim[0] columns:&dim[1]];
1106     if (dim[0] != rows || dim[1] != cols) {
1107         //NSLog(@"Notify Vim that text storage dimensions changed to %dx%d",
1108         //        dim[0], dim[1]);
1109         NSData *data = [NSData dataWithBytes:dim length:2*sizeof(int)];
1111         // NOTE! This can get called a lot when in live resize, which causes
1112         // the connection buffers to fill up.  If we wait for the message to be
1113         // sent then the app might become unresponsive.
1114         [vimController sendMessage:SetTextDimensionsMsgID data:data];
1115     }
1117     [textView setFrame:textViewRect];
1119     [self placeScrollbars];
1122 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb
1124     BOOL reply = NO;
1125     id backendProxy = [vimController backendProxy];
1127     if (backendProxy) {
1128         @try {
1129             reply = [backendProxy starRegisterToPasteboard:pb];
1130         }
1131         @catch (NSException *e) {
1132             NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1133         }
1134     }
1136     return reply;
1139 @end // MMWindowController (Private)
1143 @implementation NSTabView (MMExtras)
1145 - (void)removeAllTabViewItems
1147     NSArray *existingItems = [self tabViewItems];
1148     NSEnumerator *e = [existingItems objectEnumerator];
1149     NSTabViewItem *item;
1150     while (item = [e nextObject]){
1151         [self removeTabViewItem:item];
1152     }
1155 @end // NSTabView (MMExtras)
1160 @implementation MMScroller
1162 - (id)initWithIdentifier:(long)ident type:(int)theType
1164     // HACK! NSScroller creates a horizontal scroller if it is init'ed with a
1165     // frame whose with exceeds its height; so create a bogus rect and pass it
1166     // to initWithFrame.
1167     NSRect frame = theType == MMScrollerTypeBottom
1168             ? NSMakeRect(0, 0, 1, 0)
1169             : NSMakeRect(0, 0, 0, 1);
1171     if ((self = [super initWithFrame:frame])) {
1172         identifier = ident;
1173         type = theType;
1174         [self setHidden:YES];
1175         [self setEnabled:YES];
1176     }
1178     return self;
1181 - (long)identifier
1183     return identifier;
1186 - (int)type
1188     return type;
1191 - (NSRange)range
1193     return range;
1196 - (void)setRange:(NSRange)newRange
1198     range = newRange;
1201 - (void)scrollWheel:(NSEvent *)event
1203     // HACK! Pass message on to the text view.
1204     MMWindowController *wc = [[self window] windowController];
1205     [[wc textView] scrollWheel:event];
1208 @end // MMScroller