From 307767baf4982d530a58208f52a2bbc6d58cad0d Mon Sep 17 00:00:00 2001 From: Bjorn Winckler Date: Mon, 21 Jan 2008 22:10:04 +0100 Subject: [PATCH] Window and view refactoring Window and view code was refactored to better accomodate the transition to the ATSUI text rendering code. View specific code has been completely moved into MMVimView and window code is now all in MMWindowController; the window controller is also 'full-screen aware'. Finally, the text storage is no longer referenced outside MMTextView -- the idea is that the text view is the public class for handling the work that MMTextStorage used to be the public interface for. This refactoring also brings some improvements: - window resizing is more responsive - full-screen window memory leak fixed - proper fix for erroneous 'buffer modified' warning - full-screen no longer tried to set 'nil-title' (this caused backend connection problems) - changing lines/columns in full-screen works properly - open dialog now works in full-screen on Tiger --- src/MacVim/MMAppController.m | 90 ++-- src/MacVim/MMAtsuiTextView.h | 20 +- src/MacVim/MMAtsuiTextView.m | 274 +++++++---- src/MacVim/MMBackend.m | 20 +- src/MacVim/MMFullscreenWindow.h | 1 + src/MacVim/MMFullscreenWindow.m | 136 ++---- src/MacVim/MMTextStorage.m | 2 + src/MacVim/MMTextView.h | 22 + src/MacVim/MMTextView.m | 324 ++++++++++++- src/MacVim/MMVimController.m | 193 +------- src/MacVim/MMVimView.h | 31 +- src/MacVim/MMVimView.m | 602 ++++++++++++------------ src/MacVim/MMWindow.h | 32 ++ src/MacVim/MMWindow.m | 120 +++++ src/MacVim/MMWindowController.h | 20 +- src/MacVim/MMWindowController.m | 678 +++++++++++++--------------- src/MacVim/MacVim.h | 1 + src/MacVim/MacVim.m | 1 + src/MacVim/MacVim.xcodeproj/project.pbxproj | 8 + src/MacVim/gui_macvim.m | 5 +- 20 files changed, 1437 insertions(+), 1143 deletions(-) create mode 100644 src/MacVim/MMWindow.h create mode 100644 src/MacVim/MMWindow.m diff --git a/src/MacVim/MMAppController.m b/src/MacVim/MMAppController.m index 210f3adf..6f60debb 100644 --- a/src/MacVim/MMAppController.m +++ b/src/MacVim/MMAppController.m @@ -488,55 +488,67 @@ typedef struct connectBackend:(byref in id )backend pid:(int)pid { + MMVimController *vc = nil; //NSLog(@"Connect backend (pid=%d)", pid); - [(NSDistantObject*)backend - setProtocolForProxy:@protocol(MMBackendProtocol)]; + @try { + [(NSDistantObject*)backend + setProtocolForProxy:@protocol(MMBackendProtocol)]; - MMVimController *vc = [[[MMVimController alloc] - initWithBackend:backend pid:pid] autorelease]; + vc = [[[MMVimController alloc] + initWithBackend:backend pid:pid] autorelease]; - if (![vimControllers count]) { - // The first window autosaves its position. (The autosaving features - // of Cocoa are not used because we need more control over what is - // autosaved and when it is restored.) - [[vc windowController] setWindowAutosaveKey:MMTopLeftPointKey]; - } - - [vimControllers addObject:vc]; - - // HACK! MacVim does not get activated if it is launched from the - // terminal, so we forcibly activate here unless it is an untitled window - // opening (i.e. MacVim was opened from the Finder). Untitled windows are - // treated differently, else MacVim would steal the focus if another app - // was activated while the untitled window was loading. - if (!untitledWindowOpening) - [NSApp activateIgnoringOtherApps:YES]; - - untitledWindowOpening = NO; - - // Arguments to a new Vim process that cannot be passed on the command line - // are stored in a dictionary and passed to the Vim process here. - NSNumber *key = [NSNumber numberWithInt:pid]; - NSDictionary *args = [pidArguments objectForKey:key]; - if (args) { - if ([args objectForKey:@"remoteID"]) { - [vc odbEdit:[args objectForKey:@"filenames"] - server:[[args objectForKey:@"remoteID"] unsignedIntValue] - path:[args objectForKey:@"remotePath"] - token:[args objectForKey:@"remoteToken"]]; + if (![vimControllers count]) { + // The first window autosaves its position. (The autosaving + // features of Cocoa are not used because we need more control over + // what is autosaved and when it is restored.) + [[vc windowController] setWindowAutosaveKey:MMTopLeftPointKey]; } - if ([args objectForKey:@"selectionRangeData"]) { - MMSelectionRange *selRange = (MMSelectionRange*) - [[args objectForKey:@"selectionRangeData"] bytes]; - [vc addVimInput:[self inputStringFromSelectionRange:selRange]]; + [vimControllers addObject:vc]; + + // HACK! MacVim does not get activated if it is launched from the + // terminal, so we forcibly activate here unless it is an untitled + // window opening (i.e. MacVim was opened from the Finder). Untitled + // windows are treated differently, else MacVim would steal the focus + // if another app was activated while the untitled window was loading. + if (!untitledWindowOpening) + [NSApp activateIgnoringOtherApps:YES]; + + untitledWindowOpening = NO; + + // Arguments to a new Vim process that cannot be passed on the command + // line are stored in a dictionary and passed to the Vim process here. + NSNumber *key = [NSNumber numberWithInt:pid]; + NSDictionary *args = [pidArguments objectForKey:key]; + if (args) { + if ([args objectForKey:@"remoteID"]) { + [vc odbEdit:[args objectForKey:@"filenames"] + server:[[args objectForKey:@"remoteID"] unsignedIntValue] + path:[args objectForKey:@"remotePath"] + token:[args objectForKey:@"remoteToken"]]; + } + + if ([args objectForKey:@"selectionRangeData"]) { + MMSelectionRange *selRange = (MMSelectionRange*) + [[args objectForKey:@"selectionRangeData"] bytes]; + [vc addVimInput:[self inputStringFromSelectionRange:selRange]]; + } + + [pidArguments removeObjectForKey:key]; } - [pidArguments removeObjectForKey:key]; + return vc; } - return vc; + @catch (NSException *e) { + NSLog(@"Exception caught in %s: \"%@\"", _cmd, e); + + if (vc) + [vimControllers removeObject:vc]; + } + + return nil; } - (NSArray *)serverList diff --git a/src/MacVim/MMAtsuiTextView.h b/src/MacVim/MMAtsuiTextView.h index a3bf52a5..9f7e88dc 100644 --- a/src/MacVim/MMAtsuiTextView.h +++ b/src/MacVim/MMAtsuiTextView.h @@ -13,20 +13,10 @@ enum { MMMaxCellsPerChar = 2 }; -// TODO: What does DRAW_TRANSP flag do? If the background isn't drawn when -// this flag is set, then sometimes the character after the cursor becomes -// blank. Everything seems to work fine by just ignoring this flag. -#define DRAW_TRANSP 0x01 /* draw with transparant bg */ -#define DRAW_BOLD 0x02 /* draw bold text */ -#define DRAW_UNDERL 0x04 /* draw underline text */ -#define DRAW_UNDERC 0x08 /* draw undercurl text */ -#define DRAW_ITALIC 0x10 /* draw italic text */ -#define DRAW_CURSOR 0x20 @interface MMAtsuiTextView : NSView { // From MMTextStorage int maxRows, maxColumns; - int actualRows, actualColumns; NSColor *defaultBackgroundColor; NSColor *defaultForegroundColor; NSSize cellSize; @@ -48,8 +38,6 @@ enum { MMMaxCellsPerChar = 2 }; - (void)setMaxRows:(int)rows columns:(int)cols; - (void)setDefaultColorsBackground:(NSColor *)bgColor foreground:(NSColor *)fgColor; -- (NSSize)size; -- (NSSize)fitToSize:(NSSize)size rows:(int *)rows columns:(int *)columns; - (NSRect)rectForRowsInRange:(NSRange)range; - (NSRect)rectForColumnsInRange:(NSRange)range; @@ -65,8 +53,6 @@ enum { MMMaxCellsPerChar = 2 }; - (NSEvent *)lastMouseDownEvent; - (void)setShouldDrawInsertionPoint:(BOOL)on; - (void)setPreEditRow:(int)row column:(int)col; -- (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape - fraction:(int)percent color:(NSColor *)color; - (void)hideMarkedTextField; // @@ -76,13 +62,13 @@ enum { MMMaxCellsPerChar = 2 }; - (void)insertText:(id)string; - (void)doCommandBySelector:(SEL)selector; - (BOOL)performKeyEquivalent:(NSEvent *)event; -- (NSPoint)textContainerOrigin; -- (void)setTextContainerInset:(NSSize)inset; -- (void)setBackgroundColor:(NSColor *)color; // // MMAtsuiTextView methods // - (void)performBatchDrawWithData:(NSData *)data; +- (NSSize)desiredSize; +- (NSSize)minSize; +- (NSSize)constrainRows:(int *)rows columns:(int *)cols toSize:(NSSize)size; @end diff --git a/src/MacVim/MMAtsuiTextView.m b/src/MacVim/MMAtsuiTextView.m index 495d4265..9ea9fb5b 100644 --- a/src/MacVim/MMAtsuiTextView.m +++ b/src/MacVim/MMAtsuiTextView.m @@ -11,21 +11,55 @@ * MMAtsuiTextView * * Dispatches keyboard and mouse input to the backend. Handles drag-n-drop of - * files onto window. + * files onto window. The rendering is done using ATSUI. + * + * The text view area consists of two parts: + * 1. The text area - this is where text is rendered; the size is governed by + * the current number of rows and columns. + * 2. The inset area - this is a border around the text area; the size is + * governed by the user defaults MMTextInset[Left|Right|Top|Bottom]. + * + * The current size of the text view frame does not always match the desired + * area, i.e. the area determined by the number of rows, columns plus text + * inset. This distinction is particularly important when the view is being + * resized. */ #import "MMAtsuiTextView.h" #import "MMVimController.h" #import "MacVim.h" + +// TODO: What does DRAW_TRANSP flag do? If the background isn't drawn when +// this flag is set, then sometimes the character after the cursor becomes +// blank. Everything seems to work fine by just ignoring this flag. +#define DRAW_TRANSP 0x01 /* draw with transparant bg */ +#define DRAW_BOLD 0x02 /* draw bold text */ +#define DRAW_UNDERL 0x04 /* draw underline text */ +#define DRAW_UNDERC 0x08 /* draw undercurl text */ +#define DRAW_ITALIC 0x10 /* draw italic text */ +#define DRAW_CURSOR 0x20 + + static char MMKeypadEnter[2] = { 'K', 'A' }; static NSString *MMKeypadEnterString = @"KA"; +enum { + // These values are chosen so that the min size is not too small with the + // default font (they only affect resizing with the mouse, you can still + // use e.g. ":set lines=2" to go below these values). + MMMinRows = 4, + MMMinColumns = 30 +}; + + @interface NSFont (AppKitPrivate) - (ATSUFontID) _atsFontID; @end + @interface MMAtsuiTextView (Private) +- (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column; - (void)initAtsuStyles; - (void)disposeAtsuStyles; - (void)updateAtsuStyles; @@ -34,8 +68,12 @@ static NSString *MMKeypadEnterString = @"KA"; - (MMVimController *)vimController; @end + @interface MMAtsuiTextView (Drawing) -- (void)fitImageToSize; +- (NSRect)rectFromRow:(int)row1 column:(int)col1 + toRow:(int)row2 column:(int)col2; +- (NSSize)textAreaSize; +- (void)resizeContentImage; - (void)beginDrawing; - (void)endDrawing; - (void)drawString:(UniChar *)string length:(UniCharCount)length @@ -51,6 +89,8 @@ static NSString *MMKeypadEnterString = @"KA"; - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2 column:(int)col2 color:(NSColor *)color; - (void)clearAll; +- (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape + fraction:(int)percent color:(NSColor *)color; @end @@ -112,63 +152,9 @@ static NSString *MMKeypadEnterString = @"KA"; } } -- (NSSize)size -{ - return NSMakeSize(maxColumns*cellSize.width, maxRows*cellSize.height); -} - -- (NSSize)fitToSize:(NSSize)size rows:(int *)rows columns:(int *)columns -{ - NSSize curSize = [self size]; - NSSize fitSize = curSize; - int fitRows = maxRows; - int fitCols = maxColumns; - - if (size.height < curSize.height) { - // Remove lines until the height of the text storage fits inside - // 'size'. However, always make sure there are at least 3 lines in the - // text storage. (Why 3? It seem Vim never allows less than 3 lines.) - // - // TODO: No need to search since line height is fixed, just calculate - // the new height. - int rowCount = maxRows; - int rowsToRemove; - for (rowsToRemove = 0; rowsToRemove < maxRows-3; ++rowsToRemove) { - float height = cellSize.height*rowCount; - - if (height <= size.height) { - fitSize.height = height; - break; - } - - --rowCount; - } - - fitRows -= rowsToRemove; - } else if (size.height > curSize.height) { - float fh = cellSize.height; - if (fh < 1.0f) fh = 1.0f; - - fitRows = floor(size.height/fh); - fitSize.height = fh*fitRows; - } - - if (size.width != curSize.width) { - float fw = cellSize.width; - if (fw < 1.0f) fw = 1.0f; - - fitCols = floor(size.width/fw); - fitSize.width = fw*fitCols; - } - - if (rows) *rows = fitRows; - if (columns) *columns = fitCols; - - return fitSize; -} - - (NSRect)rectForRowsInRange:(NSRange)range { + // TODO: Add text inset to origin NSRect rect = { 0, 0, 0, 0 }; unsigned start = range.location > maxRows ? maxRows : range.location; unsigned length = range.length; @@ -184,6 +170,7 @@ static NSString *MMKeypadEnterString = @"KA"; - (NSRect)rectForColumnsInRange:(NSRange)range { + // TODO: Add text inset to origin NSRect rect = { 0, 0, 0, 0 }; unsigned start = range.location > maxColumns ? maxColumns : range.location; unsigned length = range.length; @@ -262,11 +249,6 @@ static NSString *MMKeypadEnterString = @"KA"; { } -- (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape - fraction:(int)percent color:(NSColor *)color -{ -} - - (void)hideMarkedTextField { } @@ -296,7 +278,7 @@ static NSString *MMKeypadEnterString = @"KA"; [self dispatchKeyEvent:event]; } } else { - [super keyDown:event]; + [self interpretKeyEvents:[NSArray arrayWithObject:event]]; } } @@ -456,19 +438,6 @@ static NSString *MMKeypadEnterString = @"KA"; return YES; } -- (NSPoint)textContainerOrigin -{ - return NSZeroPoint; -} - -- (void)setTextContainerInset:(NSSize)inset -{ -} - -- (void)setBackgroundColor:(NSColor *)color -{ -} - @@ -503,8 +472,8 @@ static NSString *MMKeypadEnterString = @"KA"; const void *bytes = [data bytes]; const void *end = bytes + [data length]; - if (! NSEqualSizes(imageSize, [self size])) - [self fitImageToSize]; + if (! NSEqualSizes(imageSize, [self textAreaSize])) + [self resizeContentImage]; #if MM_DEBUG_DRAWING NSLog(@"====> BEGIN %s", _cmd); @@ -622,6 +591,108 @@ static NSString *MMKeypadEnterString = @"KA"; #endif } +- (NSSize)constrainRows:(int *)rows columns:(int *)cols toSize:(NSSize)size +{ + // TODO: + // - Take text area inset into consideration + // - Rounding errors may cause size change when there should be none + // - Desired rows/columns shold not be 'too small' + + // Constrain the desired size to the given size. Values for the minimum + // rows and columns is taken from Vim. + NSSize desiredSize = [self desiredSize]; + int desiredRows = maxRows; + int desiredCols = maxColumns; + + if (size.height != desiredSize.height) { + float fh = cellSize.height; + if (fh < 1.0f) fh = 1.0f; + + desiredRows = floor(size.height/fh); + desiredSize.height = fh*desiredRows; + } + + if (size.width != desiredSize.width) { + float fw = cellSize.width; + if (fw < 1.0f) fw = 1.0f; + + desiredCols = floor(size.width/fw); + desiredSize.width = fw*desiredCols; + } + + if (rows) *rows = desiredRows; + if (cols) *cols = desiredCols; + + return desiredSize; +} + +- (NSSize)desiredSize +{ + // Compute the size the text view should be for the entire text area and + // inset area to be visible with the present number of rows and columns. + // + // TODO: Add inset area to size. + return NSMakeSize(maxColumns*cellSize.width, maxRows*cellSize.height); +} + +- (NSSize)minSize +{ + // Compute the smallest size the text view is allowed to be. + // + // TODO: Add inset area to size. + return NSMakeSize(MMMinColumns*cellSize.width, MMMinRows*cellSize.height); +} + +- (void)changeFont:(id)sender +{ + NSFont *newFont = [sender convertFont:font]; + + if (newFont) { + NSString *name = [newFont displayName]; + unsigned len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + if (len > 0) { + NSMutableData *data = [NSMutableData data]; + float pointSize = [newFont pointSize]; + + [data appendBytes:&pointSize length:sizeof(float)]; + + ++len; // include NUL byte + [data appendBytes:&len length:sizeof(unsigned)]; + [data appendBytes:[name UTF8String] length:len]; + + [[self vimController] sendMessage:SetFontMsgID data:data]; + } + } +} + +- (void)scrollWheel:(NSEvent *)event +{ + if ([event deltaY] == 0) + return; + + int row, col; + NSPoint pt = [self convertPoint:[event locationInWindow] fromView:nil]; + + // View is not flipped, instead the atsui code draws to a flipped image; + // thus we need to 'flip' the coordinate here since the column number + // increases in an up-to-down order. + pt.y = [self frame].size.height - pt.y; + + if (![self convertPoint:pt toRow:&row column:&col]) + return; + + int flags = [event modifierFlags]; + float dy = [event deltaY]; + NSMutableData *data = [NSMutableData data]; + + [data appendBytes:&row length:sizeof(int)]; + [data appendBytes:&col length:sizeof(int)]; + [data appendBytes:&flags length:sizeof(int)]; + [data appendBytes:&dy length:sizeof(float)]; + + [[self vimController] sendMessage:ScrollWheelMsgID data:data]; +} + @end // MMAtsuiTextView @@ -629,6 +700,23 @@ static NSString *MMKeypadEnterString = @"KA"; @implementation MMAtsuiTextView (Private) +- (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column +{ + // TODO: text inset + NSPoint origin = { 0,0 }; + + if (!(cellSize.width > 0 && cellSize.height > 0)) + return NO; + + if (row) *row = floor((point.y-origin.y-1) / cellSize.height); + if (column) *column = floor((point.x-origin.x-1) / cellSize.width); + + //NSLog(@"convertPoint:%@ toRow:%d column:%d", NSStringFromPoint(point), + // *row, *column); + + return YES; +} + - (void)initAtsuStyles { int i; @@ -779,6 +867,22 @@ static NSString *MMKeypadEnterString = @"KA"; (row2 + 1 - row1) * cellSize.height); } +- (NSSize)textAreaSize +{ + // Calculate the (desired) size of the text area, i.e. the text view area + // minus the inset area. + return NSMakeSize(maxColumns*cellSize.width, maxRows*cellSize.height); +} + +- (void)resizeContentImage +{ + //NSLog(@"resizeContentImage"); + [contentImage release]; + contentImage = [[NSImage alloc] initWithSize:[self textAreaSize]]; + [contentImage setFlipped: YES]; + imageSize = [self textAreaSize]; +} + - (void)beginDrawing { [contentImage lockFocus]; @@ -789,15 +893,6 @@ static NSString *MMKeypadEnterString = @"KA"; [contentImage unlockFocus]; } -- (void)fitImageToSize -{ - NSLog(@"fitImageToSize"); - [contentImage release]; - contentImage = [[NSImage alloc] initWithSize:[self size]]; - [contentImage setFlipped: YES]; - imageSize = [self size]; -} - - (void)drawString:(UniChar *)string length:(UniCharCount)length atRow:(int)row column:(int)col cells:(int)cells withFlags:(int)flags foregroundColor:(NSColor *)fg @@ -900,4 +995,9 @@ static NSString *MMKeypadEnterString = @"KA"; NSRectFill(NSMakeRect(0, 0, imageSize.width, imageSize.height)); } +- (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape + fraction:(int)percent color:(NSColor *)color +{ +} + @end // MMAtsuiTextView (Drawing) diff --git a/src/MacVim/MMBackend.m b/src/MacVim/MMBackend.m index 0343771d..d9c2760d 100644 --- a/src/MacVim/MMBackend.m +++ b/src/MacVim/MMBackend.m @@ -442,11 +442,19 @@ enum { - (BOOL)waitForInput:(int)milliseconds { //NSLog(@"|ENTER| %s%d", _cmd, milliseconds); - NSDate *date = milliseconds > 0 ? - [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : - [NSDate distantFuture]; - [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date]; + // Only start the run loop if the input queue is empty, otherwise process + // the input first so that the input on queue isn't delayed. + if ([inputQueue count]) { + inputReceived = YES; + } else { + NSDate *date = milliseconds > 0 ? + [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : + [NSDate distantFuture]; + + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode + beforeDate:date]; + } // I know of no way to figure out if the run loop exited because input was // found or because of a time out, so I need to manually indicate when @@ -1546,7 +1554,7 @@ enum { int idx = *((int*)bytes); tabpage_move(idx); - } else if (SetTextDimensionsMsgID == msgid) { + } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid) { if (!data) return; const void *bytes = [data bytes]; int rows = *((int*)bytes); bytes += sizeof(int); @@ -1556,7 +1564,7 @@ enum { // gui_resize_shell(), so we have to manually set the rows and columns // here. (MacVim doesn't change the rows and columns to avoid // inconsistent states between Vim and MacVim.) - [self setRows:rows columns:cols]; + [self queueMessage:msgid data:data]; //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows); gui_resize_shell(cols, rows); diff --git a/src/MacVim/MMFullscreenWindow.h b/src/MacVim/MMFullscreenWindow.h index 65d9153b..af228aed 100644 --- a/src/MacVim/MMFullscreenWindow.h +++ b/src/MacVim/MMFullscreenWindow.h @@ -25,6 +25,7 @@ - (void)enterFullscreen; - (void)leaveFullscreen; +- (void)centerView; - (BOOL)canBecomeKeyWindow; - (BOOL)canBecomeMainWindow; diff --git a/src/MacVim/MMFullscreenWindow.m b/src/MacVim/MMFullscreenWindow.m index f2f20808..c30b73d2 100644 --- a/src/MacVim/MMFullscreenWindow.m +++ b/src/MacVim/MMFullscreenWindow.m @@ -8,9 +8,17 @@ * See README.txt for an overview of the Vim source code. */ /* - * MMFullscreen + * MMFullscreenWindow * - * Support for full-screen editing. + * A window without any decorations which covers an entire screen. + * + * When entering full-screen mode the window controller is set to control an + * instance of this class instead of an MMWindow. (This seems to work fine + * even though the Apple docs state that it is generally a better idea to + * create a separate window controller for each window.) + * + * Most of the full-screen logic is currently in this class although it might + * move to the window controller in the future. * * Author: Nico Weber */ @@ -26,7 +34,6 @@ static int numFullscreenWindows = 0; @interface MMFullscreenWindow (Private) -- (void)centerView; - (BOOL)isOnPrimaryScreen; - (void)hideDockIfAppropriate; - (void)revealDockIfAppropriate; @@ -56,14 +63,14 @@ static int numFullscreenWindows = 0; if (self == nil) return nil; + target = [t retain]; + view = [v retain]; + [self setHasShadow:NO]; [self setShowsResizeIndicator:NO]; [self setBackgroundColor:[NSColor blackColor]]; [self setReleasedWhenClosed:NO]; - target = t; [target retain]; - view = v; [view retain]; - return self; } @@ -93,30 +100,29 @@ static int numFullscreenWindows = 0; [target setDelegate:nil]; // make target's window controller believe that it's now controlling us - [target retain]; // NSWindowController will release target once in the - // in the next line [[target windowController] setWindow:self]; - oldTabBarStyle = [[view tabBarControl] styleName]; [[view tabBarControl] setStyleNamed:@"Unified"]; // add text view oldPosition = [view frame].origin; + [view removeFromSuperviewWithoutNeedingDisplay]; [[self contentView] addSubview:view]; [self setInitialFirstResponder:[view textView]]; - [self setTitle:[target title]]; + // NOTE: Calling setTitle:nil causes an exception to be raised (and it is + // possible that 'target' has no title when we get here). + if ([target title]) + [self setTitle:[target title]]; + [self setOpaque:[target isOpaque]]; // don't set this sooner, so we don't get an additional // focus gained message [self setDelegate:delegate]; - // update bottom right corner scrollbar (no resize handle in fu mode) - [view placeViews]; - // move vim view to the window's center [self centerView]; @@ -158,6 +164,7 @@ static int numFullscreenWindows = 0; // do this _after_ resetting delegate and window controller, so the // window controller doesn't get a focus lost message from the fullscreen // window. + [view removeFromSuperviewWithoutNeedingDisplay]; [[target contentView] addSubview:view]; [view setFrameOrigin:oldPosition]; [self close]; @@ -167,9 +174,6 @@ static int numFullscreenWindows = 0; // sooner [target setDelegate:delegate]; - // update bottom right corner scrollbar (resize handle reappears) - [view placeViews]; - // fade back in if (didBlend) { CGDisplayFade(token, .25, kCGDisplayBlendSolidColor, @@ -178,6 +182,8 @@ static int numFullscreenWindows = 0; } [self revealDockIfAppropriate]; + + [self autorelease]; // Balance the above retain } // Title-less windows normally don't receive key presses, override this @@ -193,90 +199,22 @@ static int numFullscreenWindows = 0; return YES; } - -#pragma mark Proxy/Decorator/whatever stuff - -- (void)scrollWheel:(NSEvent *)theEvent -{ - [[view textView] scrollWheel:theEvent]; -} - -// the window controller will send us messages that are meant for the original, -// non-fullscreen window. forward those, and interpret the messages that are -// interesting for us - -- (void)setTitle:(NSString *)title -{ - [target setTitle:title]; - [super setTitle:title]; -} - -// HACK: if the T flag in guioptions is changed in fu mode, the toolbar needs -// to be changed when nofu is set. MMWindowController gets the toolbar object, -// so we need to return a toolbar from this method, even if none is visible for -// the fullscreen window. Seems to work, though. -- (NSToolbar *)toolbar -{ - return [target toolbar]; -} - -- (void)setFrame:(NSRect)frame display:(BOOL)display -{ - // HACK: if the target window would resize, we have to call our own - // windowDidResize method so that placeViews in MMWindowController is called - if (!NSEqualRects(frame, [target frame])) - { - [target setFrame:frame display:NO]; - - // XXX: send this directly to MMVimView - if ([[self delegate] respondsToSelector:@selector(windowDidResize:)]) - [[self delegate] windowDidResize:nil]; - - [self centerView]; - [self display]; - } -} - -/*- (NSRect)frame -{ - return [target frame]; // really? needed by MMWindowController placeViews. - // but mucks up display -}*/ - -- (NSRect)contentRectForFrameRect:(NSRect)rect -{ - //return [target contentRectForFrameRect:rect]; - - // EVIL HACK: if this is always called with [[self window] frame] as - // argument from MMWindowController, we can't let frame return the frame - // of target so "fix" this here. - if (NSEqualRects([self frame], rect)) { - return [target contentRectForFrameRect:[target frame]]; - } else { - return [target contentRectForFrameRect:rect]; - } -} - -- (NSRect)frameRectForContentRect:(NSRect)contentRect +- (void)centerView { - return [target frameRectForContentRect:contentRect]; + NSRect outer = [self frame], inner = [view frame]; + //NSLog(@"%s %@%@", _cmd, NSStringFromRect(outer), NSStringFromRect(inner)); + + NSPoint origin = NSMakePoint((outer.size.width - inner.size.width)/2, + (outer.size.height - inner.size.height)/2); + [view setFrameOrigin:origin]; } -- (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen*)screen -{ - return [target constrainFrameRect:frameRect toScreen:screen]; -} -- (void)setContentResizeIncrements:(NSSize)size -{ - [target setContentResizeIncrements:size]; -} +#pragma mark Proxy/Decorator/whatever stuff -- (void)setOpaque:(BOOL)isOpaque +- (void)scrollWheel:(NSEvent *)theEvent { - // XXX: Do we want transparency even in fullscreen mode? - [super setOpaque:isOpaque]; - [target setOpaque:isOpaque]; + [[view textView] scrollWheel:theEvent]; } @end // MMFullscreenWindow @@ -286,16 +224,6 @@ static int numFullscreenWindows = 0; @implementation MMFullscreenWindow (Private) -- (void)centerView -{ - NSRect outer = [self frame], inner = [view frame]; - //NSLog(@"%s %@%@", _cmd, NSStringFromRect(outer), NSStringFromRect(inner)); - - NSPoint origin = NSMakePoint((outer.size.width - inner.size.width)/2, - (outer.size.height - inner.size.height)/2); - [view setFrameOrigin:origin]; -} - - (BOOL)isOnPrimaryScreen { // The primary screen is the screen the menu bar is on. This is different diff --git a/src/MacVim/MMTextStorage.m b/src/MacVim/MMTextStorage.m index 7df39350..9cfa4f77 100644 --- a/src/MacVim/MMTextStorage.m +++ b/src/MacVim/MMTextStorage.m @@ -83,6 +83,8 @@ static NSString *MMWideCharacterAttributeName = @"MMWideChar"; - (void)dealloc { + //NSLog(@"MMTextStorage dealloc"); + #if MM_USE_ROW_CACHE if (rowCache) { free(rowCache); diff --git a/src/MacVim/MMTextView.h b/src/MacVim/MMTextView.h index 528d4f0b..7e40a4a6 100644 --- a/src/MacVim/MMTextView.h +++ b/src/MacVim/MMTextView.h @@ -30,11 +30,33 @@ int preEditColumn; } +- (id)initWithFrame:(NSRect)frame; + - (NSEvent *)lastMouseDownEvent; - (void)setShouldDrawInsertionPoint:(BOOL)on; - (void)setPreEditRow:(int)row column:(int)col; - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape fraction:(int)percent color:(NSColor *)color; - (void)hideMarkedTextField; +- (void)performBatchDrawWithData:(NSData *)data; + +// +// MMTextStorage methods +// +- (NSFont *)font; +- (void)setFont:(NSFont *)newFont; +- (void)setWideFont:(NSFont *)newFont; +- (NSSize)cellSize; +- (void)setLinespace:(float)newLinespace; +- (void)getMaxRows:(int*)rows columns:(int*)cols; +- (void)setMaxRows:(int)rows columns:(int)cols; +- (NSRect)rectForRowsInRange:(NSRange)range; +- (NSRect)rectForColumnsInRange:(NSRange)range; +- (void)setDefaultColorsBackground:(NSColor *)bgColor + foreground:(NSColor *)fgColor; + +- (NSSize)constrainRows:(int *)rows columns:(int *)cols toSize:(NSSize)size; +- (NSSize)desiredSize; +- (NSSize)minSize; @end diff --git a/src/MacVim/MMTextView.m b/src/MacVim/MMTextView.m index 21ab4537..b1e7148c 100644 --- a/src/MacVim/MMTextView.m +++ b/src/MacVim/MMTextView.m @@ -21,10 +21,14 @@ #import "MMTextStorage.h" #import "MMWindowController.h" #import "MMVimController.h" +#import "MMTypesetter.h" #import "MacVim.h" +// This is taken from gui.h +#define DRAW_CURSOR 0x20 + // The max/min drag timer interval in seconds static NSTimeInterval MMDragTimerMaxInterval = .3f; static NSTimeInterval MMDragTimerMinInterval = .01f; @@ -35,6 +39,10 @@ static float MMDragAreaSize = 73.0f; static char MMKeypadEnter[2] = { 'K', 'A' }; static NSString *MMKeypadEnterString = @"KA"; +enum { + MMMinRows = 4, + MMMinColumns = 20 +}; @interface MMTextView (Private) @@ -52,8 +60,69 @@ static NSString *MMKeypadEnterString = @"KA"; @implementation MMTextView +- (id)initWithFrame:(NSRect)frame +{ + // Set up a Cocoa text system. Note that the textStorage is released in + // -[MMVimView dealloc]. + MMTextStorage *textStorage = [[MMTextStorage alloc] init]; + NSLayoutManager *lm = [[NSLayoutManager alloc] init]; + NSTextContainer *tc = [[NSTextContainer alloc] initWithContainerSize: + NSMakeSize(1.0e7,1.0e7)]; + + NSString *typesetterString = [[NSUserDefaults standardUserDefaults] + stringForKey:MMTypesetterKey]; + if ([typesetterString isEqual:@"MMTypesetter"]) { + NSTypesetter *typesetter = [[MMTypesetter alloc] init]; + [lm setTypesetter:typesetter]; + [typesetter release]; + } else if ([typesetterString isEqual:@"MMTypesetter2"]) { + NSTypesetter *typesetter = [[MMTypesetter2 alloc] init]; + [lm setTypesetter:typesetter]; + [typesetter release]; + } else { + // Only MMTypesetter supports different cell width multipliers. + [[NSUserDefaults standardUserDefaults] + setFloat:1.0 forKey:MMCellWidthMultiplierKey]; + } + + // The characters in the text storage are in display order, so disable + // bidirectional text processing (this call is 10.4 only). + [[lm typesetter] setBidiProcessingEnabled:NO]; + + [tc setWidthTracksTextView:NO]; + [tc setHeightTracksTextView:NO]; + [tc setLineFragmentPadding:0]; + + [textStorage addLayoutManager:lm]; + [lm addTextContainer:tc]; + + // The text storage retains the layout manager which in turn retains + // the text container. + [tc release]; + [lm release]; + + // NOTE: This will make the text storage the principal owner of the text + // system. Releasing the text storage will in turn release the layout + // manager, the text container, and finally the text view (self). This + // complicates deallocation somewhat, see -[MMVimView dealloc]. + if (![super initWithFrame:frame textContainer:tc]) { + [textStorage release]; + return nil; + } + + // Allow control of text view inset via MMTextInset* user defaults. + NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; + int left = [ud integerForKey:MMTextInsetLeftKey]; + int top = [ud integerForKey:MMTextInsetTopKey]; + [self setTextContainerInset:NSMakeSize(left, top)]; + + return self; +} + - (void)dealloc { + //NSLog(@"MMTextView dealloc"); + if (markedTextField) { [[markedTextField window] autorelease]; [markedTextField release]; @@ -115,6 +184,245 @@ static NSString *MMKeypadEnterString = @"KA"; } } + +#define MM_DEBUG_DRAWING 0 + +- (void)performBatchDrawWithData:(NSData *)data +{ + MMTextStorage *textStorage = (MMTextStorage *)[self textStorage]; + if (!textStorage) + return; + + const void *bytes = [data bytes]; + const void *end = bytes + [data length]; + +#if MM_DEBUG_DRAWING + NSLog(@"====> BEGIN %s", _cmd); +#endif + [textStorage beginEditing]; + + // TODO: Sanity check input + + while (bytes < end) { + int type = *((int*)bytes); bytes += sizeof(int); + + if (ClearAllDrawType == type) { +#if MM_DEBUG_DRAWING + NSLog(@" Clear all"); +#endif + [textStorage clearAll]; + } else if (ClearBlockDrawType == type) { + unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned); + int row1 = *((int*)bytes); bytes += sizeof(int); + int col1 = *((int*)bytes); bytes += sizeof(int); + int row2 = *((int*)bytes); bytes += sizeof(int); + int col2 = *((int*)bytes); bytes += sizeof(int); + +#if MM_DEBUG_DRAWING + NSLog(@" Clear block (%d,%d) -> (%d,%d)", row1, col1, + row2,col2); +#endif + [textStorage clearBlockFromRow:row1 column:col1 + toRow:row2 column:col2 + color:[NSColor colorWithArgbInt:color]]; + } else if (DeleteLinesDrawType == type) { + unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned); + int row = *((int*)bytes); bytes += sizeof(int); + int count = *((int*)bytes); bytes += sizeof(int); + int bot = *((int*)bytes); bytes += sizeof(int); + int left = *((int*)bytes); bytes += sizeof(int); + int right = *((int*)bytes); bytes += sizeof(int); + +#if MM_DEBUG_DRAWING + NSLog(@" Delete %d line(s) from %d", count, row); +#endif + [textStorage deleteLinesFromRow:row lineCount:count + scrollBottom:bot left:left right:right + color:[NSColor colorWithArgbInt:color]]; + } else if (DrawStringDrawType == type) { + int bg = *((int*)bytes); bytes += sizeof(int); + int fg = *((int*)bytes); bytes += sizeof(int); + int sp = *((int*)bytes); bytes += sizeof(int); + int row = *((int*)bytes); bytes += sizeof(int); + int col = *((int*)bytes); bytes += sizeof(int); + int cells = *((int*)bytes); bytes += sizeof(int); + int flags = *((int*)bytes); bytes += sizeof(int); + int len = *((int*)bytes); bytes += sizeof(int); + NSString *string = [[NSString alloc] + initWithBytesNoCopy:(void*)bytes + length:len + encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + bytes += len; + +#if MM_DEBUG_DRAWING + NSLog(@" Draw string at (%d,%d) length=%d flags=%d fg=0x%x " + "bg=0x%x sp=0x%x (%@)", row, col, len, flags, fg, bg, sp, + len > 0 ? [string substringToIndex:1] : @""); +#endif + // NOTE: If this is a call to draw the (block) cursor, then cancel + // any previous request to draw the insertion point, or it might + // get drawn as well. + if (flags & DRAW_CURSOR) { + [self setShouldDrawInsertionPoint:NO]; + //NSColor *color = [NSColor colorWithRgbInt:bg]; + //[self drawInsertionPointAtRow:row column:col + // shape:MMInsertionPointBlock + // color:color]; + } + + [textStorage drawString:string + atRow:row column:col cells:cells + withFlags:flags + foregroundColor:[NSColor colorWithRgbInt:fg] + backgroundColor:[NSColor colorWithArgbInt:bg] + specialColor:[NSColor colorWithRgbInt:sp]]; + + [string release]; + } else if (InsertLinesDrawType == type) { + unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned); + int row = *((int*)bytes); bytes += sizeof(int); + int count = *((int*)bytes); bytes += sizeof(int); + int bot = *((int*)bytes); bytes += sizeof(int); + int left = *((int*)bytes); bytes += sizeof(int); + int right = *((int*)bytes); bytes += sizeof(int); + +#if MM_DEBUG_DRAWING + NSLog(@" Insert %d line(s) at row %d", count, row); +#endif + [textStorage insertLinesAtRow:row lineCount:count + scrollBottom:bot left:left right:right + color:[NSColor colorWithArgbInt:color]]; + } else if (DrawCursorDrawType == type) { + unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned); + int row = *((int*)bytes); bytes += sizeof(int); + int col = *((int*)bytes); bytes += sizeof(int); + int shape = *((int*)bytes); bytes += sizeof(int); + int percent = *((int*)bytes); bytes += sizeof(int); + +#if MM_DEBUG_DRAWING + NSLog(@" Draw cursor at (%d,%d)", row, col); +#endif + [self drawInsertionPointAtRow:row column:col shape:shape + fraction:percent + color:[NSColor colorWithRgbInt:color]]; + } else { + NSLog(@"WARNING: Unknown draw type (type=%d)", type); + } + } + + [textStorage endEditing]; + + // NOTE: During resizing, Cocoa only sends draw messages before Vim's rows + // and columns are changed (due to ipc delays). Force a redraw here. + [self displayIfNeeded]; + +#if MM_DEBUG_DRAWING + NSLog(@"<==== END %s", _cmd); +#endif +} + +- (NSFont *)font +{ + return [(MMTextStorage*)[self textStorage] font]; +} + +- (void)setFont:(NSFont *)newFont +{ + [(MMTextStorage*)[self textStorage] setFont:newFont]; +} + +- (void)setWideFont:(NSFont *)newFont +{ + [(MMTextStorage*)[self textStorage] setWideFont:newFont]; +} + +- (NSSize)cellSize +{ + return [(MMTextStorage*)[self textStorage] cellSize]; +} + +- (void)setLinespace:(float)newLinespace +{ + return [(MMTextStorage*)[self textStorage] setLinespace:newLinespace]; +} + +- (void)getMaxRows:(int*)rows columns:(int*)cols +{ + return [(MMTextStorage*)[self textStorage] getMaxRows:rows columns:cols]; +} + +- (void)setMaxRows:(int)rows columns:(int)cols +{ + return [(MMTextStorage*)[self textStorage] setMaxRows:rows columns:cols]; +} + +- (NSRect)rectForRowsInRange:(NSRange)range +{ + return [(MMTextStorage*)[self textStorage] rectForRowsInRange:range]; +} + +- (NSRect)rectForColumnsInRange:(NSRange)range +{ + return [(MMTextStorage*)[self textStorage] rectForColumnsInRange:range]; +} + +- (void)setDefaultColorsBackground:(NSColor *)bgColor + foreground:(NSColor *)fgColor +{ + [self setBackgroundColor:bgColor]; + return [(MMTextStorage*)[self textStorage] + setDefaultColorsBackground:bgColor foreground:fgColor]; +} + +- (NSSize)constrainRows:(int *)rows columns:(int *)cols toSize:(NSSize)size +{ + NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; + int right = [ud integerForKey:MMTextInsetRightKey]; + int bot = [ud integerForKey:MMTextInsetBottomKey]; + + size.width -= [self textContainerOrigin].x + right; + size.height -= [self textContainerOrigin].y + bot; + + NSSize newSize = [(MMTextStorage*)[self textStorage] fitToSize:size + rows:rows + columns:cols]; + + newSize.width += [self textContainerOrigin].x + right; + newSize.height += [self textContainerOrigin].y + bot; + + return newSize; +} + +- (NSSize)desiredSize +{ + NSSize size = [(MMTextStorage*)[self textStorage] size]; + + NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; + int right = [ud integerForKey:MMTextInsetRightKey]; + int bot = [ud integerForKey:MMTextInsetBottomKey]; + + size.width += [self textContainerOrigin].x + right; + size.height += [self textContainerOrigin].y + bot; + + return size; +} + +- (NSSize)minSize +{ + NSSize cellSize = [(MMTextStorage*)[self textStorage] cellSize]; + NSSize size = { MMMinColumns*cellSize.width, MMMinRows*cellSize.height }; + + NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; + int right = [ud integerForKey:MMTextInsetRightKey]; + int bot = [ud integerForKey:MMTextInsetBottomKey]; + + size.width += [self textContainerOrigin].x + right; + size.height += [self textContainerOrigin].y + bot; + + return size; +} + - (BOOL)isOpaque { return NO; @@ -772,22 +1080,6 @@ static NSString *MMKeypadEnterString = @"KA"; // The font panel is updated whenever the font is set. } -- (void)viewWillStartLiveResize -{ - id windowController = [[self window] windowController]; - [windowController liveResizeWillStart]; - - [super viewWillStartLiveResize]; -} - -- (void)viewDidEndLiveResize -{ - id windowController = [[self window] windowController]; - [windowController liveResizeDidEnd]; - - [super viewDidEndLiveResize]; -} - @end // MMTextView diff --git a/src/MacVim/MMVimController.m b/src/MacVim/MMVimController.m index 4a036cbd..442b5ddb 100644 --- a/src/MacVim/MMVimController.m +++ b/src/MacVim/MMVimController.m @@ -25,15 +25,12 @@ #import "MMVimController.h" #import "MMWindowController.h" -#import "MMTextView.h" #import "MMAppController.h" -#import "MMTextStorage.h" +#import "MMVimView.h" +#import "MMTextView.h" #import "MMAtsuiTextView.h" -// This is taken from gui.h -#define DRAW_CURSOR 0x20 - static NSString *MMDefaultToolbarImageName = @"Attention"; static int MMAlertTextFieldHeight = 22; @@ -61,7 +58,6 @@ static NSTimeInterval MMResendInterval = 0.5; @interface MMVimController (Private) - (void)handleMessage:(int)msgid data:(NSData *)data; -- (void)performBatchDrawWithData:(NSData *)data; - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code context:(void *)context; - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context; @@ -116,13 +112,12 @@ static NSTimeInterval MMResendInterval = 0.5; name:NSConnectionDidDieNotification object:connection]; - NSWindow *win = [windowController window]; - + // TODO: What if [windowController window] is the full-screen window? [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidBecomeMain:) name:NSWindowDidBecomeMainNotification - object:win]; + object:[windowController window]]; isInitialized = YES; } @@ -322,6 +317,11 @@ static NSTimeInterval MMResendInterval = 0.5; - (BOOL)sendMessageNow:(int)msgid data:(NSData *)data timeout:(NSTimeInterval)timeout { + // Send a message with a timeout. USE WITH EXTREME CAUTION! Sending + // messages in rapid succession with a timeout may cause MacVim to beach + // ball forever. In almost all circumstances sendMessage:data: should be + // used instead. + if (!isInitialized || inProcessCommandQueue) return NO; @@ -549,16 +549,13 @@ static NSTimeInterval MMResendInterval = 0.5; - (void)handleMessage:(int)msgid data:(NSData *)data { - //NSLog(@"%@ %s", [self className], _cmd); + //if (msgid != AddMenuMsgID && msgid != AddMenuItemMsgID) + // NSLog(@"%@ %s%s", [self className], _cmd, MessageStrings[msgid]); if (OpenVimWindowMsgID == msgid) { [windowController openWindow]; } else if (BatchDrawMsgID == msgid) { - if ([[NSUserDefaults standardUserDefaults] - boolForKey:MMAtsuiRendererKey]) - [(MMAtsuiTextView *)[windowController textView] performBatchDrawWithData:data]; - else - [self performBatchDrawWithData:data]; + [[[windowController vimView] textView] performBatchDrawWithData:data]; } else if (SelectTabMsgID == msgid) { #if 0 // NOTE: Tab selection is done inside updateTabsWithData:. const void *bytes = [data bytes]; @@ -572,12 +569,13 @@ static NSTimeInterval MMResendInterval = 0.5; [windowController showTabBar:YES]; } else if (HideTabBarMsgID == msgid) { [windowController showTabBar:NO]; - } else if (SetTextDimensionsMsgID == msgid) { + } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid) { const void *bytes = [data bytes]; int rows = *((int*)bytes); bytes += sizeof(int); int cols = *((int*)bytes); bytes += sizeof(int); - [windowController setTextDimensionsWithRows:rows columns:cols]; + [windowController setTextDimensionsWithRows:rows columns:cols + live:(LiveResizeMsgID==msgid)]; } else if (SetWindowTitleMsgID == msgid) { const void *bytes = [data bytes]; int len = *((int*)bytes); bytes += sizeof(int); @@ -585,7 +583,7 @@ static NSTimeInterval MMResendInterval = 0.5; NSString *string = [[NSString alloc] initWithBytes:(void*)bytes length:len encoding:NSUTF8StringEncoding]; - [[windowController window] setTitle:string]; + [windowController setTitle:string]; [string release]; } else if (AddMenuMsgID == msgid) { @@ -614,17 +612,7 @@ static NSTimeInterval MMResendInterval = 0.5; [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly]; [toolbar setSizeMode:NSToolbarSizeModeSmall]; - NSWindow *win = [windowController window]; - [win setToolbar:toolbar]; - - // HACK! Redirect the pill button so that we can ask Vim to - // hide the toolbar. - NSButton *pillButton = [win - standardWindowButton:NSWindowToolbarButton]; - if (pillButton) { - [pillButton setAction:@selector(toggleToolbar:)]; - [pillButton setTarget:windowController]; - } + [windowController setToolbar:toolbar]; } } else if (title) { [self addMenuWithTag:tag parent:parentTag title:title atIndex:idx]; @@ -862,157 +850,18 @@ static NSTimeInterval MMResendInterval = 0.5; } else if (LeaveFullscreenMsgID == msgid) { [windowController leaveFullscreen]; } else if (BuffersNotModifiedMsgID == msgid) { - [[windowController window] setDocumentEdited:NO]; + [windowController setBuffersModified:NO]; } else if (BuffersModifiedMsgID == msgid) { - [[windowController window] setDocumentEdited:YES]; + [windowController setBuffersModified:YES]; } else if (SetPreEditPositionMsgID == msgid) { const int *dim = (const int*)[data bytes]; - [[windowController textView] setPreEditRow:dim[0] column:dim[1]]; + [[[windowController vimView] textView] setPreEditRow:dim[0] + column:dim[1]]; } else { NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid); } } - -#define MM_DEBUG_DRAWING 0 - -- (void)performBatchDrawWithData:(NSData *)data -{ - // TODO! Move to window controller. - MMTextStorage *textStorage = [windowController textStorage]; - MMTextView *textView = [windowController textView]; - if (!(textStorage && textView)) - return; - - const void *bytes = [data bytes]; - const void *end = bytes + [data length]; - -#if MM_DEBUG_DRAWING - NSLog(@"====> BEGIN %s", _cmd); -#endif - [textStorage beginEditing]; - - // TODO: Sanity check input - - while (bytes < end) { - int type = *((int*)bytes); bytes += sizeof(int); - - if (ClearAllDrawType == type) { -#if MM_DEBUG_DRAWING - NSLog(@" Clear all"); -#endif - [textStorage clearAll]; - } else if (ClearBlockDrawType == type) { - unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned); - int row1 = *((int*)bytes); bytes += sizeof(int); - int col1 = *((int*)bytes); bytes += sizeof(int); - int row2 = *((int*)bytes); bytes += sizeof(int); - int col2 = *((int*)bytes); bytes += sizeof(int); - -#if MM_DEBUG_DRAWING - NSLog(@" Clear block (%d,%d) -> (%d,%d)", row1, col1, - row2,col2); -#endif - [textStorage clearBlockFromRow:row1 column:col1 - toRow:row2 column:col2 - color:[NSColor colorWithArgbInt:color]]; - } else if (DeleteLinesDrawType == type) { - unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned); - int row = *((int*)bytes); bytes += sizeof(int); - int count = *((int*)bytes); bytes += sizeof(int); - int bot = *((int*)bytes); bytes += sizeof(int); - int left = *((int*)bytes); bytes += sizeof(int); - int right = *((int*)bytes); bytes += sizeof(int); - -#if MM_DEBUG_DRAWING - NSLog(@" Delete %d line(s) from %d", count, row); -#endif - [textStorage deleteLinesFromRow:row lineCount:count - scrollBottom:bot left:left right:right - color:[NSColor colorWithArgbInt:color]]; - } else if (DrawStringDrawType == type) { - int bg = *((int*)bytes); bytes += sizeof(int); - int fg = *((int*)bytes); bytes += sizeof(int); - int sp = *((int*)bytes); bytes += sizeof(int); - int row = *((int*)bytes); bytes += sizeof(int); - int col = *((int*)bytes); bytes += sizeof(int); - int cells = *((int*)bytes); bytes += sizeof(int); - int flags = *((int*)bytes); bytes += sizeof(int); - int len = *((int*)bytes); bytes += sizeof(int); - NSString *string = [[NSString alloc] - initWithBytesNoCopy:(void*)bytes - length:len - encoding:NSUTF8StringEncoding - freeWhenDone:NO]; - bytes += len; - -#if MM_DEBUG_DRAWING - NSLog(@" Draw string at (%d,%d) length=%d flags=%d fg=0x%x " - "bg=0x%x sp=0x%x (%@)", row, col, len, flags, fg, bg, sp, - len > 0 ? [string substringToIndex:1] : @""); -#endif - // NOTE: If this is a call to draw the (block) cursor, then cancel - // any previous request to draw the insertion point, or it might - // get drawn as well. - if (flags & DRAW_CURSOR) { - [textView setShouldDrawInsertionPoint:NO]; - //NSColor *color = [NSColor colorWithRgbInt:bg]; - //[textView drawInsertionPointAtRow:row column:col - // shape:MMInsertionPointBlock - // color:color]; - } - - [textStorage drawString:string - atRow:row column:col cells:cells - withFlags:flags - foregroundColor:[NSColor colorWithRgbInt:fg] - backgroundColor:[NSColor colorWithArgbInt:bg] - specialColor:[NSColor colorWithRgbInt:sp]]; - - [string release]; - } else if (InsertLinesDrawType == type) { - unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned); - int row = *((int*)bytes); bytes += sizeof(int); - int count = *((int*)bytes); bytes += sizeof(int); - int bot = *((int*)bytes); bytes += sizeof(int); - int left = *((int*)bytes); bytes += sizeof(int); - int right = *((int*)bytes); bytes += sizeof(int); - -#if MM_DEBUG_DRAWING - NSLog(@" Insert %d line(s) at row %d", count, row); -#endif - [textStorage insertLinesAtRow:row lineCount:count - scrollBottom:bot left:left right:right - color:[NSColor colorWithArgbInt:color]]; - } else if (DrawCursorDrawType == type) { - unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned); - int row = *((int*)bytes); bytes += sizeof(int); - int col = *((int*)bytes); bytes += sizeof(int); - int shape = *((int*)bytes); bytes += sizeof(int); - int percent = *((int*)bytes); bytes += sizeof(int); - -#if MM_DEBUG_DRAWING - NSLog(@" Draw cursor at (%d,%d)", row, col); -#endif - [textView drawInsertionPointAtRow:row column:col shape:shape - fraction:percent - color:[NSColor colorWithRgbInt:color]]; - } else { - NSLog(@"WARNING: Unknown draw type (type=%d)", type); - } - } - - [textStorage endEditing]; - - // NOTE: During resizing, Cocoa only sends draw messages before Vim's rows - // and columns are changed (due to ipc delays). Force a redraw here. - [[windowController vimView] displayIfNeeded]; - -#if MM_DEBUG_DRAWING - NSLog(@"<==== END %s", _cmd); -#endif -} - - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code context:(void *)context { diff --git a/src/MacVim/MMVimView.h b/src/MacVim/MMVimView.h index 8f2e3d90..676e5ab8 100644 --- a/src/MacVim/MMVimView.h +++ b/src/MacVim/MMVimView.h @@ -14,7 +14,6 @@ @class PSMTabBarControl; @class MMTextView; -@class MMTextStorage; @class MMScroller; @class MMVimController; @@ -22,29 +21,22 @@ @interface MMVimView : NSView { PSMTabBarControl *tabBarControl; NSTabView *tabView; - MMVimController *vimController; BOOL vimTaskSelectedTab; MMTextView *textView; - MMTextStorage *textStorage; NSMutableArray *scrollbars; - - // This is temporary to make the refactoring easier (XXX) - BOOL shouldUpdateWindowSize; } -- (MMVimView *)initWithFrame:(NSRect)frame vimController:(MMVimController *) c; +- (MMVimView *)initWithFrame:(NSRect)frame vimController:(MMVimController *)c; - (MMTextView *)textView; -- (MMTextStorage *)textStorage; - (NSMutableArray *)scrollbars; -- (BOOL)inLiveResize; - (void)cleanup; -- (NSSize)desiredSizeForActualRowsAndColumns; -- (NSSize)getDesiredRows:(int *)r columns:(int *)c forSize:(NSSize)size; -- (void)getActualRows:(int *)r columns:(int *)c; -- (void)setActualRows:(int)r columns:(int)c; +- (NSSize)desiredSize; +- (NSSize)minSize; +- (NSSize)constrainRows:(int *)r columns:(int *)c toSize:(NSSize)size; +- (void)setDesiredRows:(int)r columns:(int)c; - (PSMTabBarControl *)tabBarControl; - (IBAction)addNewTab:(id)sender; @@ -53,18 +45,17 @@ - (NSTabViewItem *)addNewTabViewItem; - (void)createScrollbarWithIdentifier:(long)ident type:(int)type; -- (void)destroyScrollbarWithIdentifier:(long)ident; -- (void)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible; +- (BOOL)destroyScrollbarWithIdentifier:(long)ident; +- (BOOL)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible; - (void)setScrollbarThumbValue:(float)val proportion:(float)prop identifier:(long)ident; - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident; - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore; -- (BOOL)shouldUpdateWindowSize; -- (NSRect)textViewRectForContentSize:(NSSize)contentSize; -- (void)setShouldUpdateWindowSize:(BOOL)b; - -- (void)placeViews; // XXX: this should probably not be public +- (void)viewWillStartLiveResize; +- (void)viewDidEndLiveResize; +- (void)setFrameSize:(NSSize)size; +- (void)setFrame:(NSRect)frame; @end diff --git a/src/MacVim/MMVimView.m b/src/MacVim/MMVimView.m index 3a617dff..69356574 100644 --- a/src/MacVim/MMVimView.m +++ b/src/MacVim/MMVimView.m @@ -10,7 +10,12 @@ /* * MMVimView * - * A view class with a tabline, scrollbars, and text view. + * A view class with a tabline, scrollbars, and a text view. The tabline may + * appear at the top of the view in which case it fills up the view from left + * to right edge. Any number of scrollbars may appear adjacent to all other + * edges of the view (there may be more than one scrollbar per edge and + * scrollbars may also be placed on the left edge of the view). The rest of + * the view is filled by the text view. */ #import "MMVimView.h" @@ -18,12 +23,10 @@ #import #import "MacVim.h" #import "MMTextView.h" -#import "MMTextStorage.h" -#import "MMTypesetter.h" #import "MMVimController.h" #import "MMAtsuiTextView.h" -#import "MMWindowController.h" // needed by MMScroller. TODO: remove + // Scroller type; these must match SBAR_* in gui.h enum { @@ -59,88 +62,49 @@ enum { - (void)placeScrollbars; - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi; - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx; -- (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize; -- (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize; +- (NSSize)vimViewSizeForTextViewSize:(NSSize)textViewSize; +- (NSRect)textViewRectForVimViewSize:(NSSize)contentSize; - (NSTabView *)tabView; +- (void)frameSizeMayHaveChanged; @end -@implementation MMVimView +// This is an informal protocol implemented by MMWindowController (maybe it +// shold be a formal protocol, but ...). +@interface NSWindowController (MMVimViewDelegate) +- (void)liveResizeWillStart; +- (void)liveResizeDidEnd; +@end -- (NSRect)tabBarFrameForFrame:(NSRect)frame -{ - NSRect tabFrame = { - { 0, frame.size.height - 22 }, - { frame.size.width, 22 } - }; - return tabFrame; -} + + +@implementation MMVimView - (MMVimView *)initWithFrame:(NSRect)frame - vimController:(MMVimController *)controller { + vimController:(MMVimController *)controller +{ if (![super initWithFrame:frame]) return nil; vimController = controller; scrollbars = [[NSMutableArray alloc] init]; + // Only the tabline is autoresized, all other subview placement is done in + // frameSizeMayHaveChanged. + [self setAutoresizesSubviews:YES]; + if ([[NSUserDefaults standardUserDefaults] boolForKey:MMAtsuiRendererKey]) { // Use ATSUI for text rendering. + // + // HACK! 'textView' has type MMTextView, but MMAtsuiTextView is not + // derived from MMTextView. textView = [[MMAtsuiTextView alloc] initWithFrame:frame]; - - // HACK! The ATSUI text view has no text storage, but to avoid having - // to rewrite a lot of code we simply pretend like there still is a - // text storage. - textStorage = [textView retain]; } else { - // Set up a Cocoa text system. - textStorage = [[MMTextStorage alloc] init]; - NSLayoutManager *lm = [[NSLayoutManager alloc] init]; - NSTextContainer *tc = [[NSTextContainer alloc] initWithContainerSize: - NSMakeSize(1.0e7,1.0e7)]; - - NSString *typesetterString = [[NSUserDefaults standardUserDefaults] - stringForKey:MMTypesetterKey]; - if ([typesetterString isEqual:@"MMTypesetter"]) { - NSTypesetter *typesetter = [[MMTypesetter alloc] init]; - [lm setTypesetter:typesetter]; - [typesetter release]; - } else if ([typesetterString isEqual:@"MMTypesetter2"]) { - NSTypesetter *typesetter = [[MMTypesetter2 alloc] init]; - [lm setTypesetter:typesetter]; - [typesetter release]; - } else { - // Only MMTypesetter supports different cell width multipliers. - [[NSUserDefaults standardUserDefaults] - setFloat:1.0 forKey:MMCellWidthMultiplierKey]; - } - - // The characters in the text storage are in display order, so disable - // bidirectional text processing (this call is 10.4 only). - [[lm typesetter] setBidiProcessingEnabled:NO]; - - [tc setWidthTracksTextView:NO]; - [tc setHeightTracksTextView:NO]; - [tc setLineFragmentPadding:0]; - - [textStorage addLayoutManager:lm]; - [lm addTextContainer:tc]; - - textView = [[MMTextView alloc] initWithFrame:frame - textContainer:tc]; - - // The text storage retains the layout manager which in turn retains - // the text container. - [tc release]; - [lm release]; + // Use Cocoa text system for text rendering. + textView = [[MMTextView alloc] initWithFrame:frame]; } - // Allow control of text view inset via MMTextInset* user defaults. - NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; - int left = [ud integerForKey:MMTextInsetLeftKey]; - int top = [ud integerForKey:MMTextInsetTopKey]; - [textView setTextContainerInset:NSMakeSize(left, top)]; - + [textView setAutoresizingMask:NSViewNotSizable]; [self addSubview:textView]; // Create the tab view (which is never visible, but the tab bar control @@ -149,7 +113,8 @@ enum { // Create the tab bar control (which is responsible for actually // drawing the tabline and tabs). - NSRect tabFrame = [self tabBarFrameForFrame:frame]; + NSRect tabFrame = { { 0, frame.size.height - 22 }, + { frame.size.width, 22 } }; tabBarControl = [[PSMTabBarControl alloc] initWithFrame:tabFrame]; [tabView setDelegate:tabBarControl]; @@ -157,16 +122,21 @@ enum { [tabBarControl setTabView:tabView]; [tabBarControl setDelegate:self]; [tabBarControl setHidden:YES]; + + NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; [tabBarControl setCellMinWidth:[ud integerForKey:MMTabMinWidthKey]]; [tabBarControl setCellMaxWidth:[ud integerForKey:MMTabMaxWidthKey]]; [tabBarControl setCellOptimumWidth: [ud integerForKey:MMTabOptimumWidthKey]]; + [tabBarControl setShowAddTabButton:YES]; [[tabBarControl addTabButton] setTarget:self]; [[tabBarControl addTabButton] setAction:@selector(addNewTab:)]; [tabBarControl setAllowsDragBetweenWindows:NO]; + + [tabBarControl setAutoresizingMask:NSViewWidthSizable|NSViewMinYMargin]; - [tabBarControl setPartnerView:[self textView]]; + //[tabBarControl setPartnerView:[self textView]]; // tab bar resizing only works if awakeFromNib is called (that's where // the NSViewFrameDidChangeNotification callback is installed). Sounds like @@ -175,11 +145,6 @@ enum { [self addSubview:tabBarControl]; - [self setPostsFrameChangedNotifications:YES]; - [[NSNotificationCenter defaultCenter] - addObserver:self selector:@selector(placeViews) - name:NSViewFrameDidChangeNotification object:self]; - return self; } @@ -188,8 +153,15 @@ enum { [tabBarControl release]; tabBarControl = nil; [tabView release]; tabView = nil; [scrollbars release]; scrollbars = nil; + + // HACK! The text storage is the principal owner of the text system, but we + // keep only a reference to the text view, so release the text storage + // first (unless we are using the ATSUI renderer). + if (![[NSUserDefaults standardUserDefaults] + boolForKey:MMAtsuiRendererKey]) + [[textView textStorage] release]; + [textView release]; textView = nil; - [textStorage release]; textStorage = nil; [super dealloc]; } @@ -231,52 +203,33 @@ enum { return textView; } -- (MMTextStorage *)textStorage -{ - return textStorage; -} - - (NSMutableArray *)scrollbars { return scrollbars; } -- (BOOL)inLiveResize -{ - return [textView inLiveResize]; -} - - (PSMTabBarControl *)tabBarControl { return tabBarControl; } -- (NSTabView *)tabView -{ - return tabView; -} - - - (void)cleanup { vimController = nil; // NOTE! There is a bug in PSMTabBarControl in that it retains the delegate - // (which is the MMWindowController) so reset the delegate here, otherwise - // the MMWindowController never gets released resulting in a pretty serious - // memory leak. + // so reset the delegate here, otherwise the delegate may never get + // released. [tabView setDelegate:nil]; [tabBarControl setDelegate:nil]; [tabBarControl setTabView:nil]; [[self window] setDelegate:nil]; // NOTE! There is another bug in PSMTabBarControl where the control is not - // removed as an observer, so remove it here (else lots of evil nasty bugs - // will come and gnaw at your feet while you are sleeping). + // removed as an observer, so remove it here (failing to remove an observer + // may lead to very strange bugs). [[NSNotificationCenter defaultCenter] removeObserver:tabBarControl]; - [[NSNotificationCenter defaultCenter] removeObserver:self]; - [tabBarControl removeFromSuperviewWithoutNeedingDisplay]; [textView removeFromSuperviewWithoutNeedingDisplay]; @@ -289,34 +242,30 @@ enum { [tabView removeAllTabViewItems]; } -- (NSSize)desiredSizeForActualRowsAndColumns +- (NSSize)desiredSize { - return [self contentSizeForTextStorageSize:[[self textStorage] size]]; + return [self vimViewSizeForTextViewSize:[[self textView] desiredSize]]; } -- (NSSize)getDesiredRows:(int *)r columns:(int *)c forSize:(NSSize)size +- (NSSize)minSize { - NSSize textViewSize = [self textViewRectForContentSize:size].size; - NSSize textStorageSize = [self textStorageSizeForTextViewSize:textViewSize]; - NSSize newSize = [textStorage fitToSize:textStorageSize rows:r columns:c]; - return [self contentSizeForTextStorageSize:newSize]; + return [self vimViewSizeForTextViewSize:[[self textView] minSize]]; } -- (void)getActualRows:(int *)r columns:(int *)c +- (NSSize)constrainRows:(int *)r columns:(int *)c toSize:(NSSize)size { - [textStorage getMaxRows:r columns:c]; + NSSize textViewSize = [self textViewRectForVimViewSize:size].size; + textViewSize = [textView constrainRows:r columns:c toSize:textViewSize]; + return [self vimViewSizeForTextViewSize:textViewSize]; } -- (void)setActualRows:(int)r columns:(int)c +- (void)setDesiredRows:(int)r columns:(int)c { - [textStorage setMaxRows:r columns:c]; + [textView setMaxRows:r columns:c]; } - (IBAction)addNewTab:(id)sender { - // NOTE! This can get called a lot if the user holds down the key - // equivalent for this action, which causes the ports to fill up. If we - // wait for the message to be sent then the app might become unresponsive. [vimController sendMessage:AddNewTabMsgID data:nil]; } @@ -389,16 +338,16 @@ enum { [[self tabView] selectTabViewItem:tvi]; vimTaskSelectedTab = NO; - // we might need to change the scrollbars that are visible + // We might need to change the scrollbars that are visible. [self placeScrollbars]; } } - (NSTabViewItem *)addNewTabViewItem { - // NOTE! A newly created tab is not by selected by default; the VimTask - // decides which tab should be selected at all times. However, the AppKit - // will automatically select the first tab added to a tab view. + // NOTE! A newly created tab is not by selected by default; Vim decides + // which tab should be selected at all times. However, the AppKit will + // automatically select the first tab added to a tab view. NSTabViewItem *tvi = [[NSTabViewItem alloc] initWithIdentifier:nil]; @@ -412,48 +361,6 @@ enum { return tvi; } -- (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi -{ - NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems]; - return [tabViewItems indexOfObject:tvi]; -} - -- (BOOL)bottomScrollbarVisible -{ - unsigned i, count = [scrollbars count]; - for (i = 0; i < count; ++i) { - MMScroller *scroller = [scrollbars objectAtIndex:i]; - if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden]) - return YES; - } - - return NO; -} - -- (BOOL)leftScrollbarVisible -{ - unsigned i, count = [scrollbars count]; - for (i = 0; i < count; ++i) { - MMScroller *scroller = [scrollbars objectAtIndex:i]; - if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden]) - return YES; - } - - return NO; -} - -- (BOOL)rightScrollbarVisible -{ - unsigned i, count = [scrollbars count]; - for (i = 0; i < count; ++i) { - MMScroller *scroller = [scrollbars objectAtIndex:i]; - if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden]) - return YES; - } - - return NO; -} - - (void)createScrollbarWithIdentifier:(long)ident type:(int)type { //NSLog(@"Create scroller %d of type %d", ident, type); @@ -468,42 +375,35 @@ enum { [scroller release]; } -- (void)destroyScrollbarWithIdentifier:(long)ident +- (BOOL)destroyScrollbarWithIdentifier:(long)ident { //NSLog(@"Destroy scroller %d", ident); unsigned idx = 0; MMScroller *scroller = [self scrollbarForIdentifier:ident index:&idx]; - if (scroller) { - [scroller removeFromSuperview]; - [[self scrollbars] removeObjectAtIndex:idx]; + if (!scroller) return NO; - if (![scroller isHidden]) { - // A visible scroller was removed, so the window must resize to - // fit. - //NSLog(@"Visible scroller %d was destroyed, resizing window.", - // ident); - shouldUpdateWindowSize = YES; - } - } + [scroller removeFromSuperview]; + [[self scrollbars] removeObjectAtIndex:idx]; + + // If a visible scroller was removed then the vim view must resize. This + // is handled by the window controller (the vim view never resizes itself). + return ![scroller isHidden]; } -- (void)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible +- (BOOL)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible { MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL]; - if (!scroller) return; + if (!scroller) return NO; BOOL wasVisible = ![scroller isHidden]; //NSLog(@"%s scroller %d (was %svisible)", visible ? "Show" : "Hide", // ident, wasVisible ? "" : "in"); [scroller setHidden:!visible]; - if (wasVisible != visible) { - // A scroller was hidden or shown, so the window must resize to fit. - //NSLog(@"%s scroller %d and resize.", visible ? "Show" : "Hide", - // ident); - shouldUpdateWindowSize = YES; - } + // If a scroller was hidden or shown then the vim view must resize. This + // is handled by the window controller (the vim view never resizes itself). + return wasVisible != visible; } - (void)setScrollbarThumbValue:(float)val proportion:(float)prop @@ -531,6 +431,161 @@ enum { [vimController sendMessage:ScrollbarEventMsgID data:data]; } +- (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident +{ + MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL]; + NSRange range = NSMakeRange(pos, len); + if (!NSEqualRanges(range, [scroller range])) { + //NSLog(@"Set range %@ for scroller %d", + // NSStringFromRange(range), ident); + [scroller setRange:range]; + // TODO! Should only do this once per update. + + // This could be sent because a text window was created or closed, so + // we might need to update which scrollbars are visible. + [self placeScrollbars]; + } +} + +- (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore +{ + [textView setDefaultColorsBackground:back foreground:fore]; +} + + +// -- PSMTabBarControl delegate ---------------------------------------------- + + +- (BOOL)tabView:(NSTabView *)theTabView shouldSelectTabViewItem: + (NSTabViewItem *)tabViewItem +{ + // NOTE: It would be reasonable to think that 'shouldSelect...' implies + // that this message only gets sent when the user clicks the tab. + // Unfortunately it is not so, which is why we need the + // 'vimTaskSelectedTab' flag. + // + // HACK! The selection message should not be propagated to Vim if Vim + // selected the tab (e.g. as opposed the user clicking the tab). The + // delegate method has no way of knowing who initiated the selection so a + // flag is set when Vim initiated the selection. + if (!vimTaskSelectedTab) { + // Propagate the selection message to Vim. + int idx = [self representedIndexOfTabViewItem:tabViewItem]; + if (NSNotFound != idx) { + NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)]; + [vimController sendMessage:SelectTabMsgID data:data]; + } + } + + // Unless Vim selected the tab, return NO, and let Vim decide if the tab + // should get selected or not. + return vimTaskSelectedTab; +} + +- (BOOL)tabView:(NSTabView *)theTabView shouldCloseTabViewItem: + (NSTabViewItem *)tabViewItem +{ + // HACK! This method is only called when the user clicks the close button + // on the tab. Instead of letting the tab bar close the tab, we return NO + // and pass a message on to Vim to let it handle the closing. + int idx = [self representedIndexOfTabViewItem:tabViewItem]; + //NSLog(@"Closing tab with index %d", idx); + NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)]; + [vimController sendMessage:CloseTabMsgID data:data]; + + return NO; +} + +- (void)tabView:(NSTabView *)theTabView didDragTabViewItem: + (NSTabViewItem *)tabViewItem toIndex:(int)idx +{ + NSMutableData *data = [NSMutableData data]; + [data appendBytes:&idx length:sizeof(int)]; + + [vimController sendMessage:DraggedTabMsgID data:data]; +} + + +// -- NSView customization --------------------------------------------------- + + +- (void)viewWillStartLiveResize +{ + id windowController = [[self window] windowController]; + [windowController liveResizeWillStart]; + + [super viewWillStartLiveResize]; +} + +- (void)viewDidEndLiveResize +{ + id windowController = [[self window] windowController]; + [windowController liveResizeDidEnd]; + + [super viewDidEndLiveResize]; +} + +- (void)setFrameSize:(NSSize)size +{ + // NOTE: Instead of only acting when a frame was resized, we do some + // updating each time a frame may be resized. (At the moment, if we only + // respond to actual frame changes then typing ":set lines=1000" twice in a + // row will result in the vim view holding more rows than the can fit + // inside the window.) + [super setFrameSize:size]; + [self frameSizeMayHaveChanged]; +} + +- (void)setFrame:(NSRect)frame +{ + // See comment in setFrameSize: above. + [super setFrame:frame]; + [self frameSizeMayHaveChanged]; +} + +@end // MMVimView + + + + +@implementation MMVimView (Private) + +- (BOOL)bottomScrollbarVisible +{ + unsigned i, count = [scrollbars count]; + for (i = 0; i < count; ++i) { + MMScroller *scroller = [scrollbars objectAtIndex:i]; + if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden]) + return YES; + } + + return NO; +} + +- (BOOL)leftScrollbarVisible +{ + unsigned i, count = [scrollbars count]; + for (i = 0; i < count; ++i) { + MMScroller *scroller = [scrollbars objectAtIndex:i]; + if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden]) + return YES; + } + + return NO; +} + +- (BOOL)rightScrollbarVisible +{ + unsigned i, count = [scrollbars count]; + for (i = 0; i < count; ++i) { + MMScroller *scroller = [scrollbars objectAtIndex:i]; + if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden]) + return YES; + } + + return NO; +} + - (void)placeScrollbars { NSRect textViewFrame = [textView frame]; @@ -574,7 +629,7 @@ enum { NSRect rect; if ([scroller type] == MMScrollerTypeBottom) { - rect = [textStorage rectForColumnsInRange:[scroller range]]; + rect = [textView rectForColumnsInRange:[scroller range]]; rect.size.height = [NSScroller scrollerWidth]; if (lsbVisible) rect.origin.x += [NSScroller scrollerWidth]; @@ -598,7 +653,7 @@ enum { if (rect.size.width < 0) rect.size.width = 0; } else { - rect = [textStorage rectForRowsInRange:[scroller range]]; + rect = [textView rectForRowsInRange:[scroller range]]; // Adjust for the fact that text layout is flipped. rect.origin.y = NSMaxY(textViewFrame) - rect.origin.y - rect.size.height; @@ -653,6 +708,12 @@ enum { } } +- (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi +{ + NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems]; + return [tabViewItems indexOfObject:tvi]; +} + - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx { unsigned i, count = [[self scrollbars] count]; @@ -667,53 +728,7 @@ enum { return nil; } -- (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident -{ - MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL]; - NSRange range = NSMakeRange(pos, len); - if (!NSEqualRanges(range, [scroller range])) { - //NSLog(@"Set range %@ for scroller %d", - // NSStringFromRange(range), ident); - [scroller setRange:range]; - // TODO! Should only do this once per update. - - // This could be sent because a text window was created or closed, so - // we might need to update which scrollbars are visible. - [self placeScrollbars]; - } -} - -- (void)placeViews -{ - NSRect textViewRect = [self textViewRectForContentSize:[self frame].size]; - - // Give all superfluous space to the text view. It might be smaller or - // larger than it wants to be, but this is needed during life resizing - [[self textView] setFrame:textViewRect]; - - // for some reason, autoresizing doesn't work...set tab size manually - [tabBarControl setFrame:[self tabBarFrameForFrame:[self frame]]]; - - [self placeScrollbars]; -} - -- (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore -{ - [textStorage setDefaultColorsBackground:back foreground:fore]; - [textView setBackgroundColor:back]; -} - -- (BOOL)shouldUpdateWindowSize -{ - return shouldUpdateWindowSize; -} - -- (void)setShouldUpdateWindowSize:(BOOL)b -{ - shouldUpdateWindowSize = b; -} - -- (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize +- (NSSize)vimViewSizeForTextViewSize:(NSSize)textViewSize { NSSize size = textViewSize; @@ -727,17 +742,10 @@ enum { if ([self rightScrollbarVisible]) size.width += [NSScroller scrollerWidth]; - NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; - int right = [ud integerForKey:MMTextInsetRightKey]; - int bot = [ud integerForKey:MMTextInsetBottomKey]; - - size.width += [[self textView] textContainerOrigin].x + right; - size.height += [[self textView] textContainerOrigin].y + bot; - return size; } -- (NSRect)textViewRectForContentSize:(NSSize)contentSize +- (NSRect)textViewRectForVimViewSize:(NSSize)contentSize { NSRect rect = { 0, 0, contentSize.width, contentSize.height }; @@ -758,74 +766,65 @@ enum { return rect; } -- (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize +- (NSTabView *)tabView { - NSSize size = textViewSize; - - NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; - int right = [ud integerForKey:MMTextInsetRightKey]; - int bot = [ud integerForKey:MMTextInsetBottomKey]; - - size.width -= [[self textView] textContainerOrigin].x + right; - size.height -= [[self textView] textContainerOrigin].y + bot; - - return size; + return tabView; } +- (void)frameSizeMayHaveChanged +{ + // NOTE: Whenever a call is made that may have changed the frame size we + // take the opportunity to make sure all subviews are in place and that the + // (rows,columns) are constrained to lie inside the new frame. We not only + // do this when the frame really has changed since it is possible to modify + // the number of (rows,columns) without changing the frame size. -// -- PSMTabBarControl delegate ---------------------------------------------- + // Give all superfluous space to the text view. It might be smaller or + // larger than it wants to be, but this is needed during live resizing. + NSRect textViewRect = [self textViewRectForVimViewSize:[self frame].size]; + [[self textView] setFrame:textViewRect]; + [self placeScrollbars]; -- (BOOL)tabView:(NSTabView *)theTabView shouldSelectTabViewItem: - (NSTabViewItem *)tabViewItem -{ - // NOTE: It would be reasonable to think that 'shouldSelect...' implies - // that this message only gets sent when the user clicks the tab. - // Unfortunately it is not so, which is why we need the - // 'vimTaskSelectedTab' flag. + // It is possible that the current number of (rows,columns) is too big or + // too small to fit the new frame. If so, notify Vim that the text + // dimensions should change, but don't actually change the number of + // (rows,columns). These numbers may only change when Vim initiates the + // change (as opposed to the user dragging the window resizer, for + // example). // - // HACK! The selection message should not be propagated to the VimTask if - // the VimTask selected the tab (e.g. as opposed the user clicking the - // tab). The delegate method has no way of knowing who initiated the - // selection so a flag is set when the VimTask initiated the selection. - if (!vimTaskSelectedTab) { - // Propagate the selection message to the VimTask. - int idx = [self representedIndexOfTabViewItem:tabViewItem]; - if (NSNotFound != idx) { - NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)]; - [vimController sendMessage:SelectTabMsgID data:data]; + // Note that the message sent to Vim depends on whether we're in + // a live resize or not -- this is necessary to avoid the window jittering + // when the user drags to resize. + int constrained[2]; + NSSize textViewSize = [[self textView] frame].size; + [textView constrainRows:&constrained[0] columns:&constrained[1] + toSize:textViewSize]; + + int rows, cols; + [textView getMaxRows:&rows columns:&cols]; + + if (constrained[0] != rows || constrained[1] != cols) { + NSData *data = [NSData dataWithBytes:constrained length:2*sizeof(int)]; + int msgid = [self inLiveResize] ? LiveResizeMsgID + : SetTextDimensionsMsgID; + + //NSLog(@"Notify Vim that text dimensions changed from %dx%d to %dx%d" + // " (%s)", cols, rows, constrained[1], constrained[0], + // MessageStrings[msgid]); + + [vimController sendMessage:msgid data:data]; + + // We only want to set the window title if this resize came from + // a live-resize, not (for example) setting 'columns' or 'lines'. + if ([self inLiveResize]) { + [[self window] setTitle:[NSString stringWithFormat:@"%dx%d", + constrained[1], constrained[0]]]; } } - - // Unless Vim selected the tab, return NO, and let Vim decide if the tab - // should get selected or not. - return vimTaskSelectedTab; } -- (BOOL)tabView:(NSTabView *)theTabView shouldCloseTabViewItem: - (NSTabViewItem *)tabViewItem -{ - // HACK! This method is only called when the user clicks the close button - // on the tab. Instead of letting the tab bar close the tab, we return NO - // and pass a message on to Vim to let it handle the closing. - int idx = [self representedIndexOfTabViewItem:tabViewItem]; - //NSLog(@"Closing tab with index %d", idx); - NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)]; - [vimController sendMessage:CloseTabMsgID data:data]; - - return NO; -} - -- (void)tabView:(NSTabView *)theTabView didDragTabViewItem: - (NSTabViewItem *)tabViewItem toIndex:(int)idx -{ - NSMutableData *data = [NSMutableData data]; - [data appendBytes:&idx length:sizeof(int)]; - - [vimController sendMessage:DraggedTabMsgID data:data]; -} - -@end +@end // MMVimView (Private) @@ -858,12 +857,14 @@ enum { ? NSMakeRect(0, 0, 1, 0) : NSMakeRect(0, 0, 0, 1); - if ((self = [super initWithFrame:frame])) { - identifier = ident; - type = theType; - [self setHidden:YES]; - [self setEnabled:YES]; - } + self = [super initWithFrame:frame]; + if (!self) return nil; + + identifier = ident; + type = theType; + [self setHidden:YES]; + [self setEnabled:YES]; + [self setAutoresizingMask:NSViewNotSizable]; return self; } @@ -891,8 +892,9 @@ enum { - (void)scrollWheel:(NSEvent *)event { // HACK! Pass message on to the text view. - MMWindowController *wc = [[self window] windowController]; - [[wc textView] scrollWheel:event]; + NSView *vimView = [self superview]; + if ([vimView isKindOfClass:[MMVimView class]]) + [[(MMVimView*)vimView textView] scrollWheel:event]; } @end // MMScroller diff --git a/src/MacVim/MMWindow.h b/src/MacVim/MMWindow.h new file mode 100644 index 00000000..e80dafcb --- /dev/null +++ b/src/MacVim/MMWindow.h @@ -0,0 +1,32 @@ +/* vi:set ts=8 sts=4 sw=4 ft=objc: + * + * VIM - Vi IMproved by Bram Moolenaar + * MacVim GUI port by Bjorn Winckler + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +#import + + + +@interface MMWindow : NSWindow { + NSBox *tablineSeparator; +} + +- (id)initWithContentRect:(NSRect)rect + styleMask:(unsigned int)style + backing:(NSBackingStoreType)bufferingType + defer:(BOOL)flag; + +- (BOOL)hideTablineSeparator:(BOOL)hide; + +- (NSRect)contentRectForFrameRect:(NSRect)frame; +- (NSRect)frameRectForContentRect:(NSRect)rect; +- (void)setContentMinSize:(NSSize)size; +- (void)setContentMaxSize:(NSSize)size; +- (void)setContentSize:(NSSize)size; + +@end diff --git a/src/MacVim/MMWindow.m b/src/MacVim/MMWindow.m new file mode 100644 index 00000000..2a5968f5 --- /dev/null +++ b/src/MacVim/MMWindow.m @@ -0,0 +1,120 @@ +/* vi:set ts=8 sts=4 sw=4 ft=objc: + * + * VIM - Vi IMproved by Bram Moolenaar + * MacVim GUI port by Bjorn Winckler + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ +/* + * MMWindow + * + * A normal window with a (possibly hidden) tabline separator at the top of the + * content view. + * + * The main point of this class is for the window controller to be able to call + * contentRectForFrameRect: without having to worry about whether the separator + * is visible or not. + * + * This is a bit of a hack, it would be nicer to be able to leave the content + * view alone, but as it is the tabline separator is a subview of the content + * view. Since we want to pretend that the content view does not contain the + * separator this leads to some dangerous situations. For instance, calling + * [window setContentMinSize:size] when the separator is visible results in + * size != [window contentMinSize], since the latter is one pixel higher than + * 'size'. + */ + +#import "MMWindow.h" +#import "MacVim.h" + + + + +@implementation MMWindow + +- (id)initWithContentRect:(NSRect)rect + styleMask:(unsigned int)style + backing:(NSBackingStoreType)bufferingType + defer:(BOOL)flag +{ + self = [super initWithContentRect:rect + styleMask:style + backing:bufferingType + defer:flag]; + if (!self) return nil; + + NSRect tabSepRect = { 0, rect.size.height - 1, rect.size.width, 1 }; + tablineSeparator = [[NSBox alloc] initWithFrame:tabSepRect]; + + [tablineSeparator setBoxType:NSBoxSeparator]; + [tablineSeparator setHidden:YES]; + [tablineSeparator setAutoresizingMask:NSViewWidthSizable|NSViewMinYMargin]; + + NSView *contentView = [self contentView]; + [contentView setAutoresizesSubviews:YES]; + [contentView addSubview:tablineSeparator]; + + return self; +} + +- (void)dealloc +{ + [tablineSeparator removeFromSuperviewWithoutNeedingDisplay]; + [tablineSeparator release]; tablineSeparator = nil; + [super dealloc]; +} + +- (BOOL)hideTablineSeparator:(BOOL)hide +{ + BOOL isHidden = [tablineSeparator isHidden]; + [tablineSeparator setHidden:hide]; + + // Return YES if visibility state was toggled, NO if it was unchanged. + return isHidden != hide; +} + +- (NSRect)contentRectForFrameRect:(NSRect)frame +{ + NSRect rect = [super contentRectForFrameRect:frame]; + if (![tablineSeparator isHidden]) + --rect.size.height; + + return rect; +} + +- (NSRect)frameRectForContentRect:(NSRect)rect +{ + NSRect frame = [super frameRectForContentRect:rect]; + if (![tablineSeparator isHidden]) + ++frame.size.height; + + return frame; +} + +- (void)setContentMinSize:(NSSize)size +{ + if (![tablineSeparator isHidden]) + ++size.height; + + [super setContentMinSize:size]; +} + +- (void)setContentMaxSize:(NSSize)size +{ + if (![tablineSeparator isHidden]) + ++size.height; + + [super setContentMaxSize:size]; +} + +- (void)setContentSize:(NSSize)size +{ + if (![tablineSeparator isHidden]) + ++size.height; + + [super setContentSize:size]; +} + +@end // MMWindow diff --git a/src/MacVim/MMWindowController.h b/src/MacVim/MMWindowController.h index c73bb777..a2fd82e7 100644 --- a/src/MacVim/MMWindowController.h +++ b/src/MacVim/MMWindowController.h @@ -12,27 +12,25 @@ +@class MMWindow; @class MMFullscreenWindow; @class MMVimController; -@class MMTextStorage; -@class MMTextView; @class MMVimView; @interface MMWindowController : NSWindowController { - NSBox *tablineSeparator; MMVimController *vimController; MMVimView *vimView; BOOL setupDone; - BOOL shouldUpdateWindowSize; + BOOL shouldResizeVimView; + BOOL fullscreenEnabled; NSString *windowAutosaveKey; MMFullscreenWindow *fullscreenWindow; + MMWindow *decoratedWindow; NSString *lastSetTitle; } - (id)initWithVimController:(MMVimController *)controller; - (MMVimController *)vimController; -- (MMTextView *)textView; -- (MMTextStorage *)textStorage; - (MMVimView *)vimView; - (NSString *)windowAutosaveKey; - (void)setWindowAutosaveKey:(NSString *)key; @@ -40,10 +38,12 @@ - (void)openWindow; - (void)updateTabsWithData:(NSData *)data; - (void)selectTabWithIndex:(int)idx; -- (void)setTextDimensionsWithRows:(int)rows columns:(int)cols; +- (void)setTextDimensionsWithRows:(int)rows columns:(int)cols live:(BOOL)live; +- (void)setTitle:(NSString *)title; +- (void)setToolbar:(NSToolbar *)toolbar; - (void)createScrollbarWithIdentifier:(long)ident type:(int)type; -- (void)destroyScrollbarWithIdentifier:(long)ident; -- (void)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible; +- (BOOL)destroyScrollbarWithIdentifier:(long)ident; +- (BOOL)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible; - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident; - (void)setScrollbarThumbValue:(float)val proportion:(float)prop identifier:(long)ident; @@ -58,10 +58,10 @@ - (void)adjustLinespace:(int)linespace; - (void)liveResizeWillStart; - (void)liveResizeDidEnd; -- (void)placeViews; - (void)enterFullscreen; - (void)leaveFullscreen; +- (void)setBuffersModified:(BOOL)mod; - (IBAction)addNewTab:(id)sender; - (IBAction)toggleToolbar:(id)sender; diff --git a/src/MacVim/MMWindowController.m b/src/MacVim/MMWindowController.m index 8071af9c..acf09f8b 100644 --- a/src/MacVim/MMWindowController.m +++ b/src/MacVim/MMWindowController.m @@ -12,60 +12,76 @@ * * Handles resizing of windows, acts as an mediator between MMVimView and * MMVimController. + * + * Resizing in windowed mode: + * + * In windowed mode resizing can occur either due to the window frame changing + * size (e.g. when the user drags to resize), or due to Vim changing the number + * of (rows,columns). The former case is dealt with by letting the vim view + * fill the entire content view when the window has resized. In the latter + * case we ensure that vim view fits on the screen. + * + * The vim view notifies Vim if the number of (rows,columns) does not match the + * current number whenver the view size is about to change. Upon receiving a + * dimension change message, Vim notifies the window controller and the window + * resizes. However, the window is never resized programmatically during a + * live resize (in order to avoid jittering). + * + * The window size is constrained to not become too small during live resize, + * and it is also constrained to always fit an integer number of + * (rows,columns). + * + * In windowed mode we have to manually draw a tabline separator (due to bugs + * in the way Cocoa deals with the toolbar separator) when certain conditions + * are met. The rules for this are as follows: + * + * Tabline visible & Toolbar visible => Separator visible + * ===================================================================== + * NO & NO => YES, if the window is textured + * NO, otherwise + * NO & YES => YES + * YES & NO => NO + * YES & YES => NO + * + * + * Resizing in full-screen mode: + * + * The window never resizes since it fills the screen, however the vim view may + * change size, e.g. when the user types ":set lines=60", or when a scrollbar + * is toggled. + * + * It is ensured that the vim view never becomes larger than the screen size + * and that it always stays in the center of the screen. + * */ #import "MMWindowController.h" -#import -#import "MMTextView.h" -#import "MMTextStorage.h" -#import "MMVimController.h" -#import "MacVim.h" #import "MMAppController.h" -#import "MMTypesetter.h" +#import "MMAtsuiTextView.h" #import "MMFullscreenWindow.h" +#import "MMTextView.h" +#import "MMTypesetter.h" +#import "MMVimController.h" #import "MMVimView.h" -#import "MMAtsuiTextView.h" +#import "MMWindow.h" +#import "MacVim.h" + +#import @interface MMWindowController (Private) - (NSSize)contentSize; -- (NSRect)contentRectForFrameRect:(NSRect)frame; -- (NSRect)frameRectForContentRect:(NSRect)contentRect; -- (void)resizeWindowToFit:(id)sender; -- (NSRect)fitWindowToFrame:(NSRect)frame; -- (void)updateResizeIncrements; +- (void)resizeWindowToFitContentSize:(NSSize)contentSize; +- (NSSize)constrainContentSizeToScreenSize:(NSSize)contentSize; +- (void)updateResizeConstraints; - (NSTabViewItem *)addNewTabViewItem; - (IBAction)vimMenuItemAction:(id)sender; - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb; -- (void)checkWindowNeedsResizing; -- (NSSize)resizeVimViewToFitSize:(NSSize)size; +- (void)hideTablineSeparator:(BOOL)hide; @end - -#if 0 -NSString *buildMenuItemDescriptor(NSMenu *menu, NSString *tail) -{ - return menu ? buildMenuItemDescriptor([menu supermenu], [[menu title] - stringByAppendingString:tail]) - : tail; -} - -NSMutableArray *buildMenuAddress(NSMenu *menu) -{ - NSMutableArray *addr; - if (menu) { - addr = buildMenuAddress([menu supermenu]); - [addr addObject:[menu title]]; - } else { - addr = [NSMutableArray array]; - } - - return addr; -} -#endif - @interface NSWindow (NSWindowPrivate) // Note: This hack allows us to set content shadowing separately from // the window shadow. This is apparently what webkit and terminal do. @@ -76,6 +92,7 @@ NSMutableArray *buildMenuAddress(NSMenu *menu) - (void)setBottomCornerRounded:(BOOL)rounded; @end + @interface NSWindow (NSLeopardOnly) // Note: These functions are Leopard-only, use -[NSObject respondsToSelector:] // before calling them to make sure everything works on Tiger too. @@ -118,70 +135,61 @@ NSMutableArray *buildMenuAddress(NSMenu *menu) // started (or rather, when ~/Library/Preferences/org.vim.MacVim.plist does // not exist). The chosen values will put the window somewhere near the // top and in the middle of a 1024x768 screen. - NSWindow *win = [[NSWindow alloc] + MMWindow *win = [[[MMWindow alloc] initWithContentRect:NSMakeRect(242,364,480,360) styleMask:styleMask backing:NSBackingStoreBuffered - defer:YES]; - - if ((self = [super initWithWindow:win])) { - vimController = controller; - - // Window cascading is handled by MMAppController. - [self setShouldCascadeWindows:NO]; - - NSView *contentView = [win contentView]; - vimView = [[MMVimView alloc] initWithFrame:[contentView frame] - vimController:vimController]; - [contentView addSubview:vimView]; - - // Create the tabline separator (which may be visible when the tabline - // is hidden). See showTabBar: for circumstances when the separator - // should be hidden. - NSRect tabSepRect = [contentView frame]; - tabSepRect.origin.y = NSMaxY(tabSepRect)-1; - tabSepRect.size.height = 1; - tablineSeparator = [[NSBox alloc] initWithFrame:tabSepRect]; - - [tablineSeparator setBoxType:NSBoxSeparator]; - [tablineSeparator setHidden:NO]; - [tablineSeparator setAutoresizingMask:NSViewWidthSizable - | NSViewMinYMargin]; - - [contentView setAutoresizesSubviews:YES]; - [contentView addSubview:tablineSeparator]; - - [win setDelegate:self]; - [win setInitialFirstResponder:[vimView textView]]; - - if ([win styleMask] & NSTexturedBackgroundWindowMask) { - // On Leopard, we want to have a textured window to have nice - // looking tabs. But the textured window look implies rounded - // corners, which looks really weird -- disable them. This is a - // private api, though. - if ([win respondsToSelector:@selector(setBottomCornerRounded:)]) - [win setBottomCornerRounded:NO]; - - // When the tab bar is toggled, it changes color for the fraction - // of a second, probably because vim sends us events in a strange - // order, confusing appkit's content border heuristic for a short - // while. This can be worked around with these two methods. There - // might be a better way, but it's good enough. - if ([win respondsToSelector:@selector( - setAutorecalculatesContentBorderThickness:forEdge:)]) - [win setAutorecalculatesContentBorderThickness:NO - forEdge:NSMaxYEdge]; - if ([win respondsToSelector: - @selector(setContentBorderThickness:forEdge:)]) - [win setContentBorderThickness:0 forEdge:NSMaxYEdge]; - } - - // Make us safe on pre-tiger OSX - if ([win respondsToSelector:@selector(_setContentHasShadow:)]) - [win _setContentHasShadow:NO]; + defer:YES] autorelease]; + + self = [super initWithWindow:win]; + if (!self) return nil; + + vimController = controller; + decoratedWindow = [win retain]; + + // Window cascading is handled by MMAppController. + [self setShouldCascadeWindows:NO]; + + // NOTE: Autoresizing is enabled for the content view, but only used + // for the tabline separator. The vim view must be resized manually + // because of full-screen considerations, and because its size depends + // on whether the tabline separator is visible or not. + NSView *contentView = [win contentView]; + [contentView setAutoresizesSubviews:YES]; + + vimView = [[MMVimView alloc] initWithFrame:[contentView frame] + vimController:vimController]; + [vimView setAutoresizingMask:NSViewNotSizable]; + [contentView addSubview:vimView]; + + [win setDelegate:self]; + [win setInitialFirstResponder:[vimView textView]]; + + if ([win styleMask] & NSTexturedBackgroundWindowMask) { + // On Leopard, we want to have a textured window to have nice + // looking tabs. But the textured window look implies rounded + // corners, which looks really weird -- disable them. This is a + // private api, though. + if ([win respondsToSelector:@selector(setBottomCornerRounded:)]) + [win setBottomCornerRounded:NO]; + + // When the tab bar is toggled, it changes color for the fraction + // of a second, probably because vim sends us events in a strange + // order, confusing appkit's content border heuristic for a short + // while. This can be worked around with these two methods. There + // might be a better way, but it's good enough. + if ([win respondsToSelector:@selector( + setAutorecalculatesContentBorderThickness:forEdge:)]) + [win setAutorecalculatesContentBorderThickness:NO + forEdge:NSMaxYEdge]; + if ([win respondsToSelector: + @selector(setContentBorderThickness:forEdge:)]) + [win setContentBorderThickness:0 forEdge:NSMaxYEdge]; } - [win release]; + // Make us safe on pre-tiger OSX + if ([win respondsToSelector:@selector(_setContentHasShadow:)]) + [win _setContentHasShadow:NO]; return self; } @@ -190,7 +198,7 @@ NSMutableArray *buildMenuAddress(NSMenu *menu) { //NSLog(@"%@ %s", [self className], _cmd); - [tablineSeparator release]; tablineSeparator = nil; + [decoratedWindow release]; decoratedWindow = nil; [windowAutosaveKey release]; windowAutosaveKey = nil; [vimView release]; vimView = nil; @@ -210,16 +218,6 @@ NSMutableArray *buildMenuAddress(NSMenu *menu) return vimController; } -- (MMTextView *)textView -{ - return [vimView textView]; -} - -- (MMTextStorage *)textStorage -{ - return [vimView textStorage]; -} - - (MMVimView *)vimView { return vimView; @@ -240,24 +238,23 @@ NSMutableArray *buildMenuAddress(NSMenu *menu) { //NSLog(@"%@ %s", [self className], _cmd); - if (fullscreenWindow != nil) { - // if we are closed while still in fullscreen, end fullscreen mode, + if (fullscreenEnabled) { + // If we are closed while still in fullscreen, end fullscreen mode, // release ourselves (because this won't happen in MMWindowController) - // and perform close operation on the original window + // and perform close operation on the original window. [self leaveFullscreen]; } setupDone = NO; vimController = nil; - [tablineSeparator removeFromSuperviewWithoutNeedingDisplay]; [vimView removeFromSuperviewWithoutNeedingDisplay]; - [vimView cleanup]; // TODO: is this necessary? + [vimView cleanup]; - // It is feasible that the user quits before the window controller is - // released, make sure the edit flag is cleared so no warning dialog is - // displayed. - [[self window] setDocumentEdited:NO]; + // It is feasible (though unlikely) that the user quits before the window + // controller is released, make sure the edit flag is cleared so no warning + // dialog is displayed. + [decoratedWindow setDocumentEdited:NO]; [[self window] orderOut:self]; } @@ -270,8 +267,8 @@ NSMutableArray *buildMenuAddress(NSMenu *menu) setupDone = YES; - [self updateResizeIncrements]; - [self resizeWindowToFit:self]; + [self updateResizeConstraints]; + [self resizeWindowToFitContentSize:[vimView desiredSize]]; [[self window] makeKeyAndOrderFront:self]; } @@ -285,14 +282,47 @@ NSMutableArray *buildMenuAddress(NSMenu *menu) [vimView selectTabWithIndex:idx]; } -- (void)setTextDimensionsWithRows:(int)rows columns:(int)cols +- (void)setTextDimensionsWithRows:(int)rows columns:(int)cols live:(BOOL)live { - //NSLog(@"setTextDimensionsWithRows:%d columns:%d", rows, cols); + //NSLog(@"setTextDimensionsWithRows:%d columns:%d live:%s", rows, cols, + // live ? "YES" : "NO"); + + // NOTE: This is the only place where the (rows,columns) of the vim view + // are modified. Setting these values have no immediate effect, the actual + // resizing of the view is done in processCommandQueueDidFinish. + // + // The 'live' flag indicates that this resize originated from a live + // resize; it may very well happen that the view is no longer in live + // resize when this message is received. We refrain from changing the view + // size when this flag is set, otherwise the window might jitter when the + // user drags to resize the window. - [vimView setActualRows:rows columns:cols]; + [vimView setDesiredRows:rows columns:cols]; - if (setupDone && ![vimView inLiveResize]) - shouldUpdateWindowSize = YES; + if (setupDone && !live) + shouldResizeVimView = YES; +} + +- (void)setTitle:(NSString *)title +{ + // The full-screen window has no title (?!) and it does not show up in the + // Window menu. + [decoratedWindow setTitle:title]; +} + +- (void)setToolbar:(NSToolbar *)toolbar +{ + // The full-screen window has no toolbar. + [decoratedWindow setToolbar:toolbar]; + + // HACK! Redirect the pill button so that we can ask Vim to hide the + // toolbar. + NSButton *pillButton = [decoratedWindow + standardWindowButton:NSWindowToolbarButton]; + if (pillButton) { + [pillButton setAction:@selector(toggleToolbar:)]; + [pillButton setTarget:self]; + } } - (void)createScrollbarWithIdentifier:(long)ident type:(int)type @@ -300,16 +330,21 @@ NSMutableArray *buildMenuAddress(NSMenu *menu) [vimView createScrollbarWithIdentifier:ident type:type]; } -- (void)destroyScrollbarWithIdentifier:(long)ident +- (BOOL)destroyScrollbarWithIdentifier:(long)ident { - [vimView destroyScrollbarWithIdentifier:ident]; - [self checkWindowNeedsResizing]; + BOOL scrollbarHidden = [vimView destroyScrollbarWithIdentifier:ident]; + shouldResizeVimView = shouldResizeVimView || scrollbarHidden; + + return scrollbarHidden; } -- (void)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible +- (BOOL)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible { - [vimView showScrollbarWithIdentifier:ident state:visible]; - [self checkWindowNeedsResizing]; + BOOL scrollbarToggled = [vimView showScrollbarWithIdentifier:ident + state:visible]; + shouldResizeVimView = shouldResizeVimView || scrollbarToggled; + + return scrollbarToggled; } - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident @@ -336,23 +371,38 @@ NSMutableArray *buildMenuAddress(NSMenu *menu) - (void)setFont:(NSFont *)font { [[NSFontManager sharedFontManager] setSelectedFont:font isMultiple:NO]; - [[vimView textStorage] setFont:font]; - [self updateResizeIncrements]; + [[vimView textView] setFont:font]; + [self updateResizeConstraints]; } - (void)setWideFont:(NSFont *)font { - [[vimView textStorage] setWideFont:font]; + [[vimView textView] setWideFont:font]; } - (void)processCommandQueueDidFinish { - // XXX: If not in live resize and vimview's desired size differs from actual - // size, resize ourselves - if (shouldUpdateWindowSize) { - shouldUpdateWindowSize = NO; - [vimView setShouldUpdateWindowSize:NO]; - [self resizeWindowToFit:self]; + // NOTE: Resizing is delayed until after all commands have been processed + // since it often happens that more than one command will cause a resize. + // If we were to immediately resize then the vim view size would jitter + // (e.g. hiding/showing scrollbars often happens several time in one + // update). + + if (shouldResizeVimView) { + shouldResizeVimView = NO; + + NSSize contentSize = [vimView desiredSize]; + contentSize = [self constrainContentSizeToScreenSize:contentSize]; + contentSize = [vimView constrainRows:NULL columns:NULL + toSize:contentSize]; + [vimView setFrameSize:contentSize]; + + if (fullscreenEnabled) { + [[fullscreenWindow contentView] setNeedsDisplay:YES]; + [fullscreenWindow centerView]; + } else { + [self resizeWindowToFitContentSize:contentSize]; + } } } @@ -362,7 +412,8 @@ NSMutableArray *buildMenuAddress(NSMenu *menu) NSEvent *event; if (row >= 0 && col >= 0) { - NSSize cellSize = [[vimView textStorage] cellSize]; + // TODO: Let textView convert (row,col) to NSPoint. + NSSize cellSize = [[vimView textView] cellSize]; NSPoint pt = { (col+1)*cellSize.width, (row+1)*cellSize.height }; pt = [[vimView textView] convertPoint:pt toView:nil]; @@ -386,57 +437,47 @@ NSMutableArray *buildMenuAddress(NSMenu *menu) { [[vimView tabBarControl] setHidden:!on]; - // Rules for when to show tabline separator: - // - // Tabline visible & Toolbar visible => Separator visible - // ================================================================ - // NO & NO => NO (Tiger), YES (Leopard) - // NO & YES => YES - // YES & NO => NO - // YES & YES => NO - // - // XXX: This is ignored if called while in fullscreen mode + // Showing the tabline may result in the tabline separator being hidden or + // shown; this does not apply to full-screen mode. if (!on) { - NSToolbar *toolbar = [[self window] toolbar]; - if (([[self window] styleMask] & NSTexturedBackgroundWindowMask) == 0) { - [tablineSeparator setHidden:![toolbar isVisible]]; + NSToolbar *toolbar = [decoratedWindow toolbar]; + if (([decoratedWindow styleMask] & NSTexturedBackgroundWindowMask) + == 0) { + [self hideTablineSeparator:![toolbar isVisible]]; } else { - [tablineSeparator setHidden:NO]; + [self hideTablineSeparator:NO]; } } else { - if (([[self window] styleMask] & NSTexturedBackgroundWindowMask) == 0) { - [tablineSeparator setHidden:on]; + if (([decoratedWindow styleMask] & NSTexturedBackgroundWindowMask) + == 0) { + [self hideTablineSeparator:on]; } else { - [tablineSeparator setHidden:YES]; + [self hideTablineSeparator:YES]; } } - - //if (setupDone) - // shouldUpdateWindowSize = YES; } - (void)showToolbar:(BOOL)on size:(int)size mode:(int)mode { - NSToolbar *toolbar = [[self window] toolbar]; + NSToolbar *toolbar = [decoratedWindow toolbar]; if (!toolbar) return; [toolbar setSizeMode:size]; [toolbar setDisplayMode:mode]; [toolbar setVisible:on]; - // See showTabBar: for circumstances when the separator should be hidden. - if (([[self window] styleMask] & NSTexturedBackgroundWindowMask) == 0) { + if (([decoratedWindow styleMask] & NSTexturedBackgroundWindowMask) == 0) { if (!on) { - [tablineSeparator setHidden:YES]; + [self hideTablineSeparator:YES]; } else { - [tablineSeparator setHidden:![[vimView tabBarControl] isHidden]]; + [self hideTablineSeparator:![[vimView tabBarControl] isHidden]]; } } else { // Textured windows don't have a line below there title bar, so we // need the separator in this case as well. In fact, the only case // where we don't need the separator is when the tab bar control // is visible (because it brings its own separator). - [tablineSeparator setHidden:![[vimView tabBarControl] isHidden]]; + [self hideTablineSeparator:![[vimView tabBarControl] isHidden]]; } } @@ -463,9 +504,9 @@ NSMutableArray *buildMenuAddress(NSMenu *menu) - (void)adjustLinespace:(int)linespace { - if (vimView && [vimView textStorage]) { - [[vimView textStorage] setLinespace:(float)linespace]; - shouldUpdateWindowSize = YES; + if (vimView && [vimView textView]) { + [[vimView textView] setLinespace:(float)linespace]; + shouldResizeVimView = YES; } } @@ -473,7 +514,7 @@ NSMutableArray *buildMenuAddress(NSMenu *menu) { // Save the original title, if we haven't already. if (lastSetTitle == nil) { - lastSetTitle = [[[self window] title] retain]; + lastSetTitle = [[decoratedWindow title] retain]; } } @@ -481,75 +522,74 @@ NSMutableArray *buildMenuAddress(NSMenu *menu) { if (!setupDone) return; - // NOTE: During live resize the window is not constrained to lie inside the - // screen (because we must not programmatically alter the window size - // during live resize or it will 'jitter'), so at the end of live resize we - // make sure a final SetTextDimensionsMsgID message is sent to ensure that - // resizeWindowToFit does get called. For this reason and also because we - // want to ensure that Vim and MacVim have consistent states, this resize - // message is sent synchronously. (If the states were inconsistent the - // text view may become too large or too small to fit the window.) - - NSSize contentSize = [self contentSize]; - - int desiredSize[2]; - [vimView getDesiredRows:&desiredSize[0] columns:&desiredSize[1] - forSize:contentSize]; - - NSData *data = [NSData dataWithBytes:desiredSize length:2*sizeof(int)]; - - BOOL resizeOk = [vimController sendMessageNow:SetTextDimensionsMsgID - data:data - timeout:.5]; - - if (!resizeOk) { - // Force the window size to match the text view size otherwise Vim and - // MacVim will have inconsistent states. - [self resizeWindowToFit:self]; + // NOTE: During live resize messages from MacVim to Vim are often dropped + // (because too many messages are sent at once). This may lead to + // inconsistent states between Vim and MacVim; to avoid this we send a + // synchronous resize message to Vim now (this is not fool-proof, but it + // does seem to work quite well). + + int constrained[2]; + NSSize textViewSize = [[vimView textView] frame].size; + [[vimView textView] constrainRows:&constrained[0] columns:&constrained[1] + toSize:textViewSize]; + + //NSLog(@"End of live resize, notify Vim that text dimensions are %dx%d", + // constrained[1], constrained[0]); + + NSData *data = [NSData dataWithBytes:constrained length:2*sizeof(int)]; + BOOL sendOk = [vimController sendMessageNow:SetTextDimensionsMsgID + data:data + timeout:.5]; + + if (!sendOk) { + // Sending of synchronous message failed. Force the window size to + // match the last dimensions received from Vim, otherwise we end up + // with inconsistent states. + [self resizeWindowToFitContentSize:[vimView desiredSize]]; } // If we saved the original title while resizing, restore it. if (lastSetTitle != nil) { - [[self window] setTitle:lastSetTitle]; + [decoratedWindow setTitle:lastSetTitle]; [lastSetTitle release]; lastSetTitle = nil; } } -- (void)placeViews -{ - if (!setupDone) return; - - NSRect vimViewRect; - vimViewRect.origin = NSMakePoint(0, 0); - vimViewRect.size = [vimView getDesiredRows:NULL columns:NULL - forSize:[self contentSize]]; - - // HACK! If the window does resize, then windowDidResize is called which in - // turn calls placeViews. In case the computed new size of the window is - // no different from the current size, then we need to call placeViews - // manually. - if (NSEqualRects(vimViewRect, [vimView frame])) { - [vimView placeViews]; - } else { - [vimView setFrame:vimViewRect]; - } -} - - (void)enterFullscreen { - fullscreenWindow = [[MMFullscreenWindow alloc] initWithWindow:[self window] - view:vimView]; + if (fullscreenEnabled) return; + + fullscreenWindow = [[MMFullscreenWindow alloc] + initWithWindow:decoratedWindow view:vimView]; [fullscreenWindow enterFullscreen]; - [fullscreenWindow setDelegate:self]; + fullscreenEnabled = YES; + + // The resize handle disappears so the vim view needs to update the + // scrollbars. + shouldResizeVimView = YES; } - (void)leaveFullscreen { + if (!fullscreenEnabled) return; + + fullscreenEnabled = NO; [fullscreenWindow leaveFullscreen]; [fullscreenWindow release]; fullscreenWindow = nil; + + // The vim view may be too large to fit the screen, so update it. + shouldResizeVimView = YES; +} + +- (void)setBuffersModified:(BOOL)mod +{ + // NOTE: We only set the document edited flag on the decorated window since + // the full-screen window has no close button anyway. (It also saves us + // from keeping track of the flag in two different places.) + [decoratedWindow setDocumentEdited:mod]; } @@ -571,10 +611,9 @@ NSMutableArray *buildMenuAddress(NSMenu *menu) { [vimController sendMessage:GotFocusMsgID data:nil]; - if ([vimView textStorage]) { - NSFontManager *fontManager = [NSFontManager sharedFontManager]; - [fontManager setSelectedFont:[[vimView textStorage] font] - isMultiple:NO]; + if ([vimView textView]) { + NSFontManager *fm = [NSFontManager sharedFontManager]; + [fm setSelectedFont:[[vimView textView] font] isMultiple:NO]; } } @@ -588,6 +627,8 @@ NSMutableArray *buildMenuAddress(NSMenu *menu) - (BOOL)windowShouldClose:(id)sender { + // Don't close the window now; Instead let Vim decide whether to close the + // window or not. [vimController sendMessage:VimShouldCloseMsgID data:nil]; return NO; } @@ -595,7 +636,7 @@ NSMutableArray *buildMenuAddress(NSMenu *menu) - (void)windowDidMove:(NSNotification *)notification { if (setupDone && windowAutosaveKey) { - NSRect frame = [[self window] frame]; + NSRect frame = [decoratedWindow frame]; NSPoint topLeft = { frame.origin.x, NSMaxY(frame) }; NSString *topLeftString = NSStringFromPoint(topLeft); @@ -606,47 +647,18 @@ NSMutableArray *buildMenuAddress(NSMenu *menu) - (void)windowDidResize:(id)sender { - if (!setupDone) return; + if (!setupDone || fullscreenEnabled) return; - // Live resizing works as follows: - // VimView's size is changed immediatly, and a resize message to the - // remote vim instance is sent. The remote vim instance sends a - // "vim content size changed" right back, but in live resize mode this - // doesn't change the VimView (because we assume that it already has the - // correct size because we set the resize increments correctly). Afterward, - // the remote vim view sends a batch draw for the text visible in the - // resized text area. - - NSSize contentSize = [self contentSize]; - [self resizeVimViewToFitSize:contentSize]; - - NSRect frame; - frame.origin = NSMakePoint(0, 0); - frame.size = contentSize; - [vimView setFrame:frame]; + // NOTE: Since we have no control over when the window may resize (Cocoa + // may resize automatically) we simply set the view to fill the entire + // window. The vim view takes care of notifying Vim if the number of + // (rows,columns) changed. + [vimView setFrameSize:[self contentSize]]; } - (NSRect)windowWillUseStandardFrame:(NSWindow *)win defaultFrame:(NSRect)frame { - // HACK! For some reason 'frame' is not always constrained to fit on the - // screen (e.g. it may overlap the menu bar), so first constrain it to the - // screen; otherwise the new frame we compute may be too large and this - // will mess up the display after the window resizes. - frame = [win constrainFrameRect:frame toScreen:[win screen]]; - - // HACK! If the top of 'frame' is lower than the current window frame, - // increase 'frame' so that their tops align. Really, 'frame' should - // already have its top at least as high as the current window frame, but - // for some reason this is not always the case. - // (See resizeWindowToFit: for a similar hack.) - NSRect cur = [win frame]; - if (NSMaxY(cur) > NSMaxY(frame)) { - frame.size.height = cur.origin.y - frame.origin.y + cur.size.height; - } - - frame = [self fitWindowToFrame:frame]; - // Keep old width and horizontal position unless user clicked while the // Command key is held down. NSEvent *event = [NSApp currentEvent]; @@ -690,105 +702,52 @@ NSMutableArray *buildMenuAddress(NSMenu *menu) @implementation MMWindowController (Private) -- (NSRect)contentRectForFrameRect:(NSRect)frame -{ - NSRect result = [[self window] contentRectForFrameRect:frame]; - if (![tablineSeparator isHidden]) - --result.size.height; - return result; -} - -- (NSRect)frameRectForContentRect:(NSRect)contentRect -{ - if (![tablineSeparator isHidden]) - ++contentRect.size.height; - return [[self window] frameRectForContentRect:contentRect]; -} - - (NSSize)contentSize { - return [self contentRectForFrameRect:[[self window] frame]].size; + // NOTE: Never query the content view directly for its size since it may + // not return the same size as contentRectForFrameRect: (e.g. when in + // windowed mode and the tabline separator is visible)! + NSWindow *win = [self window]; + return [win contentRectForFrameRect:[win frame]].size; } -- (void)resizeWindowToFit:(id)sender +- (void)resizeWindowToFitContentSize:(NSSize)contentSize { - // Makes the window large enough to contain the vim view, called after the - // vim view's size was changed. If the window had to become to big, the - // vim view is made smaller. - - // NOTE: Be very careful when you call this method! Do not call while - // processing command queue, instead set 'shouldUpdateWindowSize' to YES. - // The only other place it is currently called is when live resize ends. - // This is done to ensure that the text view and window sizes match up - // (they may become out of sync if a SetTextDimensionsMsgID message to the - // backend is dropped). - - if (!setupDone) return; - - // Get size of text view, adapt window size to it - NSWindow *win = [self window]; - NSRect frame = [win frame]; - NSRect contentRect = [self contentRectForFrameRect:frame]; - NSSize newSize = [vimView desiredSizeForActualRowsAndColumns]; + NSRect frame = [decoratedWindow frame]; + NSRect contentRect = [decoratedWindow contentRectForFrameRect:frame]; // Keep top-left corner of the window fixed when resizing. - contentRect.origin.y -= newSize.height - contentRect.size.height; - contentRect.size = newSize; - - frame = [self frameRectForContentRect:contentRect]; - NSRect maxFrame = [win constrainFrameRect:frame toScreen:[win screen]]; - - // HACK! Assuming the window frame cannot already be placed too high, - // adjust 'maxFrame' so that it at least as high up as the current frame. - // The reason for doing this is that constrainFrameRect:toScreen: does not - // always seem to utilize as much area as possible. - if (NSMaxY(frame) > NSMaxY(maxFrame)) { - maxFrame.size.height = frame.origin.y - maxFrame.origin.y - + frame.size.height; - } + contentRect.origin.y -= contentSize.height - contentRect.size.height; + contentRect.size = contentSize; - if (!NSEqualRects(maxFrame, frame)) { - // The new window frame is too big to fit on the screen, so fit the - // text storage to the biggest frame which will fit on the screen. - //NSLog(@"Proposed window frame does not fit on the screen!"); - frame = [self fitWindowToFrame:maxFrame]; - [self resizeVimViewToFitSize:[self contentRectForFrameRect:frame].size]; - } - - // NSLog(@"%s %@", _cmd, NSStringFromRect(frame)); - - // HACK! If the window does resize, then windowDidResize is called which in - // turn calls placeViews. In case the computed new size of the window is - // no different from the current size, then we need to call placeViews - // manually. - if (NSEqualRects(frame, [win frame])) { - [self placeViews]; - } else { - [win setFrame:frame display:YES]; - } + frame = [decoratedWindow frameRectForContentRect:contentRect]; + [decoratedWindow setFrame:frame display:YES]; } -- (NSRect)fitWindowToFrame:(NSRect)frame +- (NSSize)constrainContentSizeToScreenSize:(NSSize)contentSize { - if (!setupDone) return frame; - - NSRect contentRect = [self contentRectForFrameRect:frame]; - NSSize size = [vimView getDesiredRows:NULL columns:NULL - forSize:contentRect.size]; + NSWindow *win = [self window]; + NSRect rect = [win contentRectForFrameRect:[[win screen] visibleFrame]]; - // Keep top-left corner of 'frame' fixed. - contentRect.origin.y -= size.height - contentRect.size.height; - contentRect.size = size; + if (contentSize.height > rect.size.height) + contentSize.height = rect.size.height; + if (contentSize.width > rect.size.width) + contentSize.width = rect.size.width; - return [self frameRectForContentRect:contentRect]; + return contentSize; } -- (void)updateResizeIncrements +- (void)updateResizeConstraints { if (!setupDone) return; - NSSize size = [[vimView textStorage] cellSize]; - [[self window] setContentResizeIncrements:size]; + // Set the resize increments to exactly match the font size; this way the + // window will always hold an integer number of (rows,columns). + NSSize cellSize = [[vimView textView] cellSize]; + [decoratedWindow setContentResizeIncrements:cellSize]; + + NSSize minSize = [vimView minSize]; + [decoratedWindow setContentMinSize:minSize]; } - (NSTabViewItem *)addNewTabViewItem @@ -808,6 +767,7 @@ NSMutableArray *buildMenuAddress(NSMenu *menu) - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb { + // TODO: Can this be done with evaluateExpression: instead? BOOL reply = NO; id backendProxy = [vimController backendProxy]; @@ -823,40 +783,16 @@ NSMutableArray *buildMenuAddress(NSMenu *menu) return reply; } -- (void)checkWindowNeedsResizing -{ - shouldUpdateWindowSize = - shouldUpdateWindowSize || [vimView shouldUpdateWindowSize]; -} - -- (NSSize)resizeVimViewToFitSize:(NSSize)size +- (void)hideTablineSeparator:(BOOL)hide { - // If our optimal (rows,cols) do not match our current (rows,cols), resize - // ourselves and tell the Vim process to sync up. - int desired[2]; - NSSize newSize = [vimView getDesiredRows:&desired[0] columns:&desired[1] - forSize:size]; - - int rows, columns; - [vimView getActualRows:&rows columns:&columns]; - - if (desired[0] != rows || desired[1] != columns) { - // NSLog(@"Notify Vim that text storage dimensions changed from %dx%d " - // @"to %dx%d", columns, rows, desired[0], desired[1]); - NSData *data = [NSData dataWithBytes:desired length:2*sizeof(int)]; - - [vimController sendMessage:SetTextDimensionsMsgID data:data]; - - // We only want to set the window title if this resize came from - // a live-resize, not (for example) setting 'columns' or 'lines'. - if ([[self textView] inLiveResize]) { - [[self window] setTitle:[NSString stringWithFormat:@"%dx%d", - desired[1], desired[0]]]; - } + // The full-screen window has no tabline separator so we operate on + // decoratedWindow instead of [self window]. + if ([decoratedWindow hideTablineSeparator:hide]) { + // The tabline separator was toggled so the content view must change + // size. + [self updateResizeConstraints]; + shouldResizeVimView = YES; } - - return newSize; } - @end // MMWindowController (Private) diff --git a/src/MacVim/MacVim.h b/src/MacVim/MacVim.h index 3b3b5e24..55f92dba 100644 --- a/src/MacVim/MacVim.h +++ b/src/MacVim/MacVim.h @@ -159,6 +159,7 @@ enum { TerminateNowMsgID, ODBEditMsgID, XcodeModMsgID, + LiveResizeMsgID, }; diff --git a/src/MacVim/MacVim.m b/src/MacVim/MacVim.m index 36c1b355..d78bf433 100644 --- a/src/MacVim/MacVim.m +++ b/src/MacVim/MacVim.m @@ -72,6 +72,7 @@ char *MessageStrings[] = "TerminateNowMsgID", "ODBEditMsgID", "XcodeModMsgID", + "LiveResizeMsgID", }; diff --git a/src/MacVim/MacVim.xcodeproj/project.pbxproj b/src/MacVim/MacVim.xcodeproj/project.pbxproj index 211e55a9..9d55b367 100644 --- a/src/MacVim/MacVim.xcodeproj/project.pbxproj +++ b/src/MacVim/MacVim.xcodeproj/project.pbxproj @@ -45,6 +45,8 @@ 1DD9F5E50C85D60500E8D5A5 /* SystemColors.plist in Resources */ = {isa = PBXBuildFile; fileRef = 1DD9F5E40C85D60500E8D5A5 /* SystemColors.plist */; }; 1DE608B40C587FDA0055263D /* runtime in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1DE602470C587FD10055263D /* runtime */; }; 1DE8CC620C5E2AAD003F56E3 /* Actions.plist in Resources */ = {isa = PBXBuildFile; fileRef = 1DE8CC610C5E2AAD003F56E3 /* Actions.plist */; }; + 1DE9B94F0D341AB8008FEDD4 /* MMWindow.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1DE9B94D0D341AB8008FEDD4 /* MMWindow.h */; }; + 1DE9B9500D341AB8008FEDD4 /* MMWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 1DE9B94E0D341AB8008FEDD4 /* MMWindow.m */; }; 1DED78600C6DE43D0079945F /* vimrc in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1DED785F0C6DE43D0079945F /* vimrc */; }; 1DEE0D9F0C4E150B008E82B2 /* Attention.png in Resources */ = {isa = PBXBuildFile; fileRef = 1DEE0D8A0C4E150B008E82B2 /* Attention.png */; }; 1DEE0DA00C4E150B008E82B2 /* Copy.png in Resources */ = {isa = PBXBuildFile; fileRef = 1DEE0D8B0C4E150B008E82B2 /* Copy.png */; }; @@ -100,6 +102,7 @@ files = ( 1D493D580C5247BF00AB718C /* Vim in CopyFiles */, 1D9918480D299F9900A96335 /* MMAtsuiTextView.h in CopyFiles */, + 1DE9B94F0D341AB8008FEDD4 /* MMWindow.h in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -179,6 +182,8 @@ 1DD9F5E40C85D60500E8D5A5 /* SystemColors.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = SystemColors.plist; sourceTree = ""; }; 1DE602470C587FD10055263D /* runtime */ = {isa = PBXFileReference; lastKnownFileType = folder; name = runtime; path = ../../runtime; sourceTree = SOURCE_ROOT; }; 1DE8CC610C5E2AAD003F56E3 /* Actions.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = Actions.plist; sourceTree = ""; }; + 1DE9B94D0D341AB8008FEDD4 /* MMWindow.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MMWindow.h; sourceTree = ""; }; + 1DE9B94E0D341AB8008FEDD4 /* MMWindow.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = MMWindow.m; sourceTree = ""; }; 1DED785F0C6DE43D0079945F /* vimrc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = vimrc; sourceTree = ""; }; 1DEE0D8A0C4E150B008E82B2 /* Attention.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Attention.png; path = Toolbar/Attention.png; sourceTree = ""; }; 1DEE0D8B0C4E150B008E82B2 /* Copy.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Copy.png; path = Toolbar/Copy.png; sourceTree = ""; }; @@ -227,6 +232,8 @@ 080E96DDFE201D6D7F000001 /* MacVim Source */ = { isa = PBXGroup; children = ( + 1DE9B94D0D341AB8008FEDD4 /* MMWindow.h */, + 1DE9B94E0D341AB8008FEDD4 /* MMWindow.m */, 1D9918460D299F9900A96335 /* MMAtsuiTextView.h */, 1D9918470D299F9900A96335 /* MMAtsuiTextView.m */, 1D80FBCF0CBBD3B700102A1C /* MMFullscreenWindow.h */, @@ -528,6 +535,7 @@ 1D80FBD40CBBD3B700102A1C /* MMFullscreenWindow.m in Sources */, 1D80FBD60CBBD3B700102A1C /* MMVimView.m in Sources */, 1D9918490D299F9900A96335 /* MMAtsuiTextView.m in Sources */, + 1DE9B9500D341AB8008FEDD4 /* MMWindow.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/src/MacVim/gui_macvim.m b/src/MacVim/gui_macvim.m index cc86adac..3ad3dbdd 100644 --- a/src/MacVim/gui_macvim.m +++ b/src/MacVim/gui_macvim.m @@ -87,8 +87,11 @@ gui_mch_init(void) { //NSLog(@"gui_mch_init()"); - if (![[MMBackend sharedInstance] checkin]) + if (![[MMBackend sharedInstance] checkin]) { + // TODO: Kill the process if there is no terminal to fall back on, + // otherwise the process will run outputting to the console. return FAIL; + } // Force 'termencoding' to utf-8 (changes to 'tenc' are disallowed in // 'option.c', so that ':set termencoding=...' is impossible). -- 2.11.4.GIT