Experimental ATSUI text rendering support
[MacVim.git] / src / MacVim / MMAtsuiTextView.m
blob495d42652289b3187ac9ecb69aaad6cec74b8a4b
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
6  * Do ":help uganda"  in Vim to read copying and usage conditions.
7  * Do ":help credits" in Vim to see a list of people who contributed.
8  * See README.txt for an overview of the Vim source code.
9  */
11  * MMAtsuiTextView
12  *
13  * Dispatches keyboard and mouse input to the backend.  Handles drag-n-drop of
14  * files onto window.
15  */
17 #import "MMAtsuiTextView.h"
18 #import "MMVimController.h"
19 #import "MacVim.h"
21 static char MMKeypadEnter[2] = { 'K', 'A' };
22 static NSString *MMKeypadEnterString = @"KA";
24 @interface NSFont (AppKitPrivate)
25 - (ATSUFontID) _atsFontID;
26 @end
28 @interface MMAtsuiTextView (Private)
29 - (void)initAtsuStyles;
30 - (void)disposeAtsuStyles;
31 - (void)updateAtsuStyles;
32 - (void)dispatchKeyEvent:(NSEvent *)event;
33 - (void)sendKeyDown:(const char *)chars length:(int)len modifiers:(int)flags;
34 - (MMVimController *)vimController;
35 @end
37 @interface MMAtsuiTextView (Drawing)
38 - (void)fitImageToSize;
39 - (void)beginDrawing;
40 - (void)endDrawing;
41 - (void)drawString:(UniChar *)string length:(UniCharCount)length
42              atRow:(int)row column:(int)col cells:(int)cells
43          withFlags:(int)flags foregroundColor:(NSColor *)fg
44    backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp;
45 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
46               scrollBottom:(int)bottom left:(int)left right:(int)right
47                      color:(NSColor *)color;
48 - (void)insertLinesAtRow:(int)row lineCount:(int)count
49             scrollBottom:(int)bottom left:(int)left right:(int)right
50                    color:(NSColor *)color;
51 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
52                    column:(int)col2 color:(NSColor *)color;
53 - (void)clearAll;
54 @end
57 @implementation MMAtsuiTextView
59 - (id)initWithFrame:(NSRect)frame
61     if ((self = [super initWithFrame:frame])) {
62         // NOTE!  It does not matter which font is set here, Vim will set its
63         // own font on startup anyway.  Just set some bogus values.
64         font = [[NSFont userFixedPitchFontOfSize:0] retain];
65         cellSize.width = cellSize.height = 1;
66         contentImage = nil;
67         imageSize = NSZeroSize;
69         [self initAtsuStyles];
70     }
72     return self;
75 - (void)dealloc
77     [self disposeAtsuStyles];
78     [font release];  font = nil;
79     [defaultBackgroundColor release];  defaultBackgroundColor = nil;
80     [defaultForegroundColor release];  defaultForegroundColor = nil;
82     [super dealloc];
85 - (void)getMaxRows:(int*)rows columns:(int*)cols
87     if (rows) *rows = maxRows;
88     if (cols) *cols = maxColumns;
91 - (void)setMaxRows:(int)rows columns:(int)cols
93     // NOTE: Just remember the new values, the actual resizing is done lazily.
94     maxRows = rows;
95     maxColumns = cols;
98 - (void)setDefaultColorsBackground:(NSColor *)bgColor
99                         foreground:(NSColor *)fgColor
101     if (defaultBackgroundColor != bgColor) {
102         [defaultBackgroundColor release];
103         defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
104     }
106     // NOTE: The default foreground color isn't actually used for anything, but
107     // other class instances might want to be able to access it so it is stored
108     // here.
109     if (defaultForegroundColor != fgColor) {
110         [defaultForegroundColor release];
111         defaultForegroundColor = fgColor ? [fgColor retain] : nil;
112     }
115 - (NSSize)size
117     return NSMakeSize(maxColumns*cellSize.width, maxRows*cellSize.height);
120 - (NSSize)fitToSize:(NSSize)size rows:(int *)rows columns:(int *)columns
122     NSSize curSize = [self size];
123     NSSize fitSize = curSize;
124     int fitRows = maxRows;
125     int fitCols = maxColumns;
127     if (size.height < curSize.height) {
128         // Remove lines until the height of the text storage fits inside
129         // 'size'.  However, always make sure there are at least 3 lines in the
130         // text storage.  (Why 3? It seem Vim never allows less than 3 lines.)
131         //
132         // TODO: No need to search since line height is fixed, just calculate
133         // the new height.
134         int rowCount = maxRows;
135         int rowsToRemove;
136         for (rowsToRemove = 0; rowsToRemove < maxRows-3; ++rowsToRemove) {
137             float height = cellSize.height*rowCount;
139             if (height <= size.height) {
140                 fitSize.height = height;
141                 break;
142             }
144             --rowCount;
145         }
147         fitRows -= rowsToRemove;
148     } else if (size.height > curSize.height) {
149         float fh = cellSize.height;
150         if (fh < 1.0f) fh = 1.0f;
152         fitRows = floor(size.height/fh);
153         fitSize.height = fh*fitRows;
154     }
156     if (size.width != curSize.width) {
157         float fw = cellSize.width;
158         if (fw < 1.0f) fw = 1.0f;
160         fitCols = floor(size.width/fw);
161         fitSize.width = fw*fitCols;
162     }
164     if (rows) *rows = fitRows;
165     if (columns) *columns = fitCols;
167     return fitSize;
170 - (NSRect)rectForRowsInRange:(NSRange)range
172     NSRect rect = { 0, 0, 0, 0 };
173     unsigned start = range.location > maxRows ? maxRows : range.location;
174     unsigned length = range.length;
176     if (start+length > maxRows)
177         length = maxRows - start;
179     rect.origin.y = cellSize.height * start;
180     rect.size.height = cellSize.height * length;
182     return rect;
185 - (NSRect)rectForColumnsInRange:(NSRange)range
187     NSRect rect = { 0, 0, 0, 0 };
188     unsigned start = range.location > maxColumns ? maxColumns : range.location;
189     unsigned length = range.length;
191     if (start+length > maxColumns)
192         length = maxColumns - start;
194     rect.origin.x = cellSize.width * start;
195     rect.size.width = cellSize.width * length;
197     return rect;
201 - (void)setFont:(NSFont *)newFont
203     if (newFont && font != newFont) {
204         [font release];
205         font = [newFont retain];
207         float em = [newFont widthOfString:@"m"];
208         float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
209                 floatForKey:MMCellWidthMultiplierKey];
211         // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
212         // only render at integer sizes.  Hence, we restrict the cell width to
213         // an integer here, otherwise the window width and the actual text
214         // width will not match.
215         cellSize.width = ceilf(em * cellWidthMultiplier);
216         cellSize.height = linespace + [newFont defaultLineHeightForFont];
218         [self updateAtsuStyles];
219     }
222 - (void)setWideFont:(NSFont *)newFont
226 - (NSFont *)font
228     return font;
231 - (NSSize)cellSize
233     return cellSize;
236 - (void)setLinespace:(float)newLinespace
238     linespace = newLinespace;
240     // NOTE: The linespace is added to the cell height in order for a multiline
241     // selection not to have white (background color) gaps between lines.  Also
242     // this simplifies the code a lot because there is no need to check the
243     // linespace when calculating the size of the text view etc.  When the
244     // linespace is non-zero the baseline will be adjusted as well; check
245     // MMTypesetter.
246     cellSize.height = linespace + [font defaultLineHeightForFont];
252 - (NSEvent *)lastMouseDownEvent
254     return nil;
257 - (void)setShouldDrawInsertionPoint:(BOOL)on
261 - (void)setPreEditRow:(int)row column:(int)col
265 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
266                        fraction:(int)percent color:(NSColor *)color
270 - (void)hideMarkedTextField
277 - (void)keyDown:(NSEvent *)event
279     //NSLog(@"%s %@", _cmd, event);
280     // HACK! If control modifier is held, don't pass the event along to
281     // interpretKeyEvents: since some keys are bound to multiple commands which
282     // means doCommandBySelector: is called several times.
283     //
284     // TODO: Figure out a way to disable Cocoa key bindings entirely, without
285     // affecting input management.
286     if ([event modifierFlags] & NSControlKeyMask) {
287         NSString *unmod = [event charactersIgnoringModifiers];
288         if ([unmod length] == 1 && [unmod characterAtIndex:0] <= 0x7f
289                                 && [unmod characterAtIndex:0] >= 0x60) {
290             // HACK! Send Ctrl-letter keys (and C-@, C-[, C-\, C-], C-^, C-_)
291             // as normal text to be added to the Vim input buffer.  This must
292             // be done in order for the backend to be able to separate e.g.
293             // Ctrl-i and Ctrl-tab.
294             [self insertText:[event characters]];
295         } else {
296             [self dispatchKeyEvent:event];
297         }
298     } else {
299         [super keyDown:event];
300     }
303 - (void)insertText:(id)string
305     //NSLog(@"%s %@", _cmd, string);
306     // NOTE!  This method is called for normal key presses but also for
307     // Option-key presses --- even when Ctrl is held as well as Option.  When
308     // Ctrl is held, the AppKit translates the character to a Ctrl+key stroke,
309     // so 'string' need not be a printable character!  In this case it still
310     // works to pass 'string' on to Vim as a printable character (since
311     // modifiers are already included and should not be added to the input
312     // buffer using CSI, K_MODIFIER).
314     [self hideMarkedTextField];
316     NSEvent *event = [NSApp currentEvent];
318     // HACK!  In order to be able to bind to <S-Space>, <S-M-Tab>, etc. we have
319     // to watch for them here.
320     if ([event type] == NSKeyDown
321             && [[event charactersIgnoringModifiers] length] > 0
322             && [event modifierFlags]
323                 & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask)) {
324         unichar c = [[event charactersIgnoringModifiers] characterAtIndex:0];
326         // <S-M-Tab> translates to 0x19 
327         if (' ' == c || 0x19 == c) {
328             [self dispatchKeyEvent:event];
329             return;
330         }
331     }
333     // TODO: Support 'mousehide' (check p_mh)
334     [NSCursor setHiddenUntilMouseMoves:YES];
336     // NOTE: 'string' is either an NSString or an NSAttributedString.  Since we
337     // do not support attributes, simply pass the corresponding NSString in the
338     // latter case.
339     if ([string isKindOfClass:[NSAttributedString class]])
340         string = [string string];
342     //NSLog(@"send InsertTextMsgID: %@", string);
344     [[self vimController] sendMessage:InsertTextMsgID
345                  data:[string dataUsingEncoding:NSUTF8StringEncoding]];
348 - (void)doCommandBySelector:(SEL)selector
350     //NSLog(@"%s %@", _cmd, NSStringFromSelector(selector));
351     // By ignoring the selector we effectively disable the key binding
352     // mechanism of Cocoa.  Hopefully this is what the user will expect
353     // (pressing Ctrl+P would otherwise result in moveUp: instead of previous
354     // match, etc.).
355     //
356     // We usually end up here if the user pressed Ctrl+key (but not
357     // Ctrl+Option+key).
359     NSEvent *event = [NSApp currentEvent];
361     if (selector == @selector(cancelOperation:)
362             || selector == @selector(insertNewline:)) {
363         // HACK! If there was marked text which got abandoned as a result of
364         // hitting escape or enter, then 'insertText:' is called with the
365         // abandoned text but '[event characters]' includes the abandoned text
366         // as well.  Since 'dispatchKeyEvent:' looks at '[event characters]' we
367         // must intercept these keys here or the abandonded text gets inserted
368         // twice.
369         NSString *key = [event charactersIgnoringModifiers];
370         const char *chars = [key UTF8String];
371         int len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
373         if (0x3 == chars[0]) {
374             // HACK! AppKit turns enter (not return) into Ctrl-C, so we need to
375             // handle it separately (else Ctrl-C doesn't work).
376             len = sizeof(MMKeypadEnter)/sizeof(MMKeypadEnter[0]);
377             chars = MMKeypadEnter;
378         }
380         [self sendKeyDown:chars length:len modifiers:[event modifierFlags]];
381     } else {
382         [self dispatchKeyEvent:event];
383     }
386 - (BOOL)performKeyEquivalent:(NSEvent *)event
388     //NSLog(@"%s %@", _cmd, event);
389     // Called for Cmd+key keystrokes, function keys, arrow keys, page
390     // up/down, home, end.
391     //
392     // NOTE: This message cannot be ignored since Cmd+letter keys never are
393     // passed to keyDown:.  It seems as if the main menu consumes Cmd-key
394     // strokes, unless the key is a function key.
396     // NOTE: If the event that triggered this method represents a function key
397     // down then we do nothing, otherwise the input method never gets the key
398     // stroke (some input methods use e.g. arrow keys).  The function key down
399     // event will still reach Vim though (via keyDown:).  The exceptions to
400     // this rule are: PageUp/PageDown (keycode 116/121).
401     int flags = [event modifierFlags];
402     if ([event type] != NSKeyDown || flags & NSFunctionKeyMask
403             && !(116 == [event keyCode] || 121 == [event keyCode]))
404         return NO;
406     // HACK!  Let the main menu try to handle any key down event, before
407     // passing it on to vim, otherwise key equivalents for menus will
408     // effectively be disabled.
409     if ([[NSApp mainMenu] performKeyEquivalent:event])
410         return YES;
412     // HACK!  KeyCode 50 represent the key which switches between windows
413     // within an application (like Cmd+Tab is used to switch between
414     // applications).  Return NO here, else the window switching does not work.
415     //
416     // Will this hack work for all languages / keyboard layouts?
417     if ([event keyCode] == 50)
418         return NO;
420     // HACK!  On Leopard Ctrl-key events end up here instead of keyDown:.
421     if (flags & NSControlKeyMask) {
422         [self keyDown:event];
423         return YES;
424     }
426     //NSLog(@"%s%@", _cmd, event);
428     NSString *chars = [event characters];
429     NSString *unmodchars = [event charactersIgnoringModifiers];
430     int len = [unmodchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
431     NSMutableData *data = [NSMutableData data];
433     if (len <= 0)
434         return NO;
436     // If 'chars' and 'unmodchars' differs when shift flag is present, then we
437     // can clear the shift flag as it is already included in 'unmodchars'.
438     // Failing to clear the shift flag means <D-Bar> turns into <S-D-Bar> (on
439     // an English keyboard).
440     if (flags & NSShiftKeyMask && ![chars isEqual:unmodchars])
441         flags &= ~NSShiftKeyMask;
443     if (0x3 == [unmodchars characterAtIndex:0]) {
444         // HACK! AppKit turns enter (not return) into Ctrl-C, so we need to
445         // handle it separately (else Cmd-enter turns into Ctrl-C).
446         unmodchars = MMKeypadEnterString;
447         len = [unmodchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
448     }
450     [data appendBytes:&flags length:sizeof(int)];
451     [data appendBytes:&len length:sizeof(int)];
452     [data appendBytes:[unmodchars UTF8String] length:len];
454     [[self vimController] sendMessage:CmdKeyMsgID data:data];
456     return YES;
459 - (NSPoint)textContainerOrigin
461     return NSZeroPoint;
464 - (void)setTextContainerInset:(NSSize)inset
468 - (void)setBackgroundColor:(NSColor *)color
475 - (BOOL)acceptsFirstResponder
477     return YES;
480 - (BOOL)isFlipped
482     return NO;
485 - (void)drawRect:(NSRect)rect
487     [contentImage drawInRect: rect
488                     fromRect: rect
489                    operation: NSCompositeCopy
490                     fraction: 1.0];
493 - (BOOL) wantsDefaultClipping
495     return NO;
499 #define MM_DEBUG_DRAWING 0
501 - (void)performBatchDrawWithData:(NSData *)data
503     const void *bytes = [data bytes];
504     const void *end = bytes + [data length];
506     if (! NSEqualSizes(imageSize, [self size]))
507         [self fitImageToSize];
509 #if MM_DEBUG_DRAWING
510     NSLog(@"====> BEGIN %s", _cmd);
511 #endif
512     [self beginDrawing];
514     // TODO: Sanity check input
516     while (bytes < end) {
517         int type = *((int*)bytes);  bytes += sizeof(int);
519         if (ClearAllDrawType == type) {
520 #if MM_DEBUG_DRAWING
521             NSLog(@"   Clear all");
522 #endif
523             [self clearAll];
524         } else if (ClearBlockDrawType == type) {
525             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
526             int row1 = *((int*)bytes);  bytes += sizeof(int);
527             int col1 = *((int*)bytes);  bytes += sizeof(int);
528             int row2 = *((int*)bytes);  bytes += sizeof(int);
529             int col2 = *((int*)bytes);  bytes += sizeof(int);
531 #if MM_DEBUG_DRAWING
532             NSLog(@"   Clear block (%d,%d) -> (%d,%d)", row1, col1,
533                     row2,col2);
534 #endif
535             [self clearBlockFromRow:row1 column:col1
536                     toRow:row2 column:col2
537                     color:[NSColor colorWithArgbInt:color]];
538         } else if (DeleteLinesDrawType == type) {
539             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
540             int row = *((int*)bytes);  bytes += sizeof(int);
541             int count = *((int*)bytes);  bytes += sizeof(int);
542             int bot = *((int*)bytes);  bytes += sizeof(int);
543             int left = *((int*)bytes);  bytes += sizeof(int);
544             int right = *((int*)bytes);  bytes += sizeof(int);
546 #if MM_DEBUG_DRAWING
547             NSLog(@"   Delete %d line(s) from %d", count, row);
548 #endif
549             [self deleteLinesFromRow:row lineCount:count
550                     scrollBottom:bot left:left right:right
551                            color:[NSColor colorWithArgbInt:color]];
552         } else if (DrawStringDrawType == type) {
553             int bg = *((int*)bytes);  bytes += sizeof(int);
554             int fg = *((int*)bytes);  bytes += sizeof(int);
555             int sp = *((int*)bytes);  bytes += sizeof(int);
556             int row = *((int*)bytes);  bytes += sizeof(int);
557             int col = *((int*)bytes);  bytes += sizeof(int);
558             int cells = *((int*)bytes);  bytes += sizeof(int);
559             int flags = *((int*)bytes);  bytes += sizeof(int);
560             int len = *((int*)bytes);  bytes += sizeof(int);
561             // UniChar *string = (UniChar*)bytes;  bytes += len;
562             NSString *string = [[NSString alloc] initWithBytesNoCopy:(void*)bytes
563                                                               length:len
564                                                             encoding:NSUTF8StringEncoding
565                                                         freeWhenDone:NO];
566             bytes += len;
567 #if MM_DEBUG_DRAWING
568             NSLog(@"   Draw string at (%d,%d) length=%d flags=%d fg=0x%x "
569                     "bg=0x%x sp=0x%x", row, col, len, flags, fg, bg, sp);
570 #endif
571             unichar *characters = malloc(sizeof(unichar) * [string length]);
572             [string getCharacters:characters];
574             [self drawString:characters length:[string length] atRow:row column:col
575                        cells:cells withFlags:flags
576                     foregroundColor:[NSColor colorWithRgbInt:fg]
577                     backgroundColor:[NSColor colorWithArgbInt:bg]
578                        specialColor:[NSColor colorWithRgbInt:sp]];
579             free(characters);
580             [string release];
581         } else if (InsertLinesDrawType == type) {
582             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
583             int row = *((int*)bytes);  bytes += sizeof(int);
584             int count = *((int*)bytes);  bytes += sizeof(int);
585             int bot = *((int*)bytes);  bytes += sizeof(int);
586             int left = *((int*)bytes);  bytes += sizeof(int);
587             int right = *((int*)bytes);  bytes += sizeof(int);
589 #if MM_DEBUG_DRAWING
590             NSLog(@"   Insert %d line(s) at row %d", count, row);
591 #endif
592             [self insertLinesAtRow:row lineCount:count
593                              scrollBottom:bot left:left right:right
594                                     color:[NSColor colorWithArgbInt:color]];
595         } else if (DrawCursorDrawType == type) {
596             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
597             int row = *((int*)bytes);  bytes += sizeof(int);
598             int col = *((int*)bytes);  bytes += sizeof(int);
599             int shape = *((int*)bytes);  bytes += sizeof(int);
600             int percent = *((int*)bytes);  bytes += sizeof(int);
602 #if MM_DEBUG_DRAWING
603             NSLog(@"   Draw cursor at (%d,%d)", row, col);
604 #endif
605             [self drawInsertionPointAtRow:row column:col shape:shape
606                                      fraction:percent
607                                         color:[NSColor colorWithRgbInt:color]];
608         } else {
609             NSLog(@"WARNING: Unknown draw type (type=%d)", type);
610         }
611     }
613     [self endDrawing];
615     // NOTE: During resizing, Cocoa only sends draw messages before Vim's rows
616     // and columns are changed (due to ipc delays). Force a redraw here.
617     [self setNeedsDisplay:YES];
618     // [self displayIfNeeded];
620 #if MM_DEBUG_DRAWING
621     NSLog(@"<==== END   %s", _cmd);
622 #endif
625 @end // MMAtsuiTextView
630 @implementation MMAtsuiTextView (Private)
632 - (void)initAtsuStyles
634     int i;
635     for (i = 0; i < MMMaxCellsPerChar; i++)
636         ATSUCreateStyle(&atsuStyles[i]);
639 - (void)disposeAtsuStyles
641     int i;
643     for (i = 0; i < MMMaxCellsPerChar; i++)
644         if (atsuStyles[i] != NULL)
645         {
646             if (ATSUDisposeStyle(atsuStyles[i]) != noErr)
647                 atsuStyles[i] = NULL;
648         }
651 - (void)updateAtsuStyles
653     ATSUFontID        fontID;
654     Fixed             fontSize;
655     Fixed             fontWidth;
656     int               i;
657     CGAffineTransform transform = CGAffineTransformMakeScale(1, -1);
658     ATSStyleRenderingOptions options;
660     fontID    = [font _atsFontID];
661     fontSize  = Long2Fix([font pointSize]);
662     options   = kATSStyleApplyAntiAliasing;
664     ATSUAttributeTag attribTags[] =
665     {
666         kATSUFontTag, kATSUSizeTag, kATSUImposeWidthTag,
667         kATSUFontMatrixTag, kATSUStyleRenderingOptionsTag,
668         kATSUMaxATSUITagValue + 1
669     };
671     ByteCount attribSizes[] =
672     {
673         sizeof(ATSUFontID), sizeof(Fixed), sizeof(fontWidth),
674         sizeof(CGAffineTransform), sizeof(ATSStyleRenderingOptions),
675         sizeof(font)
676     };
678     ATSUAttributeValuePtr attribValues[] =
679     {
680         &fontID, &fontSize, &fontWidth, &transform, &options, &font
681     };
683     for (i = 0; i < MMMaxCellsPerChar; i++)
684     {
685         fontWidth = Long2Fix(cellSize.width * (i + 1));
687         if (ATSUSetAttributes(atsuStyles[i],
688                               (sizeof attribTags) / sizeof(ATSUAttributeTag),
689                               attribTags, attribSizes, attribValues) != noErr)
690         {
691             ATSUDisposeStyle(atsuStyles[i]);
692             atsuStyles[i] = NULL;
693         }
694     }
697 - (void)dispatchKeyEvent:(NSEvent *)event
699     // Only handle the command if it came from a keyDown event
700     if ([event type] != NSKeyDown)
701         return;
703     NSString *chars = [event characters];
704     NSString *unmodchars = [event charactersIgnoringModifiers];
705     unichar c = [chars characterAtIndex:0];
706     unichar imc = [unmodchars characterAtIndex:0];
707     int len = 0;
708     const char *bytes = 0;
709     int mods = [event modifierFlags];
711     //NSLog(@"%s chars[0]=0x%x unmodchars[0]=0x%x (chars=%@ unmodchars=%@)",
712     //        _cmd, c, imc, chars, unmodchars);
714     if (' ' == imc && 0xa0 != c) {
715         // HACK!  The AppKit turns <C-Space> into <C-@> which is not standard
716         // Vim behaviour, so bypass this problem.  (0xa0 is <M-Space>, which
717         // should be passed on as is.)
718         len = [unmodchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
719         bytes = [unmodchars UTF8String];
720     } else if (imc == c && '2' == c) {
721         // HACK!  Translate Ctrl+2 to <C-@>.
722         static char ctrl_at = 0;
723         len = 1;  bytes = &ctrl_at;
724     } else if (imc == c && '6' == c) {
725         // HACK!  Translate Ctrl+6 to <C-^>.
726         static char ctrl_hat = 0x1e;
727         len = 1;  bytes = &ctrl_hat;
728     } else if (c == 0x19 && imc == 0x19) {
729         // HACK! AppKit turns back tab into Ctrl-Y, so we need to handle it
730         // separately (else Ctrl-Y doesn't work).
731         static char tab = 0x9;
732         len = 1;  bytes = &tab;  mods |= NSShiftKeyMask;
733     } else {
734         len = [chars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
735         bytes = [chars UTF8String];
736     }
738     [self sendKeyDown:bytes length:len modifiers:mods];
741 - (void)sendKeyDown:(const char *)chars length:(int)len modifiers:(int)flags
743     if (chars && len > 0) {
744         NSMutableData *data = [NSMutableData data];
746         [data appendBytes:&flags length:sizeof(int)];
747         [data appendBytes:&len length:sizeof(int)];
748         [data appendBytes:chars length:len];
750         // TODO: Support 'mousehide' (check p_mh)
751         [NSCursor setHiddenUntilMouseMoves:YES];
753         //NSLog(@"%s len=%d chars=0x%x", _cmd, len, chars[0]);
754         [[self vimController] sendMessage:KeyDownMsgID data:data];
755     }
758 - (MMVimController *)vimController
760     id windowController = [[self window] windowController];
762     // TODO: Make sure 'windowController' is a MMWindowController before type
763     // casting.
764     return [(MMWindowController*)windowController vimController];
767 @end // MMAtsuiTextView (Private)
772 @implementation MMAtsuiTextView (Drawing)
774 - (NSRect)rectFromRow:(int)row1 column:(int)col1
775                 toRow:(int)row2 column:(int)col2
777     return NSMakeRect(col1 * cellSize.width, row1 * cellSize.height,
778                       (col2 + 1 - col1) * cellSize.width,
779                       (row2 + 1 - row1) * cellSize.height);
782 - (void)beginDrawing
784     [contentImage lockFocus];
787 - (void)endDrawing
789     [contentImage unlockFocus];
792 - (void)fitImageToSize
794     NSLog(@"fitImageToSize");
795     [contentImage release];
796     contentImage = [[NSImage alloc] initWithSize:[self size]];
797     [contentImage setFlipped: YES];
798     imageSize = [self size];
801 - (void)drawString:(UniChar *)string length:(UniCharCount)length
802              atRow:(int)row column:(int)col cells:(int)cells
803          withFlags:(int)flags foregroundColor:(NSColor *)fg
804    backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp
806     // 'string' consists of 'length' utf-16 code pairs and should cover 'cells'
807     // display cells (a normal character takes up one display cell, a wide
808     // character takes up two)
809     ATSUStyle          style = atsuStyles[0];
810     ATSUTextLayout     layout;
812     // NSLog(@"drawString: %d", length);
814     ATSUCreateTextLayout(&layout);
815     ATSUSetTextPointerLocation(layout, string,
816                                kATSUFromTextBeginning, kATSUToTextEnd,
817                                length);
818     ATSUSetRunStyle(layout, style, kATSUFromTextBeginning, kATSUToTextEnd);
820     NSRect rect = NSMakeRect(col * cellSize.width, row * cellSize.height,
821                              length * cellSize.width, cellSize.height);
822     CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
824     ATSUAttributeTag tags[] = { kATSUCGContextTag };
825     ByteCount sizes[] = { sizeof(CGContextRef) };
826     ATSUAttributeValuePtr values[] = { &context };
827     ATSUSetLayoutControls(layout, 1, tags, sizes, values);
829     if (! (flags & DRAW_TRANSP))
830     {
831         [bg set];
832         NSRectFill(rect);
833     }
835     [fg set];
837     ATSUSetTransientFontMatching(layout, TRUE);
838     ATSUDrawText(layout,
839                  kATSUFromTextBeginning,
840                  kATSUToTextEnd,
841                  X2Fix(rect.origin.x),
842                  X2Fix(rect.origin.y + [font ascender]));
843     ATSUDisposeTextLayout(layout);
846 - (void)scrollRect:(NSRect)rect lineCount:(int)count
848     NSPoint destPoint = rect.origin;
849     destPoint.y += count * cellSize.height;
851     NSCopyBits(0, rect, destPoint);
854 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
855               scrollBottom:(int)bottom left:(int)left right:(int)right
856                      color:(NSColor *)color
858     NSRect rect = [self rectFromRow:row + count
859                              column:left
860                               toRow:bottom
861                              column:right];
862     [color set];
863     // move rect up for count lines
864     [self scrollRect:rect lineCount:-count];
865     [self clearBlockFromRow:bottom - count + 1
866                      column:left
867                       toRow:bottom
868                      column:right
869                       color:color];
872 - (void)insertLinesAtRow:(int)row lineCount:(int)count
873             scrollBottom:(int)bottom left:(int)left right:(int)right
874                    color:(NSColor *)color
876     NSRect rect = [self rectFromRow:row
877                              column:left
878                               toRow:bottom - count
879                              column:right];
880     [color set];
881     // move rect down for count lines
882     [self scrollRect:rect lineCount:count];
883     [self clearBlockFromRow:row
884                      column:left
885                       toRow:row + count - 1
886                      column:right
887                       color:color];
890 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
891                    column:(int)col2 color:(NSColor *)color
893     [color set];
894     NSRectFill([self rectFromRow:row1 column:col1 toRow:row2 column:col2]);
897 - (void)clearAll
899     [defaultBackgroundColor set];
900     NSRectFill(NSMakeRect(0, 0, imageSize.width, imageSize.height));
903 @end // MMAtsuiTextView (Drawing)