Added "Bitstream Vera Sans Mono" as the default font.
[MacVim/jjgod.git] / MMWindowController.m
blobd91e023bd3262e51632465d7e95a6282b0713e8a
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     setupDone = YES;
285     [self updateResizeIncrements];
286     [self resizeWindowToFit:self];
287     [[self window] makeKeyAndOrderFront:self];
290 - (void)updateTabsWithData:(NSData *)data
292     const void *p = [data bytes];
293     const void *end = p + [data length];
294     int tabIdx = 0;
296     // HACK!  Current tab is first in the message.  This way it is not
297     // necessary to guess which tab should be the selected one (this can be
298     // problematic for instance when new tabs are created).
299     int curtabIdx = *((int*)p);  p += sizeof(int);
301     NSArray *tabViewItems = [tabBarControl representedTabViewItems];
303     while (p < end) {
304         //int wincount = *((int*)p);  p += sizeof(int);
305         int length = *((int*)p);  p += sizeof(int);
307         NSString *label = [[NSString alloc]
308                 initWithBytesNoCopy:(void*)p
309                              length:length
310                            encoding:NSUTF8StringEncoding
311                        freeWhenDone:NO];
312         p += length;
314         // Set the label of the tab;  add a new tab when needed.
315         NSTabViewItem *tvi = [tabView numberOfTabViewItems] <= tabIdx
316                 ? [self addNewTabViewItem]
317                 : [tabViewItems objectAtIndex:tabIdx];
319         [tvi setLabel:label];
321         [label release];
323         ++tabIdx;
324     }
326     // Remove unused tabs from the NSTabView.  Note that when a tab is closed
327     // the NSTabView will automatically select another tab, but we want Vim to
328     // take care of which tab to select so set the vimTaskSelectedTab flag to
329     // prevent the tab selection message to be passed on to the VimTask.
330     vimTaskSelectedTab = YES;
331     int i, count = [tabView numberOfTabViewItems];
332     for (i = count-1; i >= tabIdx; --i) {
333         id tvi = [tabViewItems objectAtIndex:i];
334         //NSLog(@"Removing tab with index %d", i);
335         [tabView removeTabViewItem:tvi];
336     }
337     vimTaskSelectedTab = NO;
339     [self selectTabWithIndex:curtabIdx];
342 - (void)selectTabWithIndex:(int)idx
344     //NSLog(@"%s%d", _cmd, idx);
346     NSArray *tabViewItems = [tabBarControl representedTabViewItems];
347     if (idx < 0 || idx >= [tabViewItems count]) {
348         NSLog(@"WARNING: No tab with index %d exists.", idx);
349         return;
350     }
352     // Do not try to select a tab if already selected.
353     NSTabViewItem *tvi = [tabViewItems objectAtIndex:idx];
354     if (tvi != [tabView selectedTabViewItem]) {
355         vimTaskSelectedTab = YES;
356         [tabView selectTabViewItem:tvi];
357         vimTaskSelectedTab = NO;
358     }
361 - (void)setTextDimensionsWithRows:(int)rows columns:(int)cols
363     //NSLog(@"setTextDimensionsWithRows:%d columns:%d", rows, cols);
365     [textStorage setMaxRows:rows columns:cols];
367     if (setupDone && ![textView inLiveResize])
368         shouldUpdateWindowSize = YES;
371 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
373     //NSLog(@"Create scroller %d of type %d", ident, type);
375     MMScroller *scroller = [[MMScroller alloc] initWithIdentifier:ident
376                                                              type:type];
377     [scroller setTarget:self];
378     [scroller setAction:@selector(scroll:)];
380     [[[self window] contentView] addSubview:scroller];
381     [scrollbars addObject:scroller];
382     [scroller release];
385 - (void)destroyScrollbarWithIdentifier:(long)ident
387     //NSLog(@"Destroy scroller %d", ident);
389     unsigned idx = 0;
390     MMScroller *scroller = [self scrollbarForIdentifier:ident index:&idx];
391     if (scroller) {
392         [scroller removeFromSuperview];
393         [scrollbars removeObjectAtIndex:idx];
395         if (![scroller isHidden]) {
396             // A visible scroller was removed, so the window must resize to
397             // fit.
398             //NSLog(@"Visible scroller %d was destroyed, resizing window.",
399             //        ident);
400             shouldUpdateWindowSize = YES;
401         }
402     }
405 - (void)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
407     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
408     if (!scroller) return;
410     BOOL wasVisible = ![scroller isHidden];
411     //NSLog(@"%s scroller %d (was %svisible)", visible ? "Show" : "Hide",
412     //      ident, wasVisible ? "" : "in");
413     [scroller setHidden:!visible];
415     if (wasVisible != visible) {
416         // A scroller was hidden or shown, so the window must resize to fit.
417         //NSLog(@"%s scroller %d and resize.", visible ? "Show" : "Hide",
418         //        ident);
419         shouldUpdateWindowSize = YES;
420     }
423 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
425     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
426     NSRange range = NSMakeRange(pos, len);
427     if (!NSEqualRanges(range, [scroller range])) {
428         //NSLog(@"Set range %@ for scroller %d",
429         //        NSStringFromRange(range), ident);
430         [scroller setRange:range];
431         // TODO!  Should only do this once per update.
432         [self placeScrollbars];
433     }
436 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
437                     identifier:(long)ident
439     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
440     //NSLog(@"Set thumb value %.2f proportion %.2f for scroller %d",
441     //        val, prop, ident);
442     [scroller setFloatValue:val knobProportion:prop];
445 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
447     [textStorage setDefaultColorsBackground:back foreground:fore];
448     [textView setBackgroundColor:back];
451 - (void)setFont:(NSFont *)font
453     [[NSFontManager sharedFontManager] setSelectedFont:font isMultiple:NO];
454     [textStorage setFont:font];
455     [self updateResizeIncrements];
458 - (void)processCommandQueueDidFinish
460     if (shouldUpdateWindowSize) {
461         shouldUpdateWindowSize = NO;
462         [self resizeWindowToFit:self];
463     }
466 - (void)popupMenu:(NSMenu *)menu atRow:(int)row column:(int)col
468     if (!setupDone) return;
470     NSEvent *event;
471     if (row >= 0 && col >= 0) {
472         NSSize cellSize = [textStorage cellSize];
473         NSPoint pt = { (col+1)*cellSize.width, [textView frame].size.height
474             - (row+1)*cellSize.height };
476         event = [NSEvent mouseEventWithType:NSRightMouseDown
477                                    location:pt
478                               modifierFlags:0
479                                   timestamp:0
480                                windowNumber:[[self window] windowNumber]
481                                     context:nil
482                                 eventNumber:0
483                                  clickCount:0
484                                    pressure:1.0];
485     } else {
486         event = [textView lastMouseDownEvent];
487     }
489     [NSMenu popUpContextMenu:menu withEvent:event forView:textView];
492 - (void)showTabBar:(BOOL)on
494     [tabBarControl setHidden:!on];
496     if (!on) {
497         NSToolbar *toolbar = [[self window] toolbar]; 
498         [tablineSeparator setHidden:![toolbar isVisible]];
499     } else {
500         [tablineSeparator setHidden:on];
501     }
503     if (setupDone)
504         shouldUpdateWindowSize = YES;
507 - (void)showToolbar:(BOOL)on size:(int)size mode:(int)mode
509     NSToolbar *toolbar = [[self window] toolbar];
510     if (!toolbar) return;
512     [toolbar setSizeMode:size];
513     [toolbar setDisplayMode:mode];
514     [toolbar setVisible:on];
516     if (!on) {
517         [tablineSeparator setHidden:YES];
518     } else {
519         [tablineSeparator setHidden:![tabBarControl isHidden]];
520     }
523 - (void)setMouseShape:(int)shape
525     // This switch should match mshape_names[] in misc2.c.
526     //
527     // TODO: Add missing cursor shapes.
528     switch (shape) {
529         case 2: [[NSCursor IBeamCursor] set]; break;
530         case 3: case 4: [[NSCursor resizeUpDownCursor] set]; break;
531         case 5: case 6: [[NSCursor resizeLeftRightCursor] set]; break;
532         case 9: [[NSCursor crosshairCursor] set]; break;
533         case 10: [[NSCursor pointingHandCursor] set]; break;
534         case 11: [[NSCursor openHandCursor] set]; break;
535         default:
536             [[NSCursor arrowCursor] set]; break;
537     }
539     // Shape 1 indicates that the mouse cursor should be hidden.
540     if (1 == shape)
541         [NSCursor setHiddenUntilMouseMoves:YES];
544 - (void)adjustLinespace:(int)linespace
546     if (textStorage) {
547         [textStorage setLinespace:(float)linespace];
548         shouldUpdateWindowSize = YES;
549     }
552 - (void)liveResizeDidEnd
554     // TODO: Don't duplicate code from placeViews.
556     if (!setupDone) return;
558     // NOTE!  It is assumed that the window has been resized so that it will
559     // exactly fit the text storage (possibly after resizing it).  If this is
560     // not the case the display might be messed up.
561     BOOL resizeFailed = NO;
562     NSWindow *win = [self window];
563     NSRect contentRect = [win contentRectForFrameRect:[win frame]];
564     NSRect textViewRect = [self textViewRectForContentSize:contentRect.size];
565     NSSize tsSize = [self textStorageSizeForTextViewSize:textViewRect.size];
567     int dim[2], rows, cols;
568     [textStorage getMaxRows:&rows columns:&cols];
569     [textStorage fitToSize:tsSize rows:&dim[0] columns:&dim[1]];
571     if (dim[0] != rows || dim[1] != cols) {
572         NSData *data = [NSData dataWithBytes:dim length:2*sizeof(int)];
574         // NOTE:  Since we're at the end of a live resize we want to make sure
575         // that the SetTextDimensionsMsgID message reaches Vim, else Vim and
576         // MacVim will have inconsistent states (i.e. the text view will be too
577         // large or too small for the window size).  Thus, add a timeout (this
578         // may have to be tweaked) and take note if the message was sent or
579         // not.
580         resizeFailed = ![vimController sendMessageNow:SetTextDimensionsMsgID
581                                                  data:data
582                                               timeout:.5];
583     }
585     [textView setFrame:textViewRect];
587     [self placeScrollbars];
589     if (resizeFailed) {
590         // Force the window size to match the text view size otherwise Vim and
591         // MacVim will have inconsistent states.
592         [self resizeWindowToFit:self];
593     }
596 - (IBAction)addNewTab:(id)sender
598     // NOTE! This can get called a lot if the user holds down the key
599     // equivalent for this action, which causes the ports to fill up.  If we
600     // wait for the message to be sent then the app might become unresponsive.
601     [vimController sendMessage:AddNewTabMsgID data:nil];
604 - (IBAction)toggleToolbar:(id)sender
606     [vimController sendMessage:ToggleToolbarMsgID data:nil];
611 // -- PSMTabBarControl delegate ----------------------------------------------
614 - (BOOL)tabView:(NSTabView *)theTabView shouldSelectTabViewItem:
615     (NSTabViewItem *)tabViewItem
617     // NOTE: It would be reasonable to think that 'shouldSelect...' implies
618     // that this message only gets sent when the user clicks the tab.
619     // Unfortunately it is not so, which is why we need the
620     // 'vimTaskSelectedTab' flag.
621     //
622     // HACK!  The selection message should not be propagated to the VimTask if
623     // the VimTask selected the tab (e.g. as opposed the user clicking the
624     // tab).  The delegate method has no way of knowing who initiated the
625     // selection so a flag is set when the VimTask initiated the selection.
626     if (!vimTaskSelectedTab) {
627         // Propagate the selection message to the VimTask.
628         int idx = [self representedIndexOfTabViewItem:tabViewItem];
629         if (NSNotFound != idx) {
630             NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
631             [vimController sendMessage:SelectTabMsgID data:data];
632         }
633     }
635     // Unless Vim selected the tab, return NO, and let Vim decide if the tab
636     // should get selected or not.
637     return vimTaskSelectedTab;
640 - (BOOL)tabView:(NSTabView *)theTabView shouldCloseTabViewItem:
641         (NSTabViewItem *)tabViewItem
643     // HACK!  This method is only called when the user clicks the close button
644     // on the tab.  Instead of letting the tab bar close the tab, we return NO
645     // and pass a message on to Vim to let it handle the closing.
646     int idx = [self representedIndexOfTabViewItem:tabViewItem];
647     //NSLog(@"Closing tab with index %d", idx);
648     NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
649     [vimController sendMessage:CloseTabMsgID data:data];
651     return NO;
654 - (void)tabView:(NSTabView *)theTabView didDragTabViewItem:
655         (NSTabViewItem *)tabViewItem toIndex:(int)idx
657     NSMutableData *data = [NSMutableData data];
658     [data appendBytes:&idx length:sizeof(int)];
660     [vimController sendMessage:DraggedTabMsgID data:data];
666 // -- NSWindow delegate ------------------------------------------------------
668 - (void)windowDidBecomeMain:(NSNotification *)notification
670     [vimController sendMessage:GotFocusMsgID data:nil];
672     if (textStorage)
673         [[NSFontManager sharedFontManager] setSelectedFont:[textStorage font]
674                                                 isMultiple:NO];
677 - (void)windowDidResignMain:(NSNotification *)notification
679     [vimController sendMessage:LostFocusMsgID data:nil];
681     if (textView)
682         [textView hideMarkedTextField];
685 - (BOOL)windowShouldClose:(id)sender
687     [vimController sendMessage:VimShouldCloseMsgID data:nil];
688     return NO;
691 - (void)windowDidMove:(NSNotification *)notification
693     if (setupDone && windowAutosaveKey) {
694         NSRect frame = [[self window] frame];
695         NSPoint topLeft = { frame.origin.x, NSMaxY(frame) };
696         NSString *topLeftString = NSStringFromPoint(topLeft);
698         [[NSUserDefaults standardUserDefaults]
699             setObject:topLeftString forKey:windowAutosaveKey];
700     }
703 - (void)windowDidResize:(id)sender
705     if (!setupDone) return;
706     [self placeViews];
709 - (NSRect)windowWillUseStandardFrame:(NSWindow *)win
710                         defaultFrame:(NSRect)frame
712     // HACK!  For some reason 'frame' is not always constrained to fit on the
713     // screen (e.g. it may overlap the menu bar), so first constrain it to the
714     // screen; otherwise the new frame we compute may be too large and this
715     // will mess up the display after the window resizes.
716     frame = [win constrainFrameRect:frame toScreen:[win screen]];
718     // HACK!  If the top of 'frame' is lower than the current window frame,
719     // increase 'frame' so that their tops align.  Really, 'frame' should
720     // already have its top at least as high as the current window frame, but
721     // for some reason this is not always the case.
722     // (See resizeWindowToFit: for a similar hack.)
723     NSRect cur = [win frame];
724     if (NSMaxY(cur) > NSMaxY(frame)) {
725         frame.size.height = cur.origin.y - frame.origin.y + cur.size.height;
726     }
728     frame = [self fitWindowToFrame:frame];
730     // Keep old width and horizontal position unless user clicked with the
731     // Command key is held down.
732     NSEvent *event = [NSApp currentEvent];
733     if (!([event type] == NSLeftMouseUp
734             && [event modifierFlags] & NSCommandKeyMask)) {
735         NSRect currentFrame = [win frame];
736         frame.size.width = currentFrame.size.width;
737         frame.origin.x = currentFrame.origin.x;
738     }
740     return frame;
746 // -- Services menu delegate -------------------------------------------------
748 - (id)validRequestorForSendType:(NSString *)sendType
749                      returnType:(NSString *)returnType
751     if ([sendType isEqual:NSStringPboardType]
752             && [self askBackendForStarRegister:nil])
753         return self;
755     return [super validRequestorForSendType:sendType returnType:returnType];
758 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
759                              types:(NSArray *)types
761     if (![types containsObject:NSStringPboardType])
762         return NO;
764     return [self askBackendForStarRegister:pboard];
767 @end // MMWindowController
771 @implementation MMWindowController (Private)
773 - (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize
775     NSSize size = textViewSize;
777     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
778     int right = [ud integerForKey:MMTextInsetRightKey];
779     int bot = [ud integerForKey:MMTextInsetBottomKey];
781     size.width += [textView textContainerOrigin].x + right;
782     size.height += [textView textContainerOrigin].y + bot;
784     if (![tablineSeparator isHidden])
785         ++size.height;
786     if (![tabBarControl isHidden])
787         size.height += [tabBarControl frame].size.height;
789     if ([self bottomScrollbarVisible])
790         size.height += [NSScroller scrollerWidth];
791     if ([self leftScrollbarVisible])
792         size.width += [NSScroller scrollerWidth];
793     if ([self rightScrollbarVisible])
794         size.width += [NSScroller scrollerWidth];
796     return size;
799 - (NSRect)textViewRectForContentSize:(NSSize)contentSize
801     NSRect rect = { 0, 0, contentSize.width, contentSize.height };
803     if (![tablineSeparator isHidden])
804         --rect.size.height;
805     if (![tabBarControl isHidden])
806         rect.size.height -= [tabBarControl frame].size.height;
808     if ([self bottomScrollbarVisible]) {
809         rect.size.height -= [NSScroller scrollerWidth];
810         rect.origin.y += [NSScroller scrollerWidth];
811     }
812     if ([self leftScrollbarVisible]) {
813         rect.size.width -= [NSScroller scrollerWidth];
814         rect.origin.x += [NSScroller scrollerWidth];
815     }
816     if ([self rightScrollbarVisible])
817         rect.size.width -= [NSScroller scrollerWidth];
819     return rect;
822 - (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize
824     NSSize size = textViewSize;
826     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
827     int right = [ud integerForKey:MMTextInsetRightKey];
828     int bot = [ud integerForKey:MMTextInsetBottomKey];
830     size.width -= [textView textContainerOrigin].x + right;
831     size.height -= [textView textContainerOrigin].y + bot;
833     return size;
836 - (void)resizeWindowToFit:(id)sender
838     // NOTE: Be very careful when you call this method!  Do not call while
839     // processing command queue, instead set 'shouldUpdateWindowSize' to YES.
840     // The only other place it is currently called is when live resize ends.
841     // This is done to ensure that the text view and window sizes match up
842     // (they may become out of sync if a SetTextDimensionsMsgID message to the
843     // backend is dropped).
845     if (!setupDone) return;
847     NSWindow *win = [self window];
848     NSRect frame = [win frame];
849     NSRect contentRect = [win contentRectForFrameRect:frame];
850     NSSize newSize = [self contentSizeForTextStorageSize:[textStorage size]];
852     // Keep top-left corner of the window fixed when resizing.
853     contentRect.origin.y -= newSize.height - contentRect.size.height;
854     contentRect.size = newSize;
856     frame = [win frameRectForContentRect:contentRect];
857     NSRect maxFrame = [win constrainFrameRect:frame toScreen:[win screen]];
859     // HACK!  Assuming the window frame cannot already be placed too high,
860     // adjust 'maxFrame' so that it at least as high up as the current frame.
861     // The reason for doing this is that constrainFrameRect:toScreen: does not
862     // always seem to utilize as much area as possible.
863     if (NSMaxY(frame) > NSMaxY(maxFrame)) {
864         maxFrame.size.height = frame.origin.y - maxFrame.origin.y
865                 + frame.size.height;
866     }
868     if (!NSEqualRects(maxFrame, frame)) {
869         // The new window frame is too big to fit on the screen, so fit the
870         // text storage to the biggest frame which will fit on the screen.
871         //NSLog(@"Proposed window frame does not fit on the screen!");
872         frame = [self fitWindowToFrame:maxFrame];
873     }
875     //NSLog(@"%s %@", _cmd, NSStringFromRect(frame));
877     // HACK! If the window does resize, then windowDidResize is called which in
878     // turn calls placeViews.  In case the computed new size of the window is
879     // no different from the current size, then we need to call placeViews
880     // manually.
881     if (NSEqualRects(frame, [win frame])) {
882         [self placeViews];
883     } else {
884         [win setFrame:frame display:YES];
885     }
888 - (NSRect)fitWindowToFrame:(NSRect)frame
890     if (!setupDone) return frame;
892     NSWindow *win = [self window];
893     NSRect contentRect = [win contentRectForFrameRect:frame];
894     NSSize size = [self textViewRectForContentSize:contentRect.size].size;
895     size = [self textStorageSizeForTextViewSize:size];
896     size = [textStorage fitToSize:size];
897     size = [self contentSizeForTextStorageSize:size];
899     // Keep top-left corner of 'frame' fixed.
900     contentRect.origin.y -= size.height - contentRect.size.height;
901     contentRect.size = size;
903     return [win frameRectForContentRect:contentRect];
906 - (void)updateResizeIncrements
908     if (!setupDone) return;
910     NSSize size = [textStorage cellSize];
911     [[self window] setContentResizeIncrements:size];
914 - (NSTabViewItem *)addNewTabViewItem
916     // NOTE!  A newly created tab is not by selected by default; the VimTask
917     // decides which tab should be selected at all times.  However, the AppKit
918     // will automatically select the first tab added to a tab view.
920     NSTabViewItem *tvi = [[NSTabViewItem alloc] initWithIdentifier:nil];
922     // NOTE: If this is the first tab it will be automatically selected.
923     vimTaskSelectedTab = YES;
924     [tabView addTabViewItem:tvi];
925     vimTaskSelectedTab = NO;
927     [tvi release];
929     return tvi;
932 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
934     NSArray *tabViewItems = [tabBarControl representedTabViewItems];
935     return [tabViewItems indexOfObject:tvi];
938 - (IBAction)vimMenuItemAction:(id)sender
940     int tag = [sender tag];
942     NSMutableData *data = [NSMutableData data];
943     [data appendBytes:&tag length:sizeof(int)];
945     [vimController sendMessage:ExecuteMenuMsgID data:data];
948 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx
950     unsigned i, count = [scrollbars count];
951     for (i = 0; i < count; ++i) {
952         MMScroller *scroller = [scrollbars objectAtIndex:i];
953         if ([scroller identifier] == ident) {
954             if (idx) *idx = i;
955             return scroller;
956         }
957     }
959     return nil;
962 - (BOOL)bottomScrollbarVisible
964     unsigned i, count = [scrollbars count];
965     for (i = 0; i < count; ++i) {
966         MMScroller *scroller = [scrollbars objectAtIndex:i];
967         if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden])
968             return YES;
969     }
971     return NO;
974 - (BOOL)leftScrollbarVisible
976     unsigned i, count = [scrollbars count];
977     for (i = 0; i < count; ++i) {
978         MMScroller *scroller = [scrollbars objectAtIndex:i];
979         if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden])
980             return YES;
981     }
983     return NO;
986 - (BOOL)rightScrollbarVisible
988     unsigned i, count = [scrollbars count];
989     for (i = 0; i < count; ++i) {
990         MMScroller *scroller = [scrollbars objectAtIndex:i];
991         if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden])
992             return YES;
993     }
995     return NO;
998 - (void)placeScrollbars
1000     if (!setupDone) return;
1002     NSRect textViewFrame = [textView frame];
1003     BOOL lsbVisible = [self leftScrollbarVisible];
1005     // HACK!  Find the lowest left&right vertical scrollbars, as well as the
1006     // rightmost horizontal scrollbar.  This hack continues further down.
1007     //
1008     // TODO!  Can there be no more than one horizontal scrollbar?  If so, the
1009     // code can be simplified.
1010     unsigned lowestLeftSbIdx = (unsigned)-1;
1011     unsigned lowestRightSbIdx = (unsigned)-1;
1012     unsigned rightmostSbIdx = (unsigned)-1;
1013     unsigned rowMaxLeft = 0, rowMaxRight = 0, colMax = 0;
1014     unsigned i, count = [scrollbars count];
1015     for (i = 0; i < count; ++i) {
1016         MMScroller *scroller = [scrollbars objectAtIndex:i];
1017         if (![scroller isHidden]) {
1018             NSRange range = [scroller range];
1019             if ([scroller type] == MMScrollerTypeLeft
1020                     && range.location >= rowMaxLeft) {
1021                 rowMaxLeft = range.location;
1022                 lowestLeftSbIdx = i;
1023             } else if ([scroller type] == MMScrollerTypeRight
1024                     && range.location >= rowMaxRight) {
1025                 rowMaxRight = range.location;
1026                 lowestRightSbIdx = i;
1027             } else if ([scroller type] == MMScrollerTypeBottom
1028                     && range.location >= colMax) {
1029                 colMax = range.location;
1030                 rightmostSbIdx = i;
1031             }
1032         }
1033     }
1035     // Place the scrollbars.
1036     for (i = 0; i < count; ++i) {
1037         MMScroller *scroller = [scrollbars objectAtIndex:i];
1038         if ([scroller isHidden])
1039             continue;
1041         NSRect rect;
1042         if ([scroller type] == MMScrollerTypeBottom) {
1043             rect = [textStorage rectForColumnsInRange:[scroller range]];
1044             rect.size.height = [NSScroller scrollerWidth];
1045             if (lsbVisible)
1046                 rect.origin.x += [NSScroller scrollerWidth];
1048             // HACK!  Make sure the rightmost horizontal scrollbar covers the
1049             // text view all the way to the right, otherwise it looks ugly when
1050             // the user drags the window to resize.
1051             if (i == rightmostSbIdx) {
1052                 float w = NSMaxX(textViewFrame) - NSMaxX(rect);
1053                 if (w > 0)
1054                     rect.size.width += w;
1055             }
1057             // Make sure scrollbar rect is bounded by the text view frame.
1058             if (rect.origin.x < textViewFrame.origin.x)
1059                 rect.origin.x = textViewFrame.origin.x;
1060             else if (rect.origin.x > NSMaxX(textViewFrame))
1061                 rect.origin.x = NSMaxX(textViewFrame);
1062             if (NSMaxX(rect) > NSMaxX(textViewFrame))
1063                 rect.size.width -= NSMaxX(rect) - NSMaxX(textViewFrame);
1064             if (rect.size.width < 0)
1065                 rect.size.width = 0;
1066         } else {
1067             rect = [textStorage rectForRowsInRange:[scroller range]];
1068             // Adjust for the fact that text layout is flipped.
1069             rect.origin.y = NSMaxY(textViewFrame) - rect.origin.y
1070                     - rect.size.height;
1071             rect.size.width = [NSScroller scrollerWidth];
1072             if ([scroller type] == MMScrollerTypeRight)
1073                 rect.origin.x = NSMaxX(textViewFrame);
1075             // HACK!  Make sure the lowest vertical scrollbar covers the text
1076             // view all the way to the bottom.  This is done because Vim only
1077             // makes the scrollbar cover the (vim-)window it is associated with
1078             // and this means there is always an empty gap in the scrollbar
1079             // region next to the command line.
1080             // TODO!  Find a nicer way to do this.
1081             if (i == lowestLeftSbIdx || i == lowestRightSbIdx) {
1082                 float h = rect.origin.y + rect.size.height
1083                           - textViewFrame.origin.y;
1084                 if (rect.size.height < h) {
1085                     rect.origin.y = textViewFrame.origin.y;
1086                     rect.size.height = h;
1087                 }
1088             }
1090             // Vertical scrollers must not cover the resize box in the
1091             // bottom-right corner of the window.
1092             if (rect.origin.y < [NSScroller scrollerWidth]) {
1093                 rect.size.height -= [NSScroller scrollerWidth] - rect.origin.y;
1094                 rect.origin.y = [NSScroller scrollerWidth];
1095             }
1097             // Make sure scrollbar rect is bounded by the text view frame.
1098             if (rect.origin.y < textViewFrame.origin.y) {
1099                 rect.size.height -= textViewFrame.origin.y - rect.origin.y;
1100                 rect.origin.y = textViewFrame.origin.y;
1101             } else if (rect.origin.y > NSMaxY(textViewFrame))
1102                 rect.origin.y = NSMaxY(textViewFrame);
1103             if (NSMaxY(rect) > NSMaxY(textViewFrame))
1104                 rect.size.height -= NSMaxY(rect) - NSMaxY(textViewFrame);
1105             if (rect.size.height < 0)
1106                 rect.size.height = 0;
1107         }
1109         //NSLog(@"set scroller #%d frame = %@", i, NSStringFromRect(rect));
1110         NSRect oldRect = [scroller frame];
1111         if (!NSEqualRects(oldRect, rect)) {
1112             [scroller setFrame:rect];
1113             // Clear behind the old scroller frame, or parts of the old
1114             // scroller might still be visible after setFrame:.
1115             [[[self window] contentView] setNeedsDisplayInRect:oldRect];
1116             [scroller setNeedsDisplay:YES];
1117         }
1118     }
1121 - (void)scroll:(id)sender
1123     NSMutableData *data = [NSMutableData data];
1124     long ident = [(MMScroller*)sender identifier];
1125     int hitPart = [sender hitPart];
1126     float value = [sender floatValue];
1128     [data appendBytes:&ident length:sizeof(long)];
1129     [data appendBytes:&hitPart length:sizeof(int)];
1130     [data appendBytes:&value length:sizeof(float)];
1132     [vimController sendMessage:ScrollbarEventMsgID data:data];
1135 - (void)placeViews
1137     if (!setupDone) return;
1139     // NOTE!  It is assumed that the window has been resized so that it will
1140     // exactly fit the text storage (possibly after resizing it).  If this is
1141     // not the case the display might be messed up.
1142     NSWindow *win = [self window];
1143     NSRect contentRect = [win contentRectForFrameRect:[win frame]];
1144     NSRect textViewRect = [self textViewRectForContentSize:contentRect.size];
1145     NSSize tsSize = [self textStorageSizeForTextViewSize:textViewRect.size];
1147     int dim[2], rows, cols;
1148     [textStorage getMaxRows:&rows columns:&cols];
1149     [textStorage fitToSize:tsSize rows:&dim[0] columns:&dim[1]];
1151     if (dim[0] != rows || dim[1] != cols) {
1152         //NSLog(@"Notify Vim that text storage dimensions changed to %dx%d",
1153         //        dim[0], dim[1]);
1154         NSData *data = [NSData dataWithBytes:dim length:2*sizeof(int)];
1156         [vimController sendMessage:SetTextDimensionsMsgID data:data];
1157     }
1159     [textView setFrame:textViewRect];
1161     [self placeScrollbars];
1164 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb
1166     BOOL reply = NO;
1167     id backendProxy = [vimController backendProxy];
1169     if (backendProxy) {
1170         @try {
1171             reply = [backendProxy starRegisterToPasteboard:pb];
1172         }
1173         @catch (NSException *e) {
1174             NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1175         }
1176     }
1178     return reply;
1181 @end // MMWindowController (Private)
1185 @implementation NSTabView (MMExtras)
1187 - (void)removeAllTabViewItems
1189     NSArray *existingItems = [self tabViewItems];
1190     NSEnumerator *e = [existingItems objectEnumerator];
1191     NSTabViewItem *item;
1192     while (item = [e nextObject]){
1193         [self removeTabViewItem:item];
1194     }
1197 @end // NSTabView (MMExtras)
1202 @implementation MMScroller
1204 - (id)initWithIdentifier:(long)ident type:(int)theType
1206     // HACK! NSScroller creates a horizontal scroller if it is init'ed with a
1207     // frame whose with exceeds its height; so create a bogus rect and pass it
1208     // to initWithFrame.
1209     NSRect frame = theType == MMScrollerTypeBottom
1210             ? NSMakeRect(0, 0, 1, 0)
1211             : NSMakeRect(0, 0, 0, 1);
1213     if ((self = [super initWithFrame:frame])) {
1214         identifier = ident;
1215         type = theType;
1216         [self setHidden:YES];
1217         [self setEnabled:YES];
1218     }
1220     return self;
1223 - (long)identifier
1225     return identifier;
1228 - (int)type
1230     return type;
1233 - (NSRange)range
1235     return range;
1238 - (void)setRange:(NSRange)newRange
1240     range = newRange;
1243 - (void)scrollWheel:(NSEvent *)event
1245     // HACK! Pass message on to the text view.
1246     MMWindowController *wc = [[self window] windowController];
1247     [[wc textView] scrollWheel:event];
1250 @end // MMScroller