1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
3 * VIM - Vi IMproved by Bram Moolenaar
4 * MacVim GUI port by Bjorn Winckler
6 * Do ":help uganda" in Vim to read copying and usage conditions.
7 * Do ":help credits" in Vim to see a list of people who contributed.
8 * See README.txt for an overview of the Vim source code.
13 * Dispatches keyboard and mouse input to the backend. Handles drag-n-drop of
14 * files onto window. The rendering is done using ATSUI.
16 * The text view area consists of two parts:
17 * 1. The text area - this is where text is rendered; the size is governed by
18 * the current number of rows and columns.
19 * 2. The inset area - this is a border around the text area; the size is
20 * governed by the user defaults MMTextInset[Left|Right|Top|Bottom].
22 * The current size of the text view frame does not always match the desired
23 * area, i.e. the area determined by the number of rows, columns plus text
24 * inset. This distinction is particularly important when the view is being
28 #import "MMAtsuiTextView.h"
29 #import "MMVimController.h"
33 // TODO: What does DRAW_TRANSP flag do? If the background isn't drawn when
34 // this flag is set, then sometimes the character after the cursor becomes
35 // blank. Everything seems to work fine by just ignoring this flag.
36 #define DRAW_TRANSP 0x01 /* draw with transparant bg */
37 #define DRAW_BOLD 0x02 /* draw bold text */
38 #define DRAW_UNDERL 0x04 /* draw underline text */
39 #define DRAW_UNDERC 0x08 /* draw undercurl text */
40 #define DRAW_ITALIC 0x10 /* draw italic text */
41 #define DRAW_CURSOR 0x20
44 static char MMKeypadEnter[2] = { 'K', 'A' };
45 static NSString *MMKeypadEnterString = @"KA";
48 // These values are chosen so that the min size is not too small with the
49 // default font (they only affect resizing with the mouse, you can still
50 // use e.g. ":set lines=2" to go below these values).
56 @interface NSFont (AppKitPrivate)
57 - (ATSUFontID) _atsFontID;
61 @interface MMAtsuiTextView (Private)
62 - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column;
63 - (void)initAtsuStyles;
64 - (void)disposeAtsuStyles;
65 - (void)updateAtsuStyles;
66 - (void)dispatchKeyEvent:(NSEvent *)event;
67 - (void)sendKeyDown:(const char *)chars length:(int)len modifiers:(int)flags;
68 - (MMVimController *)vimController;
72 @interface MMAtsuiTextView (Drawing)
73 - (NSPoint)originForRow:(int)row column:(int)column;
74 - (NSRect)rectFromRow:(int)row1 column:(int)col1
75 toRow:(int)row2 column:(int)col2;
76 - (NSSize)textAreaSize;
77 - (void)resizeContentImage;
80 - (void)drawString:(UniChar *)string length:(UniCharCount)length
81 atRow:(int)row column:(int)col cells:(int)cells
82 withFlags:(int)flags foregroundColor:(NSColor *)fg
83 backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp;
84 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
85 scrollBottom:(int)bottom left:(int)left right:(int)right
86 color:(NSColor *)color;
87 - (void)insertLinesAtRow:(int)row lineCount:(int)count
88 scrollBottom:(int)bottom left:(int)left right:(int)right
89 color:(NSColor *)color;
90 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
91 column:(int)col2 color:(NSColor *)color;
93 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
94 fraction:(int)percent color:(NSColor *)color;
98 @implementation MMAtsuiTextView
100 - (id)initWithFrame:(NSRect)frame
102 if ((self = [super initWithFrame:frame])) {
103 // NOTE! It does not matter which font is set here, Vim will set its
104 // own font on startup anyway. Just set some bogus values.
105 font = [[NSFont userFixedPitchFontOfSize:0] retain];
106 cellSize.width = cellSize.height = 1;
108 imageSize = NSZeroSize;
109 insetSize = NSZeroSize;
111 [self initAtsuStyles];
119 [self disposeAtsuStyles];
120 [font release]; font = nil;
121 [defaultBackgroundColor release]; defaultBackgroundColor = nil;
122 [defaultForegroundColor release]; defaultForegroundColor = nil;
127 - (void)getMaxRows:(int*)rows columns:(int*)cols
129 if (rows) *rows = maxRows;
130 if (cols) *cols = maxColumns;
133 - (void)setMaxRows:(int)rows columns:(int)cols
135 // NOTE: Just remember the new values, the actual resizing is done lazily.
140 - (void)setDefaultColorsBackground:(NSColor *)bgColor
141 foreground:(NSColor *)fgColor
143 if (defaultBackgroundColor != bgColor) {
144 [defaultBackgroundColor release];
145 defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
148 // NOTE: The default foreground color isn't actually used for anything, but
149 // other class instances might want to be able to access it so it is stored
151 if (defaultForegroundColor != fgColor) {
152 [defaultForegroundColor release];
153 defaultForegroundColor = fgColor ? [fgColor retain] : nil;
157 - (void)setTextContainerInset:(NSSize)size
162 - (NSRect)rectForRowsInRange:(NSRange)range
164 NSRect rect = { 0, 0, 0, 0 };
165 unsigned start = range.location > maxRows ? maxRows : range.location;
166 unsigned length = range.length;
168 if (start + length > maxRows)
169 length = maxRows - start;
171 rect.origin.y = cellSize.height * start + insetSize.height;
172 rect.size.height = cellSize.height * length;
177 - (NSRect)rectForColumnsInRange:(NSRange)range
179 NSRect rect = { 0, 0, 0, 0 };
180 unsigned start = range.location > maxColumns ? maxColumns : range.location;
181 unsigned length = range.length;
183 if (start+length > maxColumns)
184 length = maxColumns - start;
186 rect.origin.x = cellSize.width * start + insetSize.width;
187 rect.size.width = cellSize.width * length;
193 - (void)setFont:(NSFont *)newFont
195 if (newFont && font != newFont) {
197 font = [newFont retain];
199 float em = [newFont widthOfString:@"m"];
200 float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
201 floatForKey:MMCellWidthMultiplierKey];
203 // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
204 // only render at integer sizes. Hence, we restrict the cell width to
205 // an integer here, otherwise the window width and the actual text
206 // width will not match.
207 cellSize.width = ceilf(em * cellWidthMultiplier);
208 cellSize.height = linespace + [newFont defaultLineHeightForFont];
210 [self updateAtsuStyles];
214 - (void)setWideFont:(NSFont *)newFont
228 - (void)setLinespace:(float)newLinespace
230 linespace = newLinespace;
232 // NOTE: The linespace is added to the cell height in order for a multiline
233 // selection not to have white (background color) gaps between lines. Also
234 // this simplifies the code a lot because there is no need to check the
235 // linespace when calculating the size of the text view etc. When the
236 // linespace is non-zero the baseline will be adjusted as well; check
238 cellSize.height = linespace + [font defaultLineHeightForFont];
244 - (NSEvent *)lastMouseDownEvent
249 - (void)setShouldDrawInsertionPoint:(BOOL)on
253 - (void)setPreEditRow:(int)row column:(int)col
257 - (void)hideMarkedTextField
264 - (void)keyDown:(NSEvent *)event
266 //NSLog(@"%s %@", _cmd, event);
267 // HACK! If control modifier is held, don't pass the event along to
268 // interpretKeyEvents: since some keys are bound to multiple commands which
269 // means doCommandBySelector: is called several times. Do the same for
270 // Alt+Function key presses (Alt+Up and Alt+Down are bound to two
271 // commands). This hack may break input management, but unless we can
272 // figure out a way to disable key bindings there seems little else to do.
274 // TODO: Figure out a way to disable Cocoa key bindings entirely, without
275 // affecting input management.
276 int flags = [event modifierFlags];
277 if ((flags & NSControlKeyMask) ||
278 ((flags & NSAlternateKeyMask) && (flags & NSFunctionKeyMask))) {
279 NSString *unmod = [event charactersIgnoringModifiers];
280 if ([unmod length] == 1 && [unmod characterAtIndex:0] <= 0x7f
281 && [unmod characterAtIndex:0] >= 0x60) {
282 // HACK! Send Ctrl-letter keys (and C-@, C-[, C-\, C-], C-^, C-_)
283 // as normal text to be added to the Vim input buffer. This must
284 // be done in order for the backend to be able to separate e.g.
285 // Ctrl-i and Ctrl-tab.
286 [self insertText:[event characters]];
288 [self dispatchKeyEvent:event];
291 [self interpretKeyEvents:[NSArray arrayWithObject:event]];
295 - (void)insertText:(id)string
297 //NSLog(@"%s %@", _cmd, string);
298 // NOTE! This method is called for normal key presses but also for
299 // Option-key presses --- even when Ctrl is held as well as Option. When
300 // Ctrl is held, the AppKit translates the character to a Ctrl+key stroke,
301 // so 'string' need not be a printable character! In this case it still
302 // works to pass 'string' on to Vim as a printable character (since
303 // modifiers are already included and should not be added to the input
304 // buffer using CSI, K_MODIFIER).
306 [self hideMarkedTextField];
308 NSEvent *event = [NSApp currentEvent];
310 // HACK! In order to be able to bind to <S-Space>, <S-M-Tab>, etc. we have
311 // to watch for them here.
312 if ([event type] == NSKeyDown
313 && [[event charactersIgnoringModifiers] length] > 0
314 && [event modifierFlags]
315 & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask)) {
316 unichar c = [[event charactersIgnoringModifiers] characterAtIndex:0];
318 // <S-M-Tab> translates to 0x19
319 if (' ' == c || 0x19 == c) {
320 [self dispatchKeyEvent:event];
325 // TODO: Support 'mousehide' (check p_mh)
326 [NSCursor setHiddenUntilMouseMoves:YES];
328 // NOTE: 'string' is either an NSString or an NSAttributedString. Since we
329 // do not support attributes, simply pass the corresponding NSString in the
331 if ([string isKindOfClass:[NSAttributedString class]])
332 string = [string string];
334 //NSLog(@"send InsertTextMsgID: %@", string);
336 [[self vimController] sendMessage:InsertTextMsgID
337 data:[string dataUsingEncoding:NSUTF8StringEncoding]];
340 - (void)doCommandBySelector:(SEL)selector
342 //NSLog(@"%s %@", _cmd, NSStringFromSelector(selector));
343 // By ignoring the selector we effectively disable the key binding
344 // mechanism of Cocoa. Hopefully this is what the user will expect
345 // (pressing Ctrl+P would otherwise result in moveUp: instead of previous
348 // We usually end up here if the user pressed Ctrl+key (but not
351 NSEvent *event = [NSApp currentEvent];
353 if (selector == @selector(cancelOperation:)
354 || selector == @selector(insertNewline:)) {
355 // HACK! If there was marked text which got abandoned as a result of
356 // hitting escape or enter, then 'insertText:' is called with the
357 // abandoned text but '[event characters]' includes the abandoned text
358 // as well. Since 'dispatchKeyEvent:' looks at '[event characters]' we
359 // must intercept these keys here or the abandonded text gets inserted
361 NSString *key = [event charactersIgnoringModifiers];
362 const char *chars = [key UTF8String];
363 int len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
365 if (0x3 == chars[0]) {
366 // HACK! AppKit turns enter (not return) into Ctrl-C, so we need to
367 // handle it separately (else Ctrl-C doesn't work).
368 len = sizeof(MMKeypadEnter)/sizeof(MMKeypadEnter[0]);
369 chars = MMKeypadEnter;
372 [self sendKeyDown:chars length:len modifiers:[event modifierFlags]];
374 [self dispatchKeyEvent:event];
378 - (BOOL)performKeyEquivalent:(NSEvent *)event
380 //NSLog(@"%s %@", _cmd, event);
381 // Called for Cmd+key keystrokes, function keys, arrow keys, page
382 // up/down, home, end.
384 // NOTE: This message cannot be ignored since Cmd+letter keys never are
385 // passed to keyDown:. It seems as if the main menu consumes Cmd-key
386 // strokes, unless the key is a function key.
388 // NOTE: If the event that triggered this method represents a function key
389 // down then we do nothing, otherwise the input method never gets the key
390 // stroke (some input methods use e.g. arrow keys). The function key down
391 // event will still reach Vim though (via keyDown:). The exceptions to
392 // this rule are: PageUp/PageDown (keycode 116/121).
393 int flags = [event modifierFlags];
394 if ([event type] != NSKeyDown || flags & NSFunctionKeyMask
395 && !(116 == [event keyCode] || 121 == [event keyCode]))
398 // HACK! Let the main menu try to handle any key down event, before
399 // passing it on to vim, otherwise key equivalents for menus will
400 // effectively be disabled.
401 if ([[NSApp mainMenu] performKeyEquivalent:event])
404 // HACK! KeyCode 50 represent the key which switches between windows
405 // within an application (like Cmd+Tab is used to switch between
406 // applications). Return NO here, else the window switching does not work.
408 // Will this hack work for all languages / keyboard layouts?
409 if ([event keyCode] == 50)
412 // HACK! On Leopard Ctrl-key events end up here instead of keyDown:.
413 if (flags & NSControlKeyMask) {
414 [self keyDown:event];
418 //NSLog(@"%s%@", _cmd, event);
420 NSString *chars = [event characters];
421 NSString *unmodchars = [event charactersIgnoringModifiers];
422 int len = [unmodchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
423 NSMutableData *data = [NSMutableData data];
428 // If 'chars' and 'unmodchars' differs when shift flag is present, then we
429 // can clear the shift flag as it is already included in 'unmodchars'.
430 // Failing to clear the shift flag means <D-Bar> turns into <S-D-Bar> (on
431 // an English keyboard).
432 if (flags & NSShiftKeyMask && ![chars isEqual:unmodchars])
433 flags &= ~NSShiftKeyMask;
435 if (0x3 == [unmodchars characterAtIndex:0]) {
436 // HACK! AppKit turns enter (not return) into Ctrl-C, so we need to
437 // handle it separately (else Cmd-enter turns into Ctrl-C).
438 unmodchars = MMKeypadEnterString;
439 len = [unmodchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
442 [data appendBytes:&flags length:sizeof(int)];
443 [data appendBytes:&len length:sizeof(int)];
444 [data appendBytes:[unmodchars UTF8String] length:len];
446 [[self vimController] sendMessage:CmdKeyMsgID data:data];
454 - (BOOL)acceptsFirstResponder
464 - (void)drawRect:(NSRect)rect
466 NSRect srcRect = NSMakeRect(0, 0, imageSize.width, imageSize.height);
467 NSRect dstRect = srcRect;
469 dstRect.origin.x += insetSize.width;
470 dstRect.origin.y += insetSize.height;
472 [contentImage drawInRect: dstRect
474 operation: NSCompositeCopy
478 - (BOOL) wantsDefaultClipping
484 #define MM_DEBUG_DRAWING 0
486 - (void)performBatchDrawWithData:(NSData *)data
488 const void *bytes = [data bytes];
489 const void *end = bytes + [data length];
491 if (! NSEqualSizes(imageSize, [self textAreaSize]))
492 [self resizeContentImage];
495 NSLog(@"====> BEGIN %s", _cmd);
499 // TODO: Sanity check input
501 while (bytes < end) {
502 int type = *((int*)bytes); bytes += sizeof(int);
504 if (ClearAllDrawType == type) {
506 NSLog(@" Clear all");
509 } else if (ClearBlockDrawType == type) {
510 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
511 int row1 = *((int*)bytes); bytes += sizeof(int);
512 int col1 = *((int*)bytes); bytes += sizeof(int);
513 int row2 = *((int*)bytes); bytes += sizeof(int);
514 int col2 = *((int*)bytes); bytes += sizeof(int);
517 NSLog(@" Clear block (%d,%d) -> (%d,%d)", row1, col1,
520 [self clearBlockFromRow:row1 column:col1
521 toRow:row2 column:col2
522 color:[NSColor colorWithArgbInt:color]];
523 } else if (DeleteLinesDrawType == type) {
524 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
525 int row = *((int*)bytes); bytes += sizeof(int);
526 int count = *((int*)bytes); bytes += sizeof(int);
527 int bot = *((int*)bytes); bytes += sizeof(int);
528 int left = *((int*)bytes); bytes += sizeof(int);
529 int right = *((int*)bytes); bytes += sizeof(int);
532 NSLog(@" Delete %d line(s) from %d", count, row);
534 [self deleteLinesFromRow:row lineCount:count
535 scrollBottom:bot left:left right:right
536 color:[NSColor colorWithArgbInt:color]];
537 } else if (DrawStringDrawType == type) {
538 int bg = *((int*)bytes); bytes += sizeof(int);
539 int fg = *((int*)bytes); bytes += sizeof(int);
540 int sp = *((int*)bytes); bytes += sizeof(int);
541 int row = *((int*)bytes); bytes += sizeof(int);
542 int col = *((int*)bytes); bytes += sizeof(int);
543 int cells = *((int*)bytes); bytes += sizeof(int);
544 int flags = *((int*)bytes); bytes += sizeof(int);
545 int len = *((int*)bytes); bytes += sizeof(int);
546 // UniChar *string = (UniChar*)bytes; bytes += len;
547 NSString *string = [[NSString alloc] initWithBytesNoCopy:(void*)bytes
549 encoding:NSUTF8StringEncoding
553 NSLog(@" Draw string at (%d,%d) length=%d flags=%d fg=0x%x "
554 "bg=0x%x sp=0x%x", row, col, len, flags, fg, bg, sp);
556 unichar *characters = malloc(sizeof(unichar) * [string length]);
557 [string getCharacters:characters];
559 [self drawString:characters length:[string length] atRow:row column:col
560 cells:cells withFlags:flags
561 foregroundColor:[NSColor colorWithRgbInt:fg]
562 backgroundColor:[NSColor colorWithArgbInt:bg]
563 specialColor:[NSColor colorWithRgbInt:sp]];
566 } else if (InsertLinesDrawType == type) {
567 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
568 int row = *((int*)bytes); bytes += sizeof(int);
569 int count = *((int*)bytes); bytes += sizeof(int);
570 int bot = *((int*)bytes); bytes += sizeof(int);
571 int left = *((int*)bytes); bytes += sizeof(int);
572 int right = *((int*)bytes); bytes += sizeof(int);
575 NSLog(@" Insert %d line(s) at row %d", count, row);
577 [self insertLinesAtRow:row lineCount:count
578 scrollBottom:bot left:left right:right
579 color:[NSColor colorWithArgbInt:color]];
580 } else if (DrawCursorDrawType == type) {
581 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
582 int row = *((int*)bytes); bytes += sizeof(int);
583 int col = *((int*)bytes); bytes += sizeof(int);
584 int shape = *((int*)bytes); bytes += sizeof(int);
585 int percent = *((int*)bytes); bytes += sizeof(int);
588 NSLog(@" Draw cursor at (%d,%d)", row, col);
590 [self drawInsertionPointAtRow:row column:col shape:shape
592 color:[NSColor colorWithRgbInt:color]];
593 } else if (SetCursorPosDrawType == type) {
594 // TODO: This is used for Voice Over support in MMTextView,
595 // MMAtsuiTextView currently does not support Voice Over.
596 /*cursorRow = *((int*)bytes);*/ bytes += sizeof(int);
597 /*cursorCol = *((int*)bytes);*/ bytes += sizeof(int);
599 NSLog(@"WARNING: Unknown draw type (type=%d)", type);
605 // NOTE: During resizing, Cocoa only sends draw messages before Vim's rows
606 // and columns are changed (due to ipc delays). Force a redraw here.
607 [self setNeedsDisplay:YES];
608 // [self displayIfNeeded];
611 NSLog(@"<==== END %s", _cmd);
615 - (NSSize)constrainRows:(int *)rows columns:(int *)cols toSize:(NSSize)size
618 // - Rounding errors may cause size change when there should be none
619 // - Desired rows/columns shold not be 'too small'
621 // Constrain the desired size to the given size. Values for the minimum
622 // rows and columns is taken from Vim.
623 NSSize desiredSize = [self desiredSize];
624 int desiredRows = maxRows;
625 int desiredCols = maxColumns;
627 if (size.height != desiredSize.height) {
628 float fh = cellSize.height;
629 float ih = 2 * insetSize.height;
630 if (fh < 1.0f) fh = 1.0f;
632 desiredRows = floor((size.height - ih)/fh);
633 desiredSize.height = fh*desiredRows + ih;
636 if (size.width != desiredSize.width) {
637 float fw = cellSize.width;
638 float iw = 2 * insetSize.width;
639 if (fw < 1.0f) fw = 1.0f;
641 desiredCols = floor((size.width - iw)/fw);
642 desiredSize.width = fw*desiredCols + iw;
645 if (rows) *rows = desiredRows;
646 if (cols) *cols = desiredCols;
651 - (NSSize)desiredSize
653 // Compute the size the text view should be for the entire text area and
654 // inset area to be visible with the present number of rows and columns.
655 return NSMakeSize(maxColumns * cellSize.width + 2 * insetSize.width,
656 maxRows * cellSize.height + 2 * insetSize.height);
661 // Compute the smallest size the text view is allowed to be.
662 return NSMakeSize(MMMinColumns * cellSize.width + 2 * insetSize.width,
663 MMMinRows * cellSize.height + 2 * insetSize.height);
666 - (void)changeFont:(id)sender
668 NSFont *newFont = [sender convertFont:font];
671 NSString *name = [newFont displayName];
672 unsigned len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
674 NSMutableData *data = [NSMutableData data];
675 float pointSize = [newFont pointSize];
677 [data appendBytes:&pointSize length:sizeof(float)];
679 ++len; // include NUL byte
680 [data appendBytes:&len length:sizeof(unsigned)];
681 [data appendBytes:[name UTF8String] length:len];
683 [[self vimController] sendMessage:SetFontMsgID data:data];
688 - (void)scrollWheel:(NSEvent *)event
690 if ([event deltaY] == 0)
694 NSPoint pt = [self convertPoint:[event locationInWindow] fromView:nil];
696 // View is not flipped, instead the atsui code draws to a flipped image;
697 // thus we need to 'flip' the coordinate here since the column number
698 // increases in an up-to-down order.
699 pt.y = [self frame].size.height - pt.y;
701 if (![self convertPoint:pt toRow:&row column:&col])
704 int flags = [event modifierFlags];
705 float dy = [event deltaY];
706 NSMutableData *data = [NSMutableData data];
708 [data appendBytes:&row length:sizeof(int)];
709 [data appendBytes:&col length:sizeof(int)];
710 [data appendBytes:&flags length:sizeof(int)];
711 [data appendBytes:&dy length:sizeof(float)];
713 [[self vimController] sendMessage:ScrollWheelMsgID data:data];
716 @end // MMAtsuiTextView
721 @implementation MMAtsuiTextView (Private)
723 - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column
725 NSPoint origin = { insetSize.width, insetSize.height };
727 if (!(cellSize.width > 0 && cellSize.height > 0))
730 if (row) *row = floor((point.y-origin.y-1) / cellSize.height);
731 if (column) *column = floor((point.x-origin.x-1) / cellSize.width);
733 //NSLog(@"convertPoint:%@ toRow:%d column:%d", NSStringFromPoint(point),
739 - (void)initAtsuStyles
742 for (i = 0; i < MMMaxCellsPerChar; i++)
743 ATSUCreateStyle(&atsuStyles[i]);
746 - (void)disposeAtsuStyles
750 for (i = 0; i < MMMaxCellsPerChar; i++)
751 if (atsuStyles[i] != NULL)
753 if (ATSUDisposeStyle(atsuStyles[i]) != noErr)
754 atsuStyles[i] = NULL;
758 - (void)updateAtsuStyles
764 CGAffineTransform transform = CGAffineTransformMakeScale(1, -1);
765 ATSStyleRenderingOptions options;
767 fontID = [font _atsFontID];
768 fontSize = Long2Fix([font pointSize]);
769 options = kATSStyleApplyAntiAliasing;
771 ATSUAttributeTag attribTags[] =
773 kATSUFontTag, kATSUSizeTag, kATSUImposeWidthTag,
774 kATSUFontMatrixTag, kATSUStyleRenderingOptionsTag,
775 kATSUMaxATSUITagValue + 1
778 ByteCount attribSizes[] =
780 sizeof(ATSUFontID), sizeof(Fixed), sizeof(fontWidth),
781 sizeof(CGAffineTransform), sizeof(ATSStyleRenderingOptions),
785 ATSUAttributeValuePtr attribValues[] =
787 &fontID, &fontSize, &fontWidth, &transform, &options, &font
790 for (i = 0; i < MMMaxCellsPerChar; i++)
792 fontWidth = Long2Fix(cellSize.width * (i + 1));
794 if (ATSUSetAttributes(atsuStyles[i],
795 (sizeof attribTags) / sizeof(ATSUAttributeTag),
796 attribTags, attribSizes, attribValues) != noErr)
798 ATSUDisposeStyle(atsuStyles[i]);
799 atsuStyles[i] = NULL;
804 - (void)dispatchKeyEvent:(NSEvent *)event
806 // Only handle the command if it came from a keyDown event
807 if ([event type] != NSKeyDown)
810 NSString *chars = [event characters];
811 NSString *unmodchars = [event charactersIgnoringModifiers];
812 unichar c = [chars characterAtIndex:0];
813 unichar imc = [unmodchars characterAtIndex:0];
815 const char *bytes = 0;
816 int mods = [event modifierFlags];
818 //NSLog(@"%s chars[0]=0x%x unmodchars[0]=0x%x (chars=%@ unmodchars=%@)",
819 // _cmd, c, imc, chars, unmodchars);
821 if (' ' == imc && 0xa0 != c) {
822 // HACK! The AppKit turns <C-Space> into <C-@> which is not standard
823 // Vim behaviour, so bypass this problem. (0xa0 is <M-Space>, which
824 // should be passed on as is.)
825 len = [unmodchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
826 bytes = [unmodchars UTF8String];
827 } else if (imc == c && '2' == c) {
828 // HACK! Translate Ctrl+2 to <C-@>.
829 static char ctrl_at = 0;
830 len = 1; bytes = &ctrl_at;
831 } else if (imc == c && '6' == c) {
832 // HACK! Translate Ctrl+6 to <C-^>.
833 static char ctrl_hat = 0x1e;
834 len = 1; bytes = &ctrl_hat;
835 } else if (c == 0x19 && imc == 0x19) {
836 // HACK! AppKit turns back tab into Ctrl-Y, so we need to handle it
837 // separately (else Ctrl-Y doesn't work).
838 static char tab = 0x9;
839 len = 1; bytes = &tab; mods |= NSShiftKeyMask;
841 len = [chars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
842 bytes = [chars UTF8String];
845 [self sendKeyDown:bytes length:len modifiers:mods];
848 - (void)sendKeyDown:(const char *)chars length:(int)len modifiers:(int)flags
850 if (chars && len > 0) {
851 NSMutableData *data = [NSMutableData data];
853 [data appendBytes:&flags length:sizeof(int)];
854 [data appendBytes:&len length:sizeof(int)];
855 [data appendBytes:chars length:len];
857 // TODO: Support 'mousehide' (check p_mh)
858 [NSCursor setHiddenUntilMouseMoves:YES];
860 //NSLog(@"%s len=%d chars=0x%x", _cmd, len, chars[0]);
861 [[self vimController] sendMessage:KeyDownMsgID data:data];
865 - (MMVimController *)vimController
867 id windowController = [[self window] windowController];
869 // TODO: Make sure 'windowController' is a MMWindowController before type
871 return [(MMWindowController*)windowController vimController];
874 @end // MMAtsuiTextView (Private)
879 @implementation MMAtsuiTextView (Drawing)
881 - (NSPoint)originForRow:(int)row column:(int)col
883 return NSMakePoint(col * cellSize.width, row * cellSize.height);
886 - (NSRect)rectFromRow:(int)row1 column:(int)col1
887 toRow:(int)row2 column:(int)col2
889 NSPoint origin = [self originForRow: row1 column: col1];
890 return NSMakeRect(origin.x, origin.y,
891 (col2 + 1 - col1) * cellSize.width,
892 (row2 + 1 - row1) * cellSize.height);
895 - (NSSize)textAreaSize
897 // Calculate the (desired) size of the text area, i.e. the text view area
898 // minus the inset area.
899 return NSMakeSize(maxColumns * cellSize.width, maxRows * cellSize.height);
902 - (void)resizeContentImage
904 //NSLog(@"resizeContentImage");
905 [contentImage release];
906 contentImage = [[NSImage alloc] initWithSize:[self textAreaSize]];
907 [contentImage setFlipped: YES];
908 imageSize = [self textAreaSize];
913 [contentImage lockFocus];
918 [contentImage unlockFocus];
921 - (void)drawString:(UniChar *)string length:(UniCharCount)length
922 atRow:(int)row column:(int)col cells:(int)cells
923 withFlags:(int)flags foregroundColor:(NSColor *)fg
924 backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp
926 // 'string' consists of 'length' utf-16 code pairs and should cover 'cells'
927 // display cells (a normal character takes up one display cell, a wide
928 // character takes up two)
929 ATSUStyle style = (flags & DRAW_WIDE) ? atsuStyles[1] : atsuStyles[0];
930 ATSUTextLayout layout;
932 // NSLog(@"drawString: %d", length);
934 ATSUCreateTextLayout(&layout);
935 ATSUSetTextPointerLocation(layout, string,
936 kATSUFromTextBeginning, kATSUToTextEnd,
938 ATSUSetRunStyle(layout, style, kATSUFromTextBeginning, kATSUToTextEnd);
940 NSRect rect = NSMakeRect(col * cellSize.width, row * cellSize.height,
941 length * cellSize.width, cellSize.height);
942 if (flags & DRAW_WIDE)
943 rect.size.width = rect.size.width * 2;
944 CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
946 ATSUAttributeTag tags[] = { kATSUCGContextTag };
947 ByteCount sizes[] = { sizeof(CGContextRef) };
948 ATSUAttributeValuePtr values[] = { &context };
949 ATSUSetLayoutControls(layout, 1, tags, sizes, values);
951 if (! (flags & DRAW_TRANSP))
959 ATSUSetTransientFontMatching(layout, TRUE);
961 kATSUFromTextBeginning,
963 X2Fix(rect.origin.x),
964 X2Fix(rect.origin.y + [font ascender]));
965 ATSUDisposeTextLayout(layout);
968 - (void)scrollRect:(NSRect)rect lineCount:(int)count
970 NSPoint destPoint = rect.origin;
971 destPoint.y += count * cellSize.height;
973 NSCopyBits(0, rect, destPoint);
976 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
977 scrollBottom:(int)bottom left:(int)left right:(int)right
978 color:(NSColor *)color
980 NSRect rect = [self rectFromRow:row + count
985 // move rect up for count lines
986 [self scrollRect:rect lineCount:-count];
987 [self clearBlockFromRow:bottom - count + 1
994 - (void)insertLinesAtRow:(int)row lineCount:(int)count
995 scrollBottom:(int)bottom left:(int)left right:(int)right
996 color:(NSColor *)color
998 NSRect rect = [self rectFromRow:row
1000 toRow:bottom - count
1003 // move rect down for count lines
1004 [self scrollRect:rect lineCount:count];
1005 [self clearBlockFromRow:row
1007 toRow:row + count - 1
1012 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
1013 column:(int)col2 color:(NSColor *)color
1016 NSRectFill([self rectFromRow:row1 column:col1 toRow:row2 column:col2]);
1021 [defaultBackgroundColor set];
1022 NSRectFill(NSMakeRect(0, 0, imageSize.width, imageSize.height));
1025 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
1026 fraction:(int)percent color:(NSColor *)color
1028 NSPoint origin = [self originForRow:row column:col];
1029 NSRect rect = NSMakeRect(origin.x, origin.y,
1030 cellSize.width, cellSize.height);
1032 // NSLog(@"shape = %d, fraction: %d", shape, percent);
1034 if (MMInsertionPointHorizontal == shape) {
1035 int frac = (cellSize.height * percent + 99)/100;
1036 rect.origin.y += rect.size.height - frac;
1037 rect.size.height = frac;
1038 } else if (MMInsertionPointVertical == shape) {
1039 int frac = (cellSize.width * percent + 99)/100;
1040 rect.size.width = frac;
1044 if (MMInsertionPointHollow == shape) {
1051 @end // MMAtsuiTextView (Drawing)