diff --git a/src/MacVim/MMAtsuiTextView.h b/src/MacVim/MMAtsuiTextView.h new file mode 100644 index 0000000000..abae6d859d --- /dev/null +++ b/src/MacVim/MMAtsuiTextView.h @@ -0,0 +1,77 @@ +/* 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 + + +enum { MMMaxCellsPerChar = 2 }; + + + +@interface MMAtsuiTextView : NSView { + // From MMTextStorage + int maxRows, maxColumns; + int actualRows, actualColumns; + NSColor *defaultBackgroundColor; + NSColor *defaultForegroundColor; + NSSize cellSize; + NSFont *font; + float linespace; + + // From vim-cocoa +} + +- (id)initWithFrame:(NSRect)frame; + +// +// MMTextStorage methods +// +- (void)getMaxRows:(int*)rows columns:(int*)cols; +- (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; + +- (void)setFont:(NSFont *)newFont; +- (void)setWideFont:(NSFont *)newFont; +- (NSFont *)font; +- (NSSize)cellSize; +- (void)setLinespace:(float)newLinespace; + +// +// MMTextView methods +// +- (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; + +// +// NSTextView methods +// +- (void)keyDown:(NSEvent *)event; +- (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; + +@end diff --git a/src/MacVim/MMAtsuiTextView.m b/src/MacVim/MMAtsuiTextView.m new file mode 100644 index 0000000000..c5e5d9d6d6 --- /dev/null +++ b/src/MacVim/MMAtsuiTextView.m @@ -0,0 +1,721 @@ +/* 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. + */ +/* + * MMAtsuiTextView + * + * Dispatches keyboard and mouse input to the backend. Handles drag-n-drop of + * files onto window. + */ + +#import "MMAtsuiTextView.h" +#import "MMVimController.h" +#import "MacVim.h" + + + +static char MMKeypadEnter[2] = { 'K', 'A' }; +static NSString *MMKeypadEnterString = @"KA"; + + + +@interface MMAtsuiTextView (Private) +- (void)dispatchKeyEvent:(NSEvent *)event; +- (void)sendKeyDown:(const char *)chars length:(int)len modifiers:(int)flags; +- (MMVimController *)vimController; +@end + +@interface MMAtsuiTextView (Drawing) +- (void)beginDrawing; +- (void)endDrawing; +- (void)drawString:(UniChar *)string length:(UniCharCount)length + atRow:(int)row column:(int)col cells:(int)cells + withFlags:(int)flags foregroundColor:(NSColor *)fg + backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp; +- (void)deleteLinesFromRow:(int)row lineCount:(int)count + scrollBottom:(int)bottom left:(int)left right:(int)right + color:(NSColor *)color; +- (void)insertLinesAtRow:(int)row lineCount:(int)count + scrollBottom:(int)bottom left:(int)left right:(int)right + color:(NSColor *)color; +- (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2 + column:(int)col2 color:(NSColor *)color; +- (void)clearAll; +@end + + +@implementation MMAtsuiTextView + +- (id)initWithFrame:(NSRect)frame +{ + if ((self = [super initWithFrame:frame])) { + // NOTE! It does not matter which font is set here, Vim will set its + // own font on startup anyway. Just set some bogus values. + font = [[NSFont userFixedPitchFontOfSize:0] retain]; + cellSize.width = cellSize.height = 1; + } + + return self; +} + +- (void)dealloc +{ + [font release]; font = nil; + [defaultBackgroundColor release]; defaultBackgroundColor = nil; + [defaultForegroundColor release]; defaultForegroundColor = nil; + + [super dealloc]; +} + +- (void)getMaxRows:(int*)rows columns:(int*)cols +{ + if (rows) *rows = maxRows; + if (cols) *cols = maxColumns; +} + +- (void)setMaxRows:(int)rows columns:(int)cols +{ + // NOTE: Just remember the new values, the actual resizing is done lazily. + maxRows = rows; + maxColumns = cols; +} + +- (void)setDefaultColorsBackground:(NSColor *)bgColor + foreground:(NSColor *)fgColor +{ + if (defaultBackgroundColor != bgColor) { + [defaultBackgroundColor release]; + defaultBackgroundColor = bgColor ? [bgColor retain] : nil; + } + + // NOTE: The default foreground color isn't actually used for anything, but + // other class instances might want to be able to access it so it is stored + // here. + if (defaultForegroundColor != fgColor) { + [defaultForegroundColor release]; + defaultForegroundColor = fgColor ? [fgColor retain] : nil; + } +} + +- (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 +{ + NSRect rect = { 0, 0, 0, 0 }; + unsigned start = range.location > maxRows ? maxRows : range.location; + unsigned length = range.length; + + if (start+length > maxRows) + length = maxRows - start; + + rect.origin.y = cellSize.height * start; + rect.size.height = cellSize.height * length; + + return rect; +} + +- (NSRect)rectForColumnsInRange:(NSRange)range +{ + NSRect rect = { 0, 0, 0, 0 }; + unsigned start = range.location > maxColumns ? maxColumns : range.location; + unsigned length = range.length; + + if (start+length > maxColumns) + length = maxColumns - start; + + rect.origin.x = cellSize.width * start; + rect.size.width = cellSize.width * length; + + return rect; +} + + +- (void)setFont:(NSFont *)newFont +{ + if (newFont && font != newFont) { + [font release]; + font = [newFont retain]; + + float em = [newFont widthOfString:@"m"]; + float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults] + floatForKey:MMCellWidthMultiplierKey]; + + // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will + // only render at integer sizes. Hence, we restrict the cell width to + // an integer here, otherwise the window width and the actual text + // width will not match. + cellSize.width = ceilf(em * cellWidthMultiplier); + cellSize.height = linespace + [newFont defaultLineHeightForFont]; + } +} + +- (void)setWideFont:(NSFont *)newFont +{ +} + +- (NSFont *)font +{ + return font; +} + +- (NSSize)cellSize +{ + return cellSize; +} + +- (void)setLinespace:(float)newLinespace +{ + linespace = newLinespace; + + // NOTE: The linespace is added to the cell height in order for a multiline + // selection not to have white (background color) gaps between lines. Also + // this simplifies the code a lot because there is no need to check the + // linespace when calculating the size of the text view etc. When the + // linespace is non-zero the baseline will be adjusted as well; check + // MMTypesetter. + cellSize.height = linespace + [font defaultLineHeightForFont]; +} + + + + +- (NSEvent *)lastMouseDownEvent +{ + return nil; +} + +- (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)keyDown:(NSEvent *)event +{ + //NSLog(@"%s %@", _cmd, event); + // HACK! If control modifier is held, don't pass the event along to + // interpretKeyEvents: since some keys are bound to multiple commands which + // means doCommandBySelector: is called several times. + // + // TODO: Figure out a way to disable Cocoa key bindings entirely, without + // affecting input management. + if ([event modifierFlags] & NSControlKeyMask) { + NSString *unmod = [event charactersIgnoringModifiers]; + if ([unmod length] == 1 && [unmod characterAtIndex:0] <= 0x7f + && [unmod characterAtIndex:0] >= 0x60) { + // HACK! Send Ctrl-letter keys (and C-@, C-[, C-\, C-], C-^, C-_) + // as normal text to be added to the Vim input buffer. This must + // be done in order for the backend to be able to separate e.g. + // Ctrl-i and Ctrl-tab. + [self insertText:[event characters]]; + } else { + [self dispatchKeyEvent:event]; + } + } else { + [super keyDown:event]; + } +} + +- (void)insertText:(id)string +{ + //NSLog(@"%s %@", _cmd, string); + // NOTE! This method is called for normal key presses but also for + // Option-key presses --- even when Ctrl is held as well as Option. When + // Ctrl is held, the AppKit translates the character to a Ctrl+key stroke, + // so 'string' need not be a printable character! In this case it still + // works to pass 'string' on to Vim as a printable character (since + // modifiers are already included and should not be added to the input + // buffer using CSI, K_MODIFIER). + + [self hideMarkedTextField]; + + NSEvent *event = [NSApp currentEvent]; + + // HACK! In order to be able to bind to , , etc. we have + // to watch for them here. + if ([event type] == NSKeyDown + && [[event charactersIgnoringModifiers] length] > 0 + && [event modifierFlags] + & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask)) { + unichar c = [[event charactersIgnoringModifiers] characterAtIndex:0]; + + // translates to 0x19 + if (' ' == c || 0x19 == c) { + [self dispatchKeyEvent:event]; + return; + } + } + + // TODO: Support 'mousehide' (check p_mh) + [NSCursor setHiddenUntilMouseMoves:YES]; + + // NOTE: 'string' is either an NSString or an NSAttributedString. Since we + // do not support attributes, simply pass the corresponding NSString in the + // latter case. + if ([string isKindOfClass:[NSAttributedString class]]) + string = [string string]; + + //NSLog(@"send InsertTextMsgID: %@", string); + + [[self vimController] sendMessage:InsertTextMsgID + data:[string dataUsingEncoding:NSUTF8StringEncoding]]; +} + +- (void)doCommandBySelector:(SEL)selector +{ + //NSLog(@"%s %@", _cmd, NSStringFromSelector(selector)); + // By ignoring the selector we effectively disable the key binding + // mechanism of Cocoa. Hopefully this is what the user will expect + // (pressing Ctrl+P would otherwise result in moveUp: instead of previous + // match, etc.). + // + // We usually end up here if the user pressed Ctrl+key (but not + // Ctrl+Option+key). + + NSEvent *event = [NSApp currentEvent]; + + if (selector == @selector(cancelOperation:) + || selector == @selector(insertNewline:)) { + // HACK! If there was marked text which got abandoned as a result of + // hitting escape or enter, then 'insertText:' is called with the + // abandoned text but '[event characters]' includes the abandoned text + // as well. Since 'dispatchKeyEvent:' looks at '[event characters]' we + // must intercept these keys here or the abandonded text gets inserted + // twice. + NSString *key = [event charactersIgnoringModifiers]; + const char *chars = [key UTF8String]; + int len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + + if (0x3 == chars[0]) { + // HACK! AppKit turns enter (not return) into Ctrl-C, so we need to + // handle it separately (else Ctrl-C doesn't work). + len = sizeof(MMKeypadEnter)/sizeof(MMKeypadEnter[0]); + chars = MMKeypadEnter; + } + + [self sendKeyDown:chars length:len modifiers:[event modifierFlags]]; + } else { + [self dispatchKeyEvent:event]; + } +} + +- (BOOL)performKeyEquivalent:(NSEvent *)event +{ + //NSLog(@"%s %@", _cmd, event); + // Called for Cmd+key keystrokes, function keys, arrow keys, page + // up/down, home, end. + // + // NOTE: This message cannot be ignored since Cmd+letter keys never are + // passed to keyDown:. It seems as if the main menu consumes Cmd-key + // strokes, unless the key is a function key. + + // NOTE: If the event that triggered this method represents a function key + // down then we do nothing, otherwise the input method never gets the key + // stroke (some input methods use e.g. arrow keys). The function key down + // event will still reach Vim though (via keyDown:). The exceptions to + // this rule are: PageUp/PageDown (keycode 116/121). + int flags = [event modifierFlags]; + if ([event type] != NSKeyDown || flags & NSFunctionKeyMask + && !(116 == [event keyCode] || 121 == [event keyCode])) + return NO; + + // HACK! Let the main menu try to handle any key down event, before + // passing it on to vim, otherwise key equivalents for menus will + // effectively be disabled. + if ([[NSApp mainMenu] performKeyEquivalent:event]) + return YES; + + // HACK! KeyCode 50 represent the key which switches between windows + // within an application (like Cmd+Tab is used to switch between + // applications). Return NO here, else the window switching does not work. + // + // Will this hack work for all languages / keyboard layouts? + if ([event keyCode] == 50) + return NO; + + // HACK! On Leopard Ctrl-key events end up here instead of keyDown:. + if (flags & NSControlKeyMask) { + [self keyDown:event]; + return YES; + } + + //NSLog(@"%s%@", _cmd, event); + + NSString *chars = [event characters]; + NSString *unmodchars = [event charactersIgnoringModifiers]; + int len = [unmodchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + NSMutableData *data = [NSMutableData data]; + + if (len <= 0) + return NO; + + // If 'chars' and 'unmodchars' differs when shift flag is present, then we + // can clear the shift flag as it is already included in 'unmodchars'. + // Failing to clear the shift flag means turns into (on + // an English keyboard). + if (flags & NSShiftKeyMask && ![chars isEqual:unmodchars]) + flags &= ~NSShiftKeyMask; + + if (0x3 == [unmodchars characterAtIndex:0]) { + // HACK! AppKit turns enter (not return) into Ctrl-C, so we need to + // handle it separately (else Cmd-enter turns into Ctrl-C). + unmodchars = MMKeypadEnterString; + len = [unmodchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + } + + [data appendBytes:&flags length:sizeof(int)]; + [data appendBytes:&len length:sizeof(int)]; + [data appendBytes:[unmodchars UTF8String] length:len]; + + [[self vimController] sendMessage:CmdKeyMsgID data:data]; + + return YES; +} + +- (NSPoint)textContainerOrigin +{ + return NSZeroPoint; +} + +- (void)setTextContainerInset:(NSSize)inset +{ +} + +- (void)setBackgroundColor:(NSColor *)color +{ +} + + + + +- (BOOL)acceptsFirstResponder +{ + return YES; +} + +- (BOOL)isFlipped +{ + return YES; +} + +- (void)drawRect:(NSRect)rect +{ + NSColor *color = defaultBackgroundColor ? defaultBackgroundColor + : [NSColor lightGrayColor]; + [color set]; + [NSBezierPath fillRect:rect]; +} + + + + +#define MM_DEBUG_DRAWING 0 + +- (void)performBatchDrawWithData:(NSData *)data +{ + const void *bytes = [data bytes]; + const void *end = bytes + [data length]; + +#if MM_DEBUG_DRAWING + NSLog(@"====> BEGIN %s", _cmd); +#endif + [self beginDrawing]; + + // 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 + [self 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 + [self 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 + [self 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); + UniChar *string = (UniChar*)bytes; 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); +#endif + [self drawString:string length:len atRow:row column:col + cells:cells withFlags:flags + foregroundColor:[NSColor colorWithRgbInt:fg] + backgroundColor:[NSColor colorWithArgbInt:bg] + specialColor:[NSColor colorWithRgbInt:sp]]; + } 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 + [self 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); + } + } + + [self endDrawing]; + + // 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 +} + +@end // MMAtsuiTextView + + + + +@implementation MMAtsuiTextView (Private) + +- (void)dispatchKeyEvent:(NSEvent *)event +{ + // Only handle the command if it came from a keyDown event + if ([event type] != NSKeyDown) + return; + + NSString *chars = [event characters]; + NSString *unmodchars = [event charactersIgnoringModifiers]; + unichar c = [chars characterAtIndex:0]; + unichar imc = [unmodchars characterAtIndex:0]; + int len = 0; + const char *bytes = 0; + int mods = [event modifierFlags]; + + //NSLog(@"%s chars[0]=0x%x unmodchars[0]=0x%x (chars=%@ unmodchars=%@)", + // _cmd, c, imc, chars, unmodchars); + + if (' ' == imc && 0xa0 != c) { + // HACK! The AppKit turns into which is not standard + // Vim behaviour, so bypass this problem. (0xa0 is , which + // should be passed on as is.) + len = [unmodchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + bytes = [unmodchars UTF8String]; + } else if (imc == c && '2' == c) { + // HACK! Translate Ctrl+2 to . + static char ctrl_at = 0; + len = 1; bytes = &ctrl_at; + } else if (imc == c && '6' == c) { + // HACK! Translate Ctrl+6 to . + static char ctrl_hat = 0x1e; + len = 1; bytes = &ctrl_hat; + } else if (c == 0x19 && imc == 0x19) { + // HACK! AppKit turns back tab into Ctrl-Y, so we need to handle it + // separately (else Ctrl-Y doesn't work). + static char tab = 0x9; + len = 1; bytes = &tab; mods |= NSShiftKeyMask; + } else { + len = [chars lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + bytes = [chars UTF8String]; + } + + [self sendKeyDown:bytes length:len modifiers:mods]; +} + +- (void)sendKeyDown:(const char *)chars length:(int)len modifiers:(int)flags +{ + if (chars && len > 0) { + NSMutableData *data = [NSMutableData data]; + + [data appendBytes:&flags length:sizeof(int)]; + [data appendBytes:&len length:sizeof(int)]; + [data appendBytes:chars length:len]; + + // TODO: Support 'mousehide' (check p_mh) + [NSCursor setHiddenUntilMouseMoves:YES]; + + //NSLog(@"%s len=%d chars=0x%x", _cmd, len, chars[0]); + [[self vimController] sendMessage:KeyDownMsgID data:data]; + } +} + +- (MMVimController *)vimController +{ + id windowController = [[self window] windowController]; + + // TODO: Make sure 'windowController' is a MMWindowController before type + // casting. + return [(MMWindowController*)windowController vimController]; +} + +@end // MMAtsuiTextView (Private) + + + + +@implementation MMAtsuiTextView (Drawing) + +- (void)beginDrawing +{ +} + +- (void)endDrawing +{ +} + +- (void)drawString:(UniChar *)string length:(UniCharCount)length + atRow:(int)row column:(int)col cells:(int)cells + withFlags:(int)flags foregroundColor:(NSColor *)fg + backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp +{ + // 'string' consists of 'length' utf-16 code pairs and should cover 'cells' + // display cells (a normal character takes up one display cell, a wide + // character takes up two) +} + +- (void)deleteLinesFromRow:(int)row lineCount:(int)count + scrollBottom:(int)bottom left:(int)left right:(int)right + color:(NSColor *)color +{ +} + +- (void)insertLinesAtRow:(int)row lineCount:(int)count + scrollBottom:(int)bottom left:(int)left right:(int)right + color:(NSColor *)color +{ +} + +- (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2 + column:(int)col2 color:(NSColor *)color +{ +} + +- (void)clearAll +{ +} + +@end // MMAtsuiTextView (Drawing) diff --git a/src/MacVim/MMVimController.m b/src/MacVim/MMVimController.m index 8f5a027c07..57ac6f45e0 100644 --- a/src/MacVim/MMVimController.m +++ b/src/MacVim/MMVimController.m @@ -28,6 +28,7 @@ #import "MMTextView.h" #import "MMAppController.h" #import "MMTextStorage.h" +#import "MMAtsuiTextView.h" // This is taken from gui.h @@ -89,14 +90,6 @@ static NSTimeInterval MMResendInterval = 0.5; -// TODO: Move to separate file -@interface NSColor (MMProtocol) -+ (NSColor *)colorWithRgbInt:(unsigned)rgb; -+ (NSColor *)colorWithArgbInt:(unsigned)argb; -@end - - - @implementation MMVimController @@ -561,7 +554,11 @@ static NSTimeInterval MMResendInterval = 0.5; if (OpenVimWindowMsgID == msgid) { [windowController openWindow]; } else if (BatchDrawMsgID == msgid) { - [self performBatchDrawWithData:data]; + if ([[NSUserDefaults standardUserDefaults] + boolForKey:MMAtsuiRendererKey]) + [[windowController textView] performBatchDrawWithData:data]; + else + [self performBatchDrawWithData:data]; } else if (SelectTabMsgID == msgid) { #if 0 // NOTE: Tab selection is done inside updateTabsWithData:. const void *bytes = [data bytes]; @@ -1376,31 +1373,6 @@ static NSTimeInterval MMResendInterval = 0.5; -@implementation NSColor (MMProtocol) - -+ (NSColor *)colorWithRgbInt:(unsigned)rgb -{ - float r = ((rgb>>16) & 0xff)/255.0f; - float g = ((rgb>>8) & 0xff)/255.0f; - float b = (rgb & 0xff)/255.0f; - - return [NSColor colorWithCalibratedRed:r green:g blue:b alpha:1.0f]; -} - -+ (NSColor *)colorWithArgbInt:(unsigned)argb -{ - float a = ((argb>>24) & 0xff)/255.0f; - float r = ((argb>>16) & 0xff)/255.0f; - float g = ((argb>>8) & 0xff)/255.0f; - float b = (argb & 0xff)/255.0f; - - return [NSColor colorWithCalibratedRed:r green:g blue:b alpha:a]; -} - -@end // NSColor (MMProtocol) - - - @implementation MMAlert - (void)dealloc { diff --git a/src/MacVim/MMVimView.m b/src/MacVim/MMVimView.m index ae8ff2450b..3a617dffdd 100644 --- a/src/MacVim/MMVimView.m +++ b/src/MacVim/MMVimView.m @@ -21,6 +21,7 @@ #import "MMTextStorage.h" #import "MMTypesetter.h" #import "MMVimController.h" +#import "MMAtsuiTextView.h" #import "MMWindowController.h" // needed by MMScroller. TODO: remove @@ -83,53 +84,64 @@ enum { vimController = controller; scrollbars = [[NSMutableArray alloc] init]; - // Set up a complete text system. - textStorage = [[MMTextStorage alloc] init]; - NSLayoutManager *lm = [[NSLayoutManager alloc] init]; - NSTextContainer *tc = [[NSTextContainer alloc] initWithContainerSize: - NSMakeSize(1.0e7,1.0e7)]; + if ([[NSUserDefaults standardUserDefaults] boolForKey:MMAtsuiRendererKey]) { + // Use ATSUI for text rendering. + textView = [[MMAtsuiTextView alloc] initWithFrame:frame]; - 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]; + // 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 { - // Only MMTypesetter supports different cell width multipliers. - [[NSUserDefaults standardUserDefaults] - setFloat:1.0 forKey:MMCellWidthMultiplierKey]; + // 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]; } - // 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]; - + // 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)]; [self addSubview:textView]; - - // The text storage retains the layout manager which in turn retains - // the text container. - [tc release]; - [lm release]; // Create the tab view (which is never visible, but the tab bar control // needs it to function). diff --git a/src/MacVim/MMWindowController.m b/src/MacVim/MMWindowController.m index 2812d7dfea..8071af9c01 100644 --- a/src/MacVim/MMWindowController.m +++ b/src/MacVim/MMWindowController.m @@ -24,6 +24,7 @@ #import "MMTypesetter.h" #import "MMFullscreenWindow.h" #import "MMVimView.h" +#import "MMAtsuiTextView.h" diff --git a/src/MacVim/MacVim.h b/src/MacVim/MacVim.h index f2d351ba1a..3b3b5e2490 100644 --- a/src/MacVim/MacVim.h +++ b/src/MacVim/MacVim.h @@ -215,6 +215,7 @@ extern NSString *MMTopLeftPointKey; extern NSString *MMOpenFilesInTabsKey; extern NSString *MMNoFontSubstitutionKey; extern NSString *MMLoginShellKey; +extern NSString *MMAtsuiRendererKey; @@ -240,6 +241,14 @@ ATSFontContainerRef loadFonts(); +@interface NSColor (MMExtras) ++ (NSColor *)colorWithRgbInt:(unsigned)rgb; ++ (NSColor *)colorWithArgbInt:(unsigned)argb; +@end + + + + // ODB Editor Suite Constants (taken from ODBEditorSuite.h) #define keyFileSender 'FSnd' #define keyFileSenderToken 'FTok' diff --git a/src/MacVim/MacVim.m b/src/MacVim/MacVim.m index 041ffee58d..36c1b3551a 100644 --- a/src/MacVim/MacVim.m +++ b/src/MacVim/MacVim.m @@ -96,6 +96,7 @@ NSString *MMTopLeftPointKey = @"MMTopLeftPoint"; NSString *MMOpenFilesInTabsKey = @"MMOpenFilesInTabs"; NSString *MMNoFontSubstitutionKey = @"MMNoFontSubstitution"; NSString *MMLoginShellKey = @"MMLoginShell"; +NSString *MMAtsuiRendererKey = @"MMAtsuiRenderer"; @@ -194,3 +195,30 @@ loadFonts() } @end // NSIndexSet (MMExtras) + + + + +@implementation NSColor (MMExtras) + ++ (NSColor *)colorWithRgbInt:(unsigned)rgb +{ + float r = ((rgb>>16) & 0xff)/255.0f; + float g = ((rgb>>8) & 0xff)/255.0f; + float b = (rgb & 0xff)/255.0f; + + return [NSColor colorWithCalibratedRed:r green:g blue:b alpha:1.0f]; +} + ++ (NSColor *)colorWithArgbInt:(unsigned)argb +{ + float a = ((argb>>24) & 0xff)/255.0f; + float r = ((argb>>16) & 0xff)/255.0f; + float g = ((argb>>8) & 0xff)/255.0f; + float b = (argb & 0xff)/255.0f; + + return [NSColor colorWithCalibratedRed:r green:g blue:b alpha:a]; +} + +@end // NSColor (MMExtras) + diff --git a/src/MacVim/MacVim.xcodeproj/project.pbxproj b/src/MacVim/MacVim.xcodeproj/project.pbxproj index e134dd6927..da1640dd93 100644 --- a/src/MacVim/MacVim.xcodeproj/project.pbxproj +++ b/src/MacVim/MacVim.xcodeproj/project.pbxproj @@ -35,6 +35,8 @@ 1D80FBD40CBBD3B700102A1C /* MMFullscreenWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D80FBD00CBBD3B700102A1C /* MMFullscreenWindow.m */; }; 1D80FBD60CBBD3B700102A1C /* MMVimView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D80FBD20CBBD3B700102A1C /* MMVimView.m */; }; 1D80FBE40CBBD6F200102A1C /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D80FBE30CBBD6F200102A1C /* Carbon.framework */; }; + 1D9918480D299F9900A96335 /* MMAtsuiTextView.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1D9918460D299F9900A96335 /* MMAtsuiTextView.h */; }; + 1D9918490D299F9900A96335 /* MMAtsuiTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D9918470D299F9900A96335 /* MMAtsuiTextView.m */; }; 1DD04DEC0C529C5E006CDC2B /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 1DD04DEB0C529C5E006CDC2B /* Credits.rtf */; }; 1DD0C20C0C60FFB4008CD84A /* gvimrc in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1DD0C20A0C60FF9A008CD84A /* gvimrc */; }; 1DD66ECE0C803D3600EBDAB3 /* MMApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 1DD66ECC0C803D3600EBDAB3 /* MMApplication.m */; }; @@ -97,6 +99,7 @@ dstSubfolderSpec = 6; files = ( 1D493D580C5247BF00AB718C /* Vim in CopyFiles */, + 1D9918480D299F9900A96335 /* MMAtsuiTextView.h in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -165,6 +168,8 @@ 1D80FBD10CBBD3B700102A1C /* MMVimView.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MMVimView.h; sourceTree = ""; }; 1D80FBD20CBBD3B700102A1C /* MMVimView.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = MMVimView.m; sourceTree = ""; }; 1D80FBE30CBBD6F200102A1C /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = /Developer/SDKs/MacOSX10.4u.sdk/System/Library/Frameworks/Carbon.framework; sourceTree = ""; }; + 1D9918460D299F9900A96335 /* MMAtsuiTextView.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MMAtsuiTextView.h; sourceTree = ""; }; + 1D9918470D299F9900A96335 /* MMAtsuiTextView.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = MMAtsuiTextView.m; sourceTree = ""; }; 1DD04DEB0C529C5E006CDC2B /* Credits.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = ""; }; 1DD0C20A0C60FF9A008CD84A /* gvimrc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = gvimrc; sourceTree = ""; }; 1DD66ECB0C803D3600EBDAB3 /* MMApplication.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MMApplication.h; sourceTree = ""; }; @@ -222,6 +227,8 @@ 080E96DDFE201D6D7F000001 /* MacVim Source */ = { isa = PBXGroup; children = ( + 1D9918460D299F9900A96335 /* MMAtsuiTextView.h */, + 1D9918470D299F9900A96335 /* MMAtsuiTextView.m */, 1D80FBCF0CBBD3B700102A1C /* MMFullscreenWindow.h */, 1D80FBD00CBBD3B700102A1C /* MMFullscreenWindow.m */, 1D80FBD10CBBD3B700102A1C /* MMVimView.h */, @@ -518,6 +525,7 @@ 1DD66ECE0C803D3600EBDAB3 /* MMApplication.m in Sources */, 1D80FBD40CBBD3B700102A1C /* MMFullscreenWindow.m in Sources */, 1D80FBD60CBBD3B700102A1C /* MMVimView.m in Sources */, + 1D9918490D299F9900A96335 /* MMAtsuiTextView.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };