No CF calls between fork() and exec()
[MacVim.git] / src / MacVim / MMAtsuiTextView.m
blobe7ccffe780fc8a517ccaa6bc5eacd98f9ed9fccf
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.  The rendering is done using ATSUI.
15  *
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].
21  *
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
25  * resized.
26  */
28 #import "MMAtsuiTextView.h"
29 #import "MMVimController.h"
30 #import "MacVim.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";
47 enum {
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).
51     MMMinRows = 4,
52     MMMinColumns = 30
56 @interface NSFont (AppKitPrivate)
57 - (ATSUFontID) _atsFontID;
58 @end
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;
69 @end
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;
78 - (void)beginDrawing;
79 - (void)endDrawing;
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;
92 - (void)clearAll;
93 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
94                        fraction:(int)percent color:(NSColor *)color;
95 @end
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;
107         contentImage = nil;
108         imageSize = NSZeroSize;
109         insetSize = NSZeroSize;
111         [self initAtsuStyles];
112     }
114     return self;
117 - (void)dealloc
119     [self disposeAtsuStyles];
120     [font release];  font = nil;
121     [defaultBackgroundColor release];  defaultBackgroundColor = nil;
122     [defaultForegroundColor release];  defaultForegroundColor = nil;
124     [super dealloc];
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.
136     maxRows = rows;
137     maxColumns = cols;
140 - (void)setDefaultColorsBackground:(NSColor *)bgColor
141                         foreground:(NSColor *)fgColor
143     if (defaultBackgroundColor != bgColor) {
144         [defaultBackgroundColor release];
145         defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
146     }
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
150     // here.
151     if (defaultForegroundColor != fgColor) {
152         [defaultForegroundColor release];
153         defaultForegroundColor = fgColor ? [fgColor retain] : nil;
154     }
157 - (void)setTextContainerInset:(NSSize)size
159     insetSize = 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;
174     return rect;
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;
189     return rect;
193 - (void)setFont:(NSFont *)newFont
195     if (newFont && font != newFont) {
196         [font release];
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];
211     }
214 - (void)setWideFont:(NSFont *)newFont
218 - (NSFont *)font
220     return font;
223 - (NSSize)cellSize
225     return cellSize;
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
237     // MMTypesetter.
238     cellSize.height = linespace + [font defaultLineHeightForFont];
244 - (NSEvent *)lastMouseDownEvent
246     return nil;
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.
273     //
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]];
287         } else {
288             [self dispatchKeyEvent:event];
289         }
290     } else {
291         [self interpretKeyEvents:[NSArray arrayWithObject:event]];
292     }
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];
321             return;
322         }
323     }
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
330     // latter case.
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
346     // match, etc.).
347     //
348     // We usually end up here if the user pressed Ctrl+key (but not
349     // Ctrl+Option+key).
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
360         // twice.
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;
370         }
372         [self sendKeyDown:chars length:len modifiers:[event modifierFlags]];
373     } else {
374         [self dispatchKeyEvent:event];
375     }
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.
383     //
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]))
396         return NO;
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])
402         return YES;
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.
407     //
408     // Will this hack work for all languages / keyboard layouts?
409     if ([event keyCode] == 50)
410         return NO;
412     // HACK!  On Leopard Ctrl-key events end up here instead of keyDown:.
413     if (flags & NSControlKeyMask) {
414         [self keyDown:event];
415         return YES;
416     }
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];
425     if (len <= 0)
426         return NO;
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];
440     }
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];
448     return YES;
454 - (BOOL)acceptsFirstResponder
456     return YES;
459 - (BOOL)isFlipped
461     return NO;
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
473                     fromRect: srcRect
474                    operation: NSCompositeCopy
475                     fraction: 1.0];
478 - (BOOL) wantsDefaultClipping
480     return NO;
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];
494 #if MM_DEBUG_DRAWING
495     NSLog(@"====> BEGIN %s", _cmd);
496 #endif
497     [self beginDrawing];
499     // TODO: Sanity check input
501     while (bytes < end) {
502         int type = *((int*)bytes);  bytes += sizeof(int);
504         if (ClearAllDrawType == type) {
505 #if MM_DEBUG_DRAWING
506             NSLog(@"   Clear all");
507 #endif
508             [self clearAll];
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);
516 #if MM_DEBUG_DRAWING
517             NSLog(@"   Clear block (%d,%d) -> (%d,%d)", row1, col1,
518                     row2,col2);
519 #endif
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);
531 #if MM_DEBUG_DRAWING
532             NSLog(@"   Delete %d line(s) from %d", count, row);
533 #endif
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
548                                                               length:len
549                                                             encoding:NSUTF8StringEncoding
550                                                         freeWhenDone:NO];
551             bytes += len;
552 #if MM_DEBUG_DRAWING
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);
555 #endif
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]];
564             free(characters);
565             [string release];
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);
574 #if MM_DEBUG_DRAWING
575             NSLog(@"   Insert %d line(s) at row %d", count, row);
576 #endif
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);
587 #if MM_DEBUG_DRAWING
588             NSLog(@"   Draw cursor at (%d,%d)", row, col);
589 #endif
590             [self drawInsertionPointAtRow:row column:col shape:shape
591                                      fraction:percent
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);
598         } else {
599             NSLog(@"WARNING: Unknown draw type (type=%d)", type);
600         }
601     }
603     [self endDrawing];
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];
610 #if MM_DEBUG_DRAWING
611     NSLog(@"<==== END   %s", _cmd);
612 #endif
615 - (NSSize)constrainRows:(int *)rows columns:(int *)cols toSize:(NSSize)size
617     // TODO:
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;
634     }
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;
643     }
645     if (rows) *rows = desiredRows;
646     if (cols) *cols = desiredCols;
648     return desiredSize;
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);
659 - (NSSize)minSize
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];
670     if (newFont) {
671         NSString *name = [newFont displayName];
672         unsigned len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
673         if (len > 0) {
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];
684         }
685     }
688 - (void)scrollWheel:(NSEvent *)event
690     if ([event deltaY] == 0)
691         return;
693     int row, col;
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])
702         return;
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))
728         return NO;
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),
734     //        *row, *column);
736     return YES;
739 - (void)initAtsuStyles
741     int i;
742     for (i = 0; i < MMMaxCellsPerChar; i++)
743         ATSUCreateStyle(&atsuStyles[i]);
746 - (void)disposeAtsuStyles
748     int i;
750     for (i = 0; i < MMMaxCellsPerChar; i++)
751         if (atsuStyles[i] != NULL)
752         {
753             if (ATSUDisposeStyle(atsuStyles[i]) != noErr)
754                 atsuStyles[i] = NULL;
755         }
758 - (void)updateAtsuStyles
760     ATSUFontID        fontID;
761     Fixed             fontSize;
762     Fixed             fontWidth;
763     int               i;
764     CGAffineTransform transform = CGAffineTransformMakeScale(1, -1);
765     ATSStyleRenderingOptions options;
767     fontID    = [font _atsFontID];
768     fontSize  = Long2Fix([font pointSize]);
769     options   = kATSStyleApplyAntiAliasing;
771     ATSUAttributeTag attribTags[] =
772     {
773         kATSUFontTag, kATSUSizeTag, kATSUImposeWidthTag,
774         kATSUFontMatrixTag, kATSUStyleRenderingOptionsTag,
775         kATSUMaxATSUITagValue + 1
776     };
778     ByteCount attribSizes[] =
779     {
780         sizeof(ATSUFontID), sizeof(Fixed), sizeof(fontWidth),
781         sizeof(CGAffineTransform), sizeof(ATSStyleRenderingOptions),
782         sizeof(font)
783     };
785     ATSUAttributeValuePtr attribValues[] =
786     {
787         &fontID, &fontSize, &fontWidth, &transform, &options, &font
788     };
790     for (i = 0; i < MMMaxCellsPerChar; i++)
791     {
792         fontWidth = Long2Fix(cellSize.width * (i + 1));
794         if (ATSUSetAttributes(atsuStyles[i],
795                               (sizeof attribTags) / sizeof(ATSUAttributeTag),
796                               attribTags, attribSizes, attribValues) != noErr)
797         {
798             ATSUDisposeStyle(atsuStyles[i]);
799             atsuStyles[i] = NULL;
800         }
801     }
804 - (void)dispatchKeyEvent:(NSEvent *)event
806     // Only handle the command if it came from a keyDown event
807     if ([event type] != NSKeyDown)
808         return;
810     NSString *chars = [event characters];
811     NSString *unmodchars = [event charactersIgnoringModifiers];
812     unichar c = [chars characterAtIndex:0];
813     unichar imc = [unmodchars characterAtIndex:0];
814     int len = 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;
840     } else {
841         len = [chars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
842         bytes = [chars UTF8String];
843     }
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];
862     }
865 - (MMVimController *)vimController
867     id windowController = [[self window] windowController];
869     // TODO: Make sure 'windowController' is a MMWindowController before type
870     // casting.
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];
911 - (void)beginDrawing
913     [contentImage lockFocus];
916 - (void)endDrawing
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,
937                                length);
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))
952     {
953         [bg set];
954         NSRectFill(rect);
955     }
957     [fg set];
959     ATSUSetTransientFontMatching(layout, TRUE);
960     ATSUDrawText(layout,
961                  kATSUFromTextBeginning,
962                  kATSUToTextEnd,
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
981                              column:left
982                               toRow:bottom
983                              column:right];
984     [color set];
985     // move rect up for count lines
986     [self scrollRect:rect lineCount:-count];
987     [self clearBlockFromRow:bottom - count + 1
988                      column:left
989                       toRow:bottom
990                      column:right
991                       color:color];
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
999                              column:left
1000                               toRow:bottom - count
1001                              column:right];
1002     [color set];
1003     // move rect down for count lines
1004     [self scrollRect:rect lineCount:count];
1005     [self clearBlockFromRow:row
1006                      column:left
1007                       toRow:row + count - 1
1008                      column:right
1009                       color:color];
1012 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
1013                    column:(int)col2 color:(NSColor *)color
1015     [color set];
1016     NSRectFill([self rectFromRow:row1 column:col1 toRow:row2 column:col2]);
1019 - (void)clearAll
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;
1041     }
1043     [color set];
1044     if (MMInsertionPointHollow == shape) {
1045         NSFrameRect(rect);
1046     } else {
1047         NSRectFill(rect);
1048     }
1051 @end // MMAtsuiTextView (Drawing)