- Added fontSizeUp/fontSizeDown actions
[MacVim/jjgod.git] / MMWindowController.m
blobedac1ac21d4ee6020b33aa91261755e38eb1e2e1
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
93 // Note: This hack allows us to set content shadowing separately from
94 // the window shadow.  This is apparently what webkit and terminal do.
95 @interface NSWindow (NSWindowPrivate) // new Tiger private method
96 - (void) _setContentHasShadow:(BOOL)shadow;
97 @end
100 @implementation MMWindowController
102 - (id)initWithVimController:(MMVimController *)controller
104     if ((self = [super initWithWindowNibName:@"EmptyWindow"])) {
105         vimController = controller;
106         scrollbars = [[NSMutableArray alloc] init];
108         // Window cascading is handled by MMAppController.
109         [self setShouldCascadeWindows:NO];
111         // Setup a complete text system.
112         textStorage = [[MMTextStorage alloc] init];
113         NSLayoutManager *lm = [[NSLayoutManager alloc] init];
114         NSTextContainer *tc = [[NSTextContainer alloc] initWithContainerSize:
115                 NSMakeSize(1.0e7,1.0e7)];
117         NSString *typesetterString = [[NSUserDefaults standardUserDefaults]
118                 stringForKey:MMTypesetterKey];
119         if (![typesetterString isEqual:@"NSTypesetter"]) {
120             MMTypesetter *typesetter = [[MMTypesetter alloc] init];
121             [lm setTypesetter:typesetter];
122             [typesetter release];
123         } else {
124             // Only MMTypesetter supports different cell width multipliers.
125             [[NSUserDefaults standardUserDefaults]
126                     setFloat:1.0 forKey:MMCellWidthMultiplierKey];
127         }
129         [tc setWidthTracksTextView:NO];
130         [tc setHeightTracksTextView:NO];
131         [tc setLineFragmentPadding:0];
133         [textStorage addLayoutManager:lm];
134         [lm addTextContainer:tc];
136         NSWindow *win = [self window];
137         NSView *contentView = [win contentView];
138         textView = [[MMTextView alloc] initWithFrame:[contentView frame]
139                                        textContainer:tc];
141         NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
142         int left = [ud integerForKey:MMTextInsetLeftKey];
143         int top = [ud integerForKey:MMTextInsetTopKey];
144         [textView setTextContainerInset:NSMakeSize(left, top)];
146         [contentView addSubview:textView];
148         // The text storage retains the layout manager which in turn retains
149         // the text container.
150         [tc release];
151         [lm release];
153         // Create the tabline separator (which may be visible when the tabline
154         // is hidden).
155         NSRect tabSepRect = [contentView frame];
156         tabSepRect.origin.y = NSMaxY(tabSepRect)-1;
157         tabSepRect.size.height = 1;
158         tablineSeparator = [[NSBox alloc] initWithFrame:tabSepRect];
160         // Create the tab view (which is never visible, but the tab bar control
161         // needs it to function).
162         tabView = [[NSTabView alloc] initWithFrame:NSZeroRect];
164         // Create the tab bar control (which is responsible for actually
165         // drawing the tabline and tabs).
166         NSRect tabFrame = [contentView frame];
167         tabFrame.origin.y = NSMaxY(tabFrame) - 22;
168         tabFrame.size.height = 22;
169         tabBarControl = [[PSMTabBarControl alloc] initWithFrame:tabFrame];
171         [tabView setDelegate:tabBarControl];
173         [tabBarControl setTabView:tabView];
174         [tabBarControl setDelegate:self];
175         [tabBarControl setHidden:YES];
176         [tabBarControl setAutoresizingMask:NSViewWidthSizable|NSViewMinYMargin];
177         [tabBarControl setCellMinWidth:[ud integerForKey:MMTabMinWidthKey]];
178         [tabBarControl setCellMaxWidth:[ud integerForKey:MMTabMaxWidthKey]];
179         [tabBarControl setCellOptimumWidth:
180                          [ud integerForKey:MMTabOptimumWidthKey]];
181         [tabBarControl setShowAddTabButton:YES];
182         [[tabBarControl addTabButton] setTarget:self];
183         [[tabBarControl addTabButton] setAction:@selector(addNewTab:)];
184         [tabBarControl setAllowsDragBetweenWindows:NO];
186         [tablineSeparator setBoxType:NSBoxSeparator];
187         [tablineSeparator setHidden:NO];
188         [tablineSeparator setAutoresizingMask:NSViewWidthSizable
189             | NSViewMinYMargin];
191         [contentView setAutoresizesSubviews:YES];
192         [contentView addSubview:tabBarControl];
193         [contentView addSubview:tablineSeparator];
195         [win setDelegate:self];
196         [win setInitialFirstResponder:textView];
197         
198         // Make us safe on pre-tiger OSX
199         if ([win respondsToSelector:@selector(_setContentHasShadow:)])
200             [win _setContentHasShadow:NO];
201     }
203     return self;
206 - (void)dealloc
208     //NSLog(@"%@ %s", [self className], _cmd);
210     [tabBarControl release];  tabBarControl = nil;
211     [tabView release];  tabView = nil;
212     [tablineSeparator release];  tablineSeparator = nil;
213     [windowAutosaveKey release];  windowAutosaveKey = nil;
214     [scrollbars release];  scrollbars = nil;
215     [textView release];  textView = nil;
216     [textStorage release];  textStorage = nil;
218     [super dealloc];
221 - (NSString *)description
223     return [NSString stringWithFormat:@"%@ : setupDone=%d windowAutosaveKey=%@ vimController=%@", [self className], setupDone, windowAutosaveKey, vimController];
226 - (MMVimController *)vimController
228     return vimController;
231 - (MMTextView *)textView
233     return textView;
236 - (MMTextStorage *)textStorage
238     return textStorage;
241 - (NSString *)windowAutosaveKey
243     return windowAutosaveKey;
246 - (void)setWindowAutosaveKey:(NSString *)key
248     [windowAutosaveKey autorelease];
249     windowAutosaveKey = [key copy];
252 - (void)cleanup
254     //NSLog(@"%@ %s", [self className], _cmd);
256     setupDone = NO;
257     vimController = nil;
259     // NOTE! There is a bug in PSMTabBarControl in that it retains the delegate
260     // (which is the MMWindowController) so reset the delegate here, otherwise
261     // the MMWindowController never gets released resulting in a pretty serious
262     // memory leak.
263     [tabView setDelegate:nil];
264     [tabBarControl setDelegate:nil];
265     [tabBarControl setTabView:nil];
266     [[self window] setDelegate:nil];
268     // NOTE! There is another bug in PSMTabBarControl where the control is not
269     // removed as an observer, so remove it here (else lots of evil nasty bugs
270     // will come and gnaw at your feet while you are sleeping).
271     [[NSNotificationCenter defaultCenter] removeObserver:tabBarControl];
273     [tabBarControl removeFromSuperviewWithoutNeedingDisplay];
274     [tablineSeparator removeFromSuperviewWithoutNeedingDisplay];
275     [textView removeFromSuperviewWithoutNeedingDisplay];
277     unsigned i, count = [scrollbars count];
278     for (i = 0; i < count; ++i) {
279         MMScroller *sb = [scrollbars objectAtIndex:i];
280         [sb removeFromSuperviewWithoutNeedingDisplay];
281     }
283     [tabView removeAllTabViewItems];
285     [[self window] orderOut:self];
288 - (void)openWindow
290     [[NSApp delegate] windowControllerWillOpen:self];
292     [self addNewTabViewItem];
294     setupDone = YES;
296     [self updateResizeIncrements];
297     [self resizeWindowToFit:self];
298     [[self window] makeKeyAndOrderFront:self];
301 - (void)updateTabsWithData:(NSData *)data
303     const void *p = [data bytes];
304     const void *end = p + [data length];
305     int tabIdx = 0;
307     // HACK!  Current tab is first in the message.  This way it is not
308     // necessary to guess which tab should be the selected one (this can be
309     // problematic for instance when new tabs are created).
310     int curtabIdx = *((int*)p);  p += sizeof(int);
312     NSArray *tabViewItems = [tabBarControl representedTabViewItems];
314     while (p < end) {
315         //int wincount = *((int*)p);  p += sizeof(int);
316         int length = *((int*)p);  p += sizeof(int);
318         NSString *label = [[NSString alloc]
319                 initWithBytesNoCopy:(void*)p
320                              length:length
321                            encoding:NSUTF8StringEncoding
322                        freeWhenDone:NO];
323         p += length;
325         // Set the label of the tab;  add a new tab when needed.
326         NSTabViewItem *tvi = [tabView numberOfTabViewItems] <= tabIdx
327                 ? [self addNewTabViewItem]
328                 : [tabViewItems objectAtIndex:tabIdx];
330         [tvi setLabel:label];
332         [label release];
334         ++tabIdx;
335     }
337     // Remove unused tabs from the NSTabView.  Note that when a tab is closed
338     // the NSTabView will automatically select another tab, but we want Vim to
339     // take care of which tab to select so set the vimTaskSelectedTab flag to
340     // prevent the tab selection message to be passed on to the VimTask.
341     vimTaskSelectedTab = YES;
342     int i, count = [tabView numberOfTabViewItems];
343     for (i = count-1; i >= tabIdx; --i) {
344         id tvi = [tabViewItems objectAtIndex:i];
345         //NSLog(@"Removing tab with index %d", i);
346         [tabView removeTabViewItem:tvi];
347     }
348     vimTaskSelectedTab = NO;
350     [self selectTabWithIndex:curtabIdx];
353 - (void)selectTabWithIndex:(int)idx
355     //NSLog(@"%s%d", _cmd, idx);
357     NSArray *tabViewItems = [tabBarControl representedTabViewItems];
358     if (idx < 0 || idx >= [tabViewItems count]) {
359         NSLog(@"WARNING: No tab with index %d exists.", idx);
360         return;
361     }
363     // Do not try to select a tab if already selected.
364     NSTabViewItem *tvi = [tabViewItems objectAtIndex:idx];
365     if (tvi != [tabView selectedTabViewItem]) {
366         vimTaskSelectedTab = YES;
367         [tabView selectTabViewItem:tvi];
368         vimTaskSelectedTab = NO;
369     }
372 - (void)setTextDimensionsWithRows:(int)rows columns:(int)cols
374     //NSLog(@"setTextDimensionsWithRows:%d columns:%d", rows, cols);
376     [textStorage setMaxRows:rows columns:cols];
378     if (setupDone && ![textView inLiveResize])
379         shouldUpdateWindowSize = YES;
382 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
384     //NSLog(@"Create scroller %d of type %d", ident, type);
386     MMScroller *scroller = [[MMScroller alloc] initWithIdentifier:ident
387                                                              type:type];
388     [scroller setTarget:self];
389     [scroller setAction:@selector(scroll:)];
391     [[[self window] contentView] addSubview:scroller];
392     [scrollbars addObject:scroller];
393     [scroller release];
396 - (void)destroyScrollbarWithIdentifier:(long)ident
398     //NSLog(@"Destroy scroller %d", ident);
400     unsigned idx = 0;
401     MMScroller *scroller = [self scrollbarForIdentifier:ident index:&idx];
402     if (scroller) {
403         [scroller removeFromSuperview];
404         [scrollbars removeObjectAtIndex:idx];
406         if (![scroller isHidden]) {
407             // A visible scroller was removed, so the window must resize to
408             // fit.
409             //NSLog(@"Visible scroller %d was destroyed, resizing window.",
410             //        ident);
411             shouldUpdateWindowSize = YES;
412         }
413     }
416 - (void)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
418     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
419     if (!scroller) return;
421     BOOL wasVisible = ![scroller isHidden];
422     //NSLog(@"%s scroller %d (was %svisible)", visible ? "Show" : "Hide",
423     //      ident, wasVisible ? "" : "in");
424     [scroller setHidden:!visible];
426     if (wasVisible != visible) {
427         // A scroller was hidden or shown, so the window must resize to fit.
428         //NSLog(@"%s scroller %d and resize.", visible ? "Show" : "Hide",
429         //        ident);
430         shouldUpdateWindowSize = YES;
431     }
434 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
436     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
437     NSRange range = NSMakeRange(pos, len);
438     if (!NSEqualRanges(range, [scroller range])) {
439         //NSLog(@"Set range %@ for scroller %d",
440         //        NSStringFromRange(range), ident);
441         [scroller setRange:range];
442         // TODO!  Should only do this once per update.
443         [self placeScrollbars];
444     }
447 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
448                     identifier:(long)ident
450     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
451     //NSLog(@"Set thumb value %.2f proportion %.2f for scroller %d",
452     //        val, prop, ident);
453     [scroller setFloatValue:val knobProportion:prop];
456 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
458     // NOTE: This is called when the transparency changes so set the opacity
459     // flag on the window here (should be faster if the window is opaque).
460     BOOL isOpaque = [back alphaComponent] == 1.0f;
461     [[self window] setOpaque:isOpaque];
463     [textStorage setDefaultColorsBackground:back foreground:fore];
464     [textView setBackgroundColor:back];
467 - (void)setFont:(NSFont *)font
469     [[NSFontManager sharedFontManager] setSelectedFont:font isMultiple:NO];
470     [textStorage setFont:font];
471     [self updateResizeIncrements];
474 - (void)processCommandQueueDidFinish
476     if (shouldUpdateWindowSize) {
477         shouldUpdateWindowSize = NO;
478         [self resizeWindowToFit:self];
479     }
482 - (void)popupMenu:(NSMenu *)menu atRow:(int)row column:(int)col
484     if (!setupDone) return;
486     NSEvent *event;
487     if (row >= 0 && col >= 0) {
488         NSSize cellSize = [textStorage cellSize];
489         NSPoint pt = { (col+1)*cellSize.width, [textView frame].size.height
490             - (row+1)*cellSize.height };
492         event = [NSEvent mouseEventWithType:NSRightMouseDown
493                                    location:pt
494                               modifierFlags:0
495                                   timestamp:0
496                                windowNumber:[[self window] windowNumber]
497                                     context:nil
498                                 eventNumber:0
499                                  clickCount:0
500                                    pressure:1.0];
501     } else {
502         event = [textView lastMouseDownEvent];
503     }
505     [NSMenu popUpContextMenu:menu withEvent:event forView:textView];
508 - (void)showTabBar:(BOOL)on
510     [tabBarControl setHidden:!on];
512     if (!on) {
513         NSToolbar *toolbar = [[self window] toolbar]; 
514         [tablineSeparator setHidden:![toolbar isVisible]];
515     } else {
516         [tablineSeparator setHidden:on];
517     }
519     if (setupDone)
520         shouldUpdateWindowSize = YES;
523 - (void)showToolbar:(BOOL)on size:(int)size mode:(int)mode
525     NSToolbar *toolbar = [[self window] toolbar];
526     if (!toolbar) return;
528     [toolbar setSizeMode:size];
529     [toolbar setDisplayMode:mode];
530     [toolbar setVisible:on];
532     if (!on) {
533         [tablineSeparator setHidden:YES];
534     } else {
535         [tablineSeparator setHidden:![tabBarControl isHidden]];
536     }
539 - (void)setMouseShape:(int)shape
541     // This switch should match mshape_names[] in misc2.c.
542     //
543     // TODO: Add missing cursor shapes.
544     switch (shape) {
545         case 2: [[NSCursor IBeamCursor] set]; break;
546         case 3: case 4: [[NSCursor resizeUpDownCursor] set]; break;
547         case 5: case 6: [[NSCursor resizeLeftRightCursor] set]; break;
548         case 9: [[NSCursor crosshairCursor] set]; break;
549         case 10: [[NSCursor pointingHandCursor] set]; break;
550         case 11: [[NSCursor openHandCursor] set]; break;
551         default:
552             [[NSCursor arrowCursor] set]; break;
553     }
555     // Shape 1 indicates that the mouse cursor should be hidden.
556     if (1 == shape)
557         [NSCursor setHiddenUntilMouseMoves:YES];
560 - (void)adjustLinespace:(int)linespace
562     if (textStorage) {
563         [textStorage setLinespace:(float)linespace];
564         shouldUpdateWindowSize = YES;
565     }
568 - (void)liveResizeDidEnd
570     // TODO: Don't duplicate code from placeViews.
572     if (!setupDone) return;
574     // NOTE!  It is assumed that the window has been resized so that it will
575     // exactly fit the text storage (possibly after resizing it).  If this is
576     // not the case the display might be messed up.
577     BOOL resizeFailed = NO;
578     NSWindow *win = [self window];
579     NSRect contentRect = [win contentRectForFrameRect:[win frame]];
580     NSRect textViewRect = [self textViewRectForContentSize:contentRect.size];
581     NSSize tsSize = [self textStorageSizeForTextViewSize:textViewRect.size];
583     int dim[2], rows, cols;
584     [textStorage getMaxRows:&rows columns:&cols];
585     [textStorage fitToSize:tsSize rows:&dim[0] columns:&dim[1]];
587     if (dim[0] != rows || dim[1] != cols) {
588         NSData *data = [NSData dataWithBytes:dim length:2*sizeof(int)];
590         // NOTE:  Since we're at the end of a live resize we want to make sure
591         // that the SetTextDimensionsMsgID message reaches Vim, else Vim and
592         // MacVim will have inconsistent states (i.e. the text view will be too
593         // large or too small for the window size).  Thus, add a timeout (this
594         // may have to be tweaked) and take note if the message was sent or
595         // not.
596         resizeFailed = ![vimController sendMessageNow:SetTextDimensionsMsgID
597                                                  data:data
598                                               timeout:.5];
599     }
601     [textView setFrame:textViewRect];
603     [self placeScrollbars];
605     if (resizeFailed) {
606         // Force the window size to match the text view size otherwise Vim and
607         // MacVim will have inconsistent states.
608         [self resizeWindowToFit:self];
609     }
612 - (IBAction)addNewTab:(id)sender
614     // NOTE! This can get called a lot if the user holds down the key
615     // equivalent for this action, which causes the ports to fill up.  If we
616     // wait for the message to be sent then the app might become unresponsive.
617     [vimController sendMessage:AddNewTabMsgID data:nil];
620 - (IBAction)toggleToolbar:(id)sender
622     [vimController sendMessage:ToggleToolbarMsgID data:nil];
627 // -- PSMTabBarControl delegate ----------------------------------------------
630 - (BOOL)tabView:(NSTabView *)theTabView shouldSelectTabViewItem:
631     (NSTabViewItem *)tabViewItem
633     // NOTE: It would be reasonable to think that 'shouldSelect...' implies
634     // that this message only gets sent when the user clicks the tab.
635     // Unfortunately it is not so, which is why we need the
636     // 'vimTaskSelectedTab' flag.
637     //
638     // HACK!  The selection message should not be propagated to the VimTask if
639     // the VimTask selected the tab (e.g. as opposed the user clicking the
640     // tab).  The delegate method has no way of knowing who initiated the
641     // selection so a flag is set when the VimTask initiated the selection.
642     if (!vimTaskSelectedTab) {
643         // Propagate the selection message to the VimTask.
644         int idx = [self representedIndexOfTabViewItem:tabViewItem];
645         if (NSNotFound != idx) {
646             NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
647             [vimController sendMessage:SelectTabMsgID data:data];
648         }
649     }
651     // Unless Vim selected the tab, return NO, and let Vim decide if the tab
652     // should get selected or not.
653     return vimTaskSelectedTab;
656 - (BOOL)tabView:(NSTabView *)theTabView shouldCloseTabViewItem:
657         (NSTabViewItem *)tabViewItem
659     // HACK!  This method is only called when the user clicks the close button
660     // on the tab.  Instead of letting the tab bar close the tab, we return NO
661     // and pass a message on to Vim to let it handle the closing.
662     int idx = [self representedIndexOfTabViewItem:tabViewItem];
663     //NSLog(@"Closing tab with index %d", idx);
664     NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
665     [vimController sendMessage:CloseTabMsgID data:data];
667     return NO;
670 - (void)tabView:(NSTabView *)theTabView didDragTabViewItem:
671         (NSTabViewItem *)tabViewItem toIndex:(int)idx
673     NSMutableData *data = [NSMutableData data];
674     [data appendBytes:&idx length:sizeof(int)];
676     [vimController sendMessage:DraggedTabMsgID data:data];
682 // -- NSWindow delegate ------------------------------------------------------
684 - (void)windowDidBecomeMain:(NSNotification *)notification
686     [vimController sendMessage:GotFocusMsgID data:nil];
688     if (textStorage)
689         [[NSFontManager sharedFontManager] setSelectedFont:[textStorage font]
690                                                 isMultiple:NO];
693 - (void)windowDidResignMain:(NSNotification *)notification
695     [vimController sendMessage:LostFocusMsgID data:nil];
697     if (textView)
698         [textView hideMarkedTextField];
701 - (BOOL)windowShouldClose:(id)sender
703     [vimController sendMessage:VimShouldCloseMsgID data:nil];
704     return NO;
707 - (void)windowDidMove:(NSNotification *)notification
709     if (setupDone && windowAutosaveKey) {
710         NSRect frame = [[self window] frame];
711         NSPoint topLeft = { frame.origin.x, NSMaxY(frame) };
712         NSString *topLeftString = NSStringFromPoint(topLeft);
714         [[NSUserDefaults standardUserDefaults]
715             setObject:topLeftString forKey:windowAutosaveKey];
716     }
719 - (void)windowDidResize:(id)sender
721     if (!setupDone) return;
722     [self placeViews];
725 - (NSRect)windowWillUseStandardFrame:(NSWindow *)win
726                         defaultFrame:(NSRect)frame
728     // HACK!  For some reason 'frame' is not always constrained to fit on the
729     // screen (e.g. it may overlap the menu bar), so first constrain it to the
730     // screen; otherwise the new frame we compute may be too large and this
731     // will mess up the display after the window resizes.
732     frame = [win constrainFrameRect:frame toScreen:[win screen]];
734     // HACK!  If the top of 'frame' is lower than the current window frame,
735     // increase 'frame' so that their tops align.  Really, 'frame' should
736     // already have its top at least as high as the current window frame, but
737     // for some reason this is not always the case.
738     // (See resizeWindowToFit: for a similar hack.)
739     NSRect cur = [win frame];
740     if (NSMaxY(cur) > NSMaxY(frame)) {
741         frame.size.height = cur.origin.y - frame.origin.y + cur.size.height;
742     }
744     frame = [self fitWindowToFrame:frame];
746     // Keep old width and horizontal position unless user clicked with the
747     // Command key is held down.
748     NSEvent *event = [NSApp currentEvent];
749     if (!([event type] == NSLeftMouseUp
750             && [event modifierFlags] & NSCommandKeyMask)) {
751         NSRect currentFrame = [win frame];
752         frame.size.width = currentFrame.size.width;
753         frame.origin.x = currentFrame.origin.x;
754     }
756     return frame;
762 // -- Services menu delegate -------------------------------------------------
764 - (id)validRequestorForSendType:(NSString *)sendType
765                      returnType:(NSString *)returnType
767     if ([sendType isEqual:NSStringPboardType]
768             && [self askBackendForStarRegister:nil])
769         return self;
771     return [super validRequestorForSendType:sendType returnType:returnType];
774 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
775                              types:(NSArray *)types
777     if (![types containsObject:NSStringPboardType])
778         return NO;
780     return [self askBackendForStarRegister:pboard];
783 @end // MMWindowController
787 @implementation MMWindowController (Private)
789 - (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize
791     NSSize size = textViewSize;
793     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
794     int right = [ud integerForKey:MMTextInsetRightKey];
795     int bot = [ud integerForKey:MMTextInsetBottomKey];
797     size.width += [textView textContainerOrigin].x + right;
798     size.height += [textView textContainerOrigin].y + bot;
800     if (![tablineSeparator isHidden])
801         ++size.height;
802     if (![tabBarControl isHidden])
803         size.height += [tabBarControl frame].size.height;
805     if ([self bottomScrollbarVisible])
806         size.height += [NSScroller scrollerWidth];
807     if ([self leftScrollbarVisible])
808         size.width += [NSScroller scrollerWidth];
809     if ([self rightScrollbarVisible])
810         size.width += [NSScroller scrollerWidth];
812     return size;
815 - (NSRect)textViewRectForContentSize:(NSSize)contentSize
817     NSRect rect = { 0, 0, contentSize.width, contentSize.height };
819     if (![tablineSeparator isHidden])
820         --rect.size.height;
821     if (![tabBarControl isHidden])
822         rect.size.height -= [tabBarControl frame].size.height;
824     if ([self bottomScrollbarVisible]) {
825         rect.size.height -= [NSScroller scrollerWidth];
826         rect.origin.y += [NSScroller scrollerWidth];
827     }
828     if ([self leftScrollbarVisible]) {
829         rect.size.width -= [NSScroller scrollerWidth];
830         rect.origin.x += [NSScroller scrollerWidth];
831     }
832     if ([self rightScrollbarVisible])
833         rect.size.width -= [NSScroller scrollerWidth];
835     return rect;
838 - (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize
840     NSSize size = textViewSize;
842     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
843     int right = [ud integerForKey:MMTextInsetRightKey];
844     int bot = [ud integerForKey:MMTextInsetBottomKey];
846     size.width -= [textView textContainerOrigin].x + right;
847     size.height -= [textView textContainerOrigin].y + bot;
849     return size;
852 - (void)resizeWindowToFit:(id)sender
854     // NOTE: Be very careful when you call this method!  Do not call while
855     // processing command queue, instead set 'shouldUpdateWindowSize' to YES.
856     // The only other place it is currently called is when live resize ends.
857     // This is done to ensure that the text view and window sizes match up
858     // (they may become out of sync if a SetTextDimensionsMsgID message to the
859     // backend is dropped).
861     if (!setupDone) return;
863     NSWindow *win = [self window];
864     NSRect frame = [win frame];
865     NSRect contentRect = [win contentRectForFrameRect:frame];
866     NSSize newSize = [self contentSizeForTextStorageSize:[textStorage size]];
868     // Keep top-left corner of the window fixed when resizing.
869     contentRect.origin.y -= newSize.height - contentRect.size.height;
870     contentRect.size = newSize;
872     frame = [win frameRectForContentRect:contentRect];
873     NSRect maxFrame = [win constrainFrameRect:frame toScreen:[win screen]];
875     // HACK!  Assuming the window frame cannot already be placed too high,
876     // adjust 'maxFrame' so that it at least as high up as the current frame.
877     // The reason for doing this is that constrainFrameRect:toScreen: does not
878     // always seem to utilize as much area as possible.
879     if (NSMaxY(frame) > NSMaxY(maxFrame)) {
880         maxFrame.size.height = frame.origin.y - maxFrame.origin.y
881                 + frame.size.height;
882     }
884     if (!NSEqualRects(maxFrame, frame)) {
885         // The new window frame is too big to fit on the screen, so fit the
886         // text storage to the biggest frame which will fit on the screen.
887         //NSLog(@"Proposed window frame does not fit on the screen!");
888         frame = [self fitWindowToFrame:maxFrame];
889     }
891     //NSLog(@"%s %@", _cmd, NSStringFromRect(frame));
893     // HACK! If the window does resize, then windowDidResize is called which in
894     // turn calls placeViews.  In case the computed new size of the window is
895     // no different from the current size, then we need to call placeViews
896     // manually.
897     if (NSEqualRects(frame, [win frame])) {
898         [self placeViews];
899     } else {
900         [win setFrame:frame display:YES];
901     }
904 - (NSRect)fitWindowToFrame:(NSRect)frame
906     if (!setupDone) return frame;
908     NSWindow *win = [self window];
909     NSRect contentRect = [win contentRectForFrameRect:frame];
910     NSSize size = [self textViewRectForContentSize:contentRect.size].size;
911     size = [self textStorageSizeForTextViewSize:size];
912     size = [textStorage fitToSize:size];
913     size = [self contentSizeForTextStorageSize:size];
915     // Keep top-left corner of 'frame' fixed.
916     contentRect.origin.y -= size.height - contentRect.size.height;
917     contentRect.size = size;
919     return [win frameRectForContentRect:contentRect];
922 - (void)updateResizeIncrements
924     if (!setupDone) return;
926     NSSize size = [textStorage cellSize];
927     [[self window] setContentResizeIncrements:size];
930 - (NSTabViewItem *)addNewTabViewItem
932     // NOTE!  A newly created tab is not by selected by default; the VimTask
933     // decides which tab should be selected at all times.  However, the AppKit
934     // will automatically select the first tab added to a tab view.
936     NSTabViewItem *tvi = [[NSTabViewItem alloc] initWithIdentifier:nil];
938     // NOTE: If this is the first tab it will be automatically selected.
939     vimTaskSelectedTab = YES;
940     [tabView addTabViewItem:tvi];
941     vimTaskSelectedTab = NO;
943     [tvi release];
945     return tvi;
948 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
950     NSArray *tabViewItems = [tabBarControl representedTabViewItems];
951     return [tabViewItems indexOfObject:tvi];
954 - (IBAction)vimMenuItemAction:(id)sender
956     int tag = [sender tag];
958     NSMutableData *data = [NSMutableData data];
959     [data appendBytes:&tag length:sizeof(int)];
961     [vimController sendMessage:ExecuteMenuMsgID data:data];
964 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx
966     unsigned i, count = [scrollbars count];
967     for (i = 0; i < count; ++i) {
968         MMScroller *scroller = [scrollbars objectAtIndex:i];
969         if ([scroller identifier] == ident) {
970             if (idx) *idx = i;
971             return scroller;
972         }
973     }
975     return nil;
978 - (BOOL)bottomScrollbarVisible
980     unsigned i, count = [scrollbars count];
981     for (i = 0; i < count; ++i) {
982         MMScroller *scroller = [scrollbars objectAtIndex:i];
983         if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden])
984             return YES;
985     }
987     return NO;
990 - (BOOL)leftScrollbarVisible
992     unsigned i, count = [scrollbars count];
993     for (i = 0; i < count; ++i) {
994         MMScroller *scroller = [scrollbars objectAtIndex:i];
995         if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden])
996             return YES;
997     }
999     return NO;
1002 - (BOOL)rightScrollbarVisible
1004     unsigned i, count = [scrollbars count];
1005     for (i = 0; i < count; ++i) {
1006         MMScroller *scroller = [scrollbars objectAtIndex:i];
1007         if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden])
1008             return YES;
1009     }
1011     return NO;
1014 - (void)placeScrollbars
1016     if (!setupDone) return;
1018     NSRect textViewFrame = [textView frame];
1019     BOOL lsbVisible = [self leftScrollbarVisible];
1021     // HACK!  Find the lowest left&right vertical scrollbars, as well as the
1022     // rightmost horizontal scrollbar.  This hack continues further down.
1023     //
1024     // TODO!  Can there be no more than one horizontal scrollbar?  If so, the
1025     // code can be simplified.
1026     unsigned lowestLeftSbIdx = (unsigned)-1;
1027     unsigned lowestRightSbIdx = (unsigned)-1;
1028     unsigned rightmostSbIdx = (unsigned)-1;
1029     unsigned rowMaxLeft = 0, rowMaxRight = 0, colMax = 0;
1030     unsigned i, count = [scrollbars count];
1031     for (i = 0; i < count; ++i) {
1032         MMScroller *scroller = [scrollbars objectAtIndex:i];
1033         if (![scroller isHidden]) {
1034             NSRange range = [scroller range];
1035             if ([scroller type] == MMScrollerTypeLeft
1036                     && range.location >= rowMaxLeft) {
1037                 rowMaxLeft = range.location;
1038                 lowestLeftSbIdx = i;
1039             } else if ([scroller type] == MMScrollerTypeRight
1040                     && range.location >= rowMaxRight) {
1041                 rowMaxRight = range.location;
1042                 lowestRightSbIdx = i;
1043             } else if ([scroller type] == MMScrollerTypeBottom
1044                     && range.location >= colMax) {
1045                 colMax = range.location;
1046                 rightmostSbIdx = i;
1047             }
1048         }
1049     }
1051     // Place the scrollbars.
1052     for (i = 0; i < count; ++i) {
1053         MMScroller *scroller = [scrollbars objectAtIndex:i];
1054         if ([scroller isHidden])
1055             continue;
1057         NSRect rect;
1058         if ([scroller type] == MMScrollerTypeBottom) {
1059             rect = [textStorage rectForColumnsInRange:[scroller range]];
1060             rect.size.height = [NSScroller scrollerWidth];
1061             if (lsbVisible)
1062                 rect.origin.x += [NSScroller scrollerWidth];
1064             // HACK!  Make sure the rightmost horizontal scrollbar covers the
1065             // text view all the way to the right, otherwise it looks ugly when
1066             // the user drags the window to resize.
1067             if (i == rightmostSbIdx) {
1068                 float w = NSMaxX(textViewFrame) - NSMaxX(rect);
1069                 if (w > 0)
1070                     rect.size.width += w;
1071             }
1073             // Make sure scrollbar rect is bounded by the text view frame.
1074             if (rect.origin.x < textViewFrame.origin.x)
1075                 rect.origin.x = textViewFrame.origin.x;
1076             else if (rect.origin.x > NSMaxX(textViewFrame))
1077                 rect.origin.x = NSMaxX(textViewFrame);
1078             if (NSMaxX(rect) > NSMaxX(textViewFrame))
1079                 rect.size.width -= NSMaxX(rect) - NSMaxX(textViewFrame);
1080             if (rect.size.width < 0)
1081                 rect.size.width = 0;
1082         } else {
1083             rect = [textStorage rectForRowsInRange:[scroller range]];
1084             // Adjust for the fact that text layout is flipped.
1085             rect.origin.y = NSMaxY(textViewFrame) - rect.origin.y
1086                     - rect.size.height;
1087             rect.size.width = [NSScroller scrollerWidth];
1088             if ([scroller type] == MMScrollerTypeRight)
1089                 rect.origin.x = NSMaxX(textViewFrame);
1091             // HACK!  Make sure the lowest vertical scrollbar covers the text
1092             // view all the way to the bottom.  This is done because Vim only
1093             // makes the scrollbar cover the (vim-)window it is associated with
1094             // and this means there is always an empty gap in the scrollbar
1095             // region next to the command line.
1096             // TODO!  Find a nicer way to do this.
1097             if (i == lowestLeftSbIdx || i == lowestRightSbIdx) {
1098                 float h = rect.origin.y + rect.size.height
1099                           - textViewFrame.origin.y;
1100                 if (rect.size.height < h) {
1101                     rect.origin.y = textViewFrame.origin.y;
1102                     rect.size.height = h;
1103                 }
1104             }
1106             // Vertical scrollers must not cover the resize box in the
1107             // bottom-right corner of the window.
1108             if (rect.origin.y < [NSScroller scrollerWidth]) {
1109                 rect.size.height -= [NSScroller scrollerWidth] - rect.origin.y;
1110                 rect.origin.y = [NSScroller scrollerWidth];
1111             }
1113             // Make sure scrollbar rect is bounded by the text view frame.
1114             if (rect.origin.y < textViewFrame.origin.y) {
1115                 rect.size.height -= textViewFrame.origin.y - rect.origin.y;
1116                 rect.origin.y = textViewFrame.origin.y;
1117             } else if (rect.origin.y > NSMaxY(textViewFrame))
1118                 rect.origin.y = NSMaxY(textViewFrame);
1119             if (NSMaxY(rect) > NSMaxY(textViewFrame))
1120                 rect.size.height -= NSMaxY(rect) - NSMaxY(textViewFrame);
1121             if (rect.size.height < 0)
1122                 rect.size.height = 0;
1123         }
1125         //NSLog(@"set scroller #%d frame = %@", i, NSStringFromRect(rect));
1126         NSRect oldRect = [scroller frame];
1127         if (!NSEqualRects(oldRect, rect)) {
1128             [scroller setFrame:rect];
1129             // Clear behind the old scroller frame, or parts of the old
1130             // scroller might still be visible after setFrame:.
1131             [[[self window] contentView] setNeedsDisplayInRect:oldRect];
1132             [scroller setNeedsDisplay:YES];
1133         }
1134     }
1137 - (void)scroll:(id)sender
1139     NSMutableData *data = [NSMutableData data];
1140     long ident = [(MMScroller*)sender identifier];
1141     int hitPart = [sender hitPart];
1142     float value = [sender floatValue];
1144     [data appendBytes:&ident length:sizeof(long)];
1145     [data appendBytes:&hitPart length:sizeof(int)];
1146     [data appendBytes:&value length:sizeof(float)];
1148     [vimController sendMessage:ScrollbarEventMsgID data:data];
1151 - (void)placeViews
1153     if (!setupDone) return;
1155     // NOTE!  It is assumed that the window has been resized so that it will
1156     // exactly fit the text storage (possibly after resizing it).  If this is
1157     // not the case the display might be messed up.
1158     NSWindow *win = [self window];
1159     NSRect contentRect = [win contentRectForFrameRect:[win frame]];
1160     NSRect textViewRect = [self textViewRectForContentSize:contentRect.size];
1161     NSSize tsSize = [self textStorageSizeForTextViewSize:textViewRect.size];
1163     int dim[2], rows, cols;
1164     [textStorage getMaxRows:&rows columns:&cols];
1165     [textStorage fitToSize:tsSize rows:&dim[0] columns:&dim[1]];
1167     if (dim[0] != rows || dim[1] != cols) {
1168         //NSLog(@"Notify Vim that text storage dimensions changed to %dx%d",
1169         //        dim[0], dim[1]);
1170         NSData *data = [NSData dataWithBytes:dim length:2*sizeof(int)];
1172         [vimController sendMessage:SetTextDimensionsMsgID data:data];
1173     }
1175     [textView setFrame:textViewRect];
1177     [self placeScrollbars];
1180 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb
1182     BOOL reply = NO;
1183     id backendProxy = [vimController backendProxy];
1185     if (backendProxy) {
1186         @try {
1187             reply = [backendProxy starRegisterToPasteboard:pb];
1188         }
1189         @catch (NSException *e) {
1190             NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1191         }
1192     }
1194     return reply;
1197 @end // MMWindowController (Private)
1201 @implementation NSTabView (MMExtras)
1203 - (void)removeAllTabViewItems
1205     NSArray *existingItems = [self tabViewItems];
1206     NSEnumerator *e = [existingItems objectEnumerator];
1207     NSTabViewItem *item;
1208     while (item = [e nextObject]){
1209         [self removeTabViewItem:item];
1210     }
1213 @end // NSTabView (MMExtras)
1218 @implementation MMScroller
1220 - (id)initWithIdentifier:(long)ident type:(int)theType
1222     // HACK! NSScroller creates a horizontal scroller if it is init'ed with a
1223     // frame whose with exceeds its height; so create a bogus rect and pass it
1224     // to initWithFrame.
1225     NSRect frame = theType == MMScrollerTypeBottom
1226             ? NSMakeRect(0, 0, 1, 0)
1227             : NSMakeRect(0, 0, 0, 1);
1229     if ((self = [super initWithFrame:frame])) {
1230         identifier = ident;
1231         type = theType;
1232         [self setHidden:YES];
1233         [self setEnabled:YES];
1234     }
1236     return self;
1239 - (long)identifier
1241     return identifier;
1244 - (int)type
1246     return type;
1249 - (NSRange)range
1251     return range;
1254 - (void)setRange:(NSRange)newRange
1256     range = newRange;
1259 - (void)scrollWheel:(NSEvent *)event
1261     // HACK! Pass message on to the text view.
1262     MMWindowController *wc = [[self window] windowController];
1263     [[wc textView] scrollWheel:event];
1266 @end // MMScroller