4ddcba0e0fa00aa9db2e065023eb8c613b23a4a2
[MacVim.git] / src / MacVim / MMAtsuiTextView.m
blob4ddcba0e0fa00aa9db2e065023eb8c613b23a4a2
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 "MMAppController.h"
29 #import "MMAtsuiTextView.h"
30 #import "MMVimController.h"
31 #import "MMWindowController.h"
32 #import "Miscellaneous.h"
35 // TODO: What does DRAW_TRANSP flag do?  If the background isn't drawn when
36 // this flag is set, then sometimes the character after the cursor becomes
37 // blank.  Everything seems to work fine by just ignoring this flag.
38 #define DRAW_TRANSP               0x01    /* draw with transparant bg */
39 #define DRAW_BOLD                 0x02    /* draw bold text */
40 #define DRAW_UNDERL               0x04    /* draw underline text */
41 #define DRAW_UNDERC               0x08    /* draw undercurl text */
42 #define DRAW_ITALIC               0x10    /* draw italic text */
43 #define DRAW_CURSOR               0x20
45 #define kUnderlineOffset            (-2)
46 #define kUnderlineHeight            1
47 #define kUndercurlHeight            2
48 #define kUndercurlOffset            (-2)
49 #define kUndercurlDotWidth          2
50 #define kUndercurlDotDistance       2
52 static char MMKeypadEnter[2] = { 'K', 'A' };
53 static NSString *MMKeypadEnterString = @"KA";
55 enum {
56     // These values are chosen so that the min size is not too small with the
57     // default font (they only affect resizing with the mouse, you can still
58     // use e.g. ":set lines=2" to go below these values).
59     MMMinRows = 4,
60     MMMinColumns = 30
64 @interface NSFont (AppKitPrivate)
65 - (ATSUFontID) _atsFontID;
66 @end
69 @interface MMAtsuiTextView (Private)
70 - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column;
71 - (void)initAtsuStyles;
72 - (void)disposeAtsuStyles;
73 - (void)updateAtsuStyles;
74 - (void)dispatchKeyEvent:(NSEvent *)event;
75 - (void)sendKeyDown:(const char *)chars length:(int)len modifiers:(int)flags;
76 - (void)hideMouseCursor;
77 - (MMWindowController *)windowController;
78 - (MMVimController *)vimController;
79 @end
82 @interface MMAtsuiTextView (Drawing)
83 - (NSPoint)originForRow:(int)row column:(int)column;
84 - (NSRect)rectFromRow:(int)row1 column:(int)col1
85                 toRow:(int)row2 column:(int)col2;
86 - (NSSize)textAreaSize;
87 - (void)resizeContentImage;
88 - (void)beginDrawing;
89 - (void)endDrawing;
90 - (void)drawString:(UniChar *)string length:(UniCharCount)length
91              atRow:(int)row column:(int)col cells:(int)cells
92          withFlags:(int)flags foregroundColor:(NSColor *)fg
93    backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp;
94 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
95               scrollBottom:(int)bottom left:(int)left right:(int)right
96                      color:(NSColor *)color;
97 - (void)insertLinesAtRow:(int)row lineCount:(int)count
98             scrollBottom:(int)bottom left:(int)left right:(int)right
99                    color:(NSColor *)color;
100 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
101                    column:(int)col2 color:(NSColor *)color;
102 - (void)clearAll;
103 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
104                        fraction:(int)percent color:(NSColor *)color;
105 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nrows
106                    numColumns:(int)ncols;
107 @end
111     static float
112 defaultLineHeightForFont(NSFont *font)
114     // HACK: -[NSFont defaultLineHeightForFont] is deprecated but since the
115     // ATSUI renderer does not use NSLayoutManager we create one temporarily.
116     NSLayoutManager *lm = [[NSLayoutManager alloc] init];
117     float height = [lm defaultLineHeightForFont:font];
118     [lm release];
120     return height;
123 @implementation MMAtsuiTextView
125 - (id)initWithFrame:(NSRect)frame
127     if ((self = [super initWithFrame:frame])) {
128         // NOTE!  It does not matter which font is set here, Vim will set its
129         // own font on startup anyway.  Just set some bogus values.
130         font = [[NSFont userFixedPitchFontOfSize:0] retain];
131         cellSize.width = cellSize.height = 1;
132         contentImage = nil;
133         imageSize = NSZeroSize;
134         insetSize = NSZeroSize;
136         // NOTE: If the default changes to 'NO' then the intialization of
137         // p_antialias in option.c must change as well.
138         antialias = YES;
140         [self initAtsuStyles];
141     }
143     return self;
146 - (void)dealloc
148     [self disposeAtsuStyles];
149     [font release];  font = nil;
150     [defaultBackgroundColor release];  defaultBackgroundColor = nil;
151     [defaultForegroundColor release];  defaultForegroundColor = nil;
153     [super dealloc];
156 - (void)getMaxRows:(int*)rows columns:(int*)cols
158     if (rows) *rows = maxRows;
159     if (cols) *cols = maxColumns;
162 - (void)setMaxRows:(int)rows columns:(int)cols
164     // NOTE: Just remember the new values, the actual resizing is done lazily.
165     maxRows = rows;
166     maxColumns = cols;
169 - (void)setDefaultColorsBackground:(NSColor *)bgColor
170                         foreground:(NSColor *)fgColor
172     if (defaultBackgroundColor != bgColor) {
173         [defaultBackgroundColor release];
174         defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
175     }
177     // NOTE: The default foreground color isn't actually used for anything, but
178     // other class instances might want to be able to access it so it is stored
179     // here.
180     if (defaultForegroundColor != fgColor) {
181         [defaultForegroundColor release];
182         defaultForegroundColor = fgColor ? [fgColor retain] : nil;
183     }
186 - (void)setTextContainerInset:(NSSize)size
188     insetSize = size;
191 - (NSRect)rectForRowsInRange:(NSRange)range
193     NSRect rect = { 0, 0, 0, 0 };
194     unsigned start = range.location > maxRows ? maxRows : range.location;
195     unsigned length = range.length;
197     if (start + length > maxRows)
198         length = maxRows - start;
200     rect.origin.y = cellSize.height * start + insetSize.height;
201     rect.size.height = cellSize.height * length;
203     return rect;
206 - (NSRect)rectForColumnsInRange:(NSRange)range
208     NSRect rect = { 0, 0, 0, 0 };
209     unsigned start = range.location > maxColumns ? maxColumns : range.location;
210     unsigned length = range.length;
212     if (start+length > maxColumns)
213         length = maxColumns - start;
215     rect.origin.x = cellSize.width * start + insetSize.width;
216     rect.size.width = cellSize.width * length;
218     return rect;
222 - (void)setFont:(NSFont *)newFont
224     if (newFont && font != newFont) {
225         [font release];
226         font = [newFont retain];
228         float em = [@"m" sizeWithAttributes:
229                 [NSDictionary dictionaryWithObject:newFont
230                                             forKey:NSFontAttributeName]].width;
231         float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
232                 floatForKey:MMCellWidthMultiplierKey];
234         // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
235         // only render at integer sizes.  Hence, we restrict the cell width to
236         // an integer here, otherwise the window width and the actual text
237         // width will not match.
238         cellSize.width = ceilf(em * cellWidthMultiplier);
239         cellSize.height = linespace + defaultLineHeightForFont(newFont);
241         [self updateAtsuStyles];
242     }
245 - (void)setWideFont:(NSFont *)newFont
249 - (NSFont *)font
251     return font;
254 - (NSSize)cellSize
256     return cellSize;
259 - (void)setLinespace:(float)newLinespace
261     linespace = newLinespace;
263     // NOTE: The linespace is added to the cell height in order for a multiline
264     // selection not to have white (background color) gaps between lines.  Also
265     // this simplifies the code a lot because there is no need to check the
266     // linespace when calculating the size of the text view etc.  When the
267     // linespace is non-zero the baseline will be adjusted as well; check
268     // MMTypesetter.
269     cellSize.height = linespace + defaultLineHeightForFont(font);
275 - (void)setShouldDrawInsertionPoint:(BOOL)on
279 - (void)setPreEditRow:(int)row column:(int)col
283 - (void)hideMarkedTextField
287 - (void)setMouseShape:(int)shape
291 - (void)setAntialias:(BOOL)state
293     antialias = state;
299 - (void)keyDown:(NSEvent *)event
301     //NSLog(@"%s %@", _cmd, event);
302     // HACK! If control modifier is held, don't pass the event along to
303     // interpretKeyEvents: since some keys are bound to multiple commands which
304     // means doCommandBySelector: is called several times.  Do the same for
305     // Alt+Function key presses (Alt+Up and Alt+Down are bound to two
306     // commands).  This hack may break input management, but unless we can
307     // figure out a way to disable key bindings there seems little else to do.
308     //
309     // TODO: Figure out a way to disable Cocoa key bindings entirely, without
310     // affecting input management.
311     int flags = [event modifierFlags];
312     if ((flags & NSControlKeyMask) ||
313             ((flags & NSAlternateKeyMask) && (flags & NSFunctionKeyMask))) {
314         NSString *unmod = [event charactersIgnoringModifiers];
315         if ([unmod length] == 1 && [unmod characterAtIndex:0] <= 0x7f
316                                 && [unmod characterAtIndex:0] >= 0x60) {
317             // HACK! Send Ctrl-letter keys (and C-@, C-[, C-\, C-], C-^, C-_)
318             // as normal text to be added to the Vim input buffer.  This must
319             // be done in order for the backend to be able to separate e.g.
320             // Ctrl-i and Ctrl-tab.
321             [self insertText:[event characters]];
322         } else {
323             [self dispatchKeyEvent:event];
324         }
325     } else {
326         [self interpretKeyEvents:[NSArray arrayWithObject:event]];
327     }
330 - (void)insertText:(id)string
332     //NSLog(@"%s %@", _cmd, string);
333     // NOTE!  This method is called for normal key presses but also for
334     // Option-key presses --- even when Ctrl is held as well as Option.  When
335     // Ctrl is held, the AppKit translates the character to a Ctrl+key stroke,
336     // so 'string' need not be a printable character!  In this case it still
337     // works to pass 'string' on to Vim as a printable character (since
338     // modifiers are already included and should not be added to the input
339     // buffer using CSI, K_MODIFIER).
341     [self hideMarkedTextField];
343     NSEvent *event = [NSApp currentEvent];
345     // HACK!  In order to be able to bind to <S-Space>, <S-M-Tab>, etc. we have
346     // to watch for them here.
347     if ([event type] == NSKeyDown
348             && [[event charactersIgnoringModifiers] length] > 0
349             && [event modifierFlags]
350                 & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask)) {
351         unichar c = [[event charactersIgnoringModifiers] characterAtIndex:0];
353         // <S-M-Tab> translates to 0x19 
354         if (' ' == c || 0x19 == c) {
355             [self dispatchKeyEvent:event];
356             return;
357         }
358     }
360     [self hideMouseCursor];
362     // NOTE: 'string' is either an NSString or an NSAttributedString.  Since we
363     // do not support attributes, simply pass the corresponding NSString in the
364     // latter case.
365     if ([string isKindOfClass:[NSAttributedString class]])
366         string = [string string];
368     //NSLog(@"send InsertTextMsgID: %@", string);
370     [[self vimController] sendMessage:InsertTextMsgID
371                  data:[string dataUsingEncoding:NSUTF8StringEncoding]];
374 - (void)doCommandBySelector:(SEL)selector
376     //NSLog(@"%s %@", _cmd, NSStringFromSelector(selector));
377     // By ignoring the selector we effectively disable the key binding
378     // mechanism of Cocoa.  Hopefully this is what the user will expect
379     // (pressing Ctrl+P would otherwise result in moveUp: instead of previous
380     // match, etc.).
381     //
382     // We usually end up here if the user pressed Ctrl+key (but not
383     // Ctrl+Option+key).
385     NSEvent *event = [NSApp currentEvent];
387     if (selector == @selector(cancelOperation:)
388             || selector == @selector(insertNewline:)) {
389         // HACK! If there was marked text which got abandoned as a result of
390         // hitting escape or enter, then 'insertText:' is called with the
391         // abandoned text but '[event characters]' includes the abandoned text
392         // as well.  Since 'dispatchKeyEvent:' looks at '[event characters]' we
393         // must intercept these keys here or the abandonded text gets inserted
394         // twice.
395         NSString *key = [event charactersIgnoringModifiers];
396         const char *chars = [key UTF8String];
397         int len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
399         if (0x3 == chars[0]) {
400             // HACK! AppKit turns enter (not return) into Ctrl-C, so we need to
401             // handle it separately (else Ctrl-C doesn't work).
402             len = sizeof(MMKeypadEnter)/sizeof(MMKeypadEnter[0]);
403             chars = MMKeypadEnter;
404         }
406         [self sendKeyDown:chars length:len modifiers:[event modifierFlags]];
407     } else {
408         [self dispatchKeyEvent:event];
409     }
412 - (BOOL)performKeyEquivalent:(NSEvent *)event
414     //NSLog(@"%s %@", _cmd, event);
415     // Called for Cmd+key keystrokes, function keys, arrow keys, page
416     // up/down, home, end.
417     //
418     // NOTE: This message cannot be ignored since Cmd+letter keys never are
419     // passed to keyDown:.  It seems as if the main menu consumes Cmd-key
420     // strokes, unless the key is a function key.
422     // NOTE: If the event that triggered this method represents a function key
423     // down then we do nothing, otherwise the input method never gets the key
424     // stroke (some input methods use e.g. arrow keys).  The function key down
425     // event will still reach Vim though (via keyDown:).  The exceptions to
426     // this rule are: PageUp/PageDown (keycode 116/121).
427     int flags = [event modifierFlags];
428     if ([event type] != NSKeyDown || flags & NSFunctionKeyMask
429             && !(116 == [event keyCode] || 121 == [event keyCode]))
430         return NO;
432     // HACK!  KeyCode 50 represent the key which switches between windows
433     // within an application (like Cmd+Tab is used to switch between
434     // applications).  Return NO here, else the window switching does not work.
435     if ([event keyCode] == 50)
436         return NO;
438     // HACK!  Let the main menu try to handle any key down event, before
439     // passing it on to vim, otherwise key equivalents for menus will
440     // effectively be disabled.
441     if ([[NSApp mainMenu] performKeyEquivalent:event])
442         return YES;
444     // HACK!  On Leopard Ctrl-key events end up here instead of keyDown:.
445     if (flags & NSControlKeyMask) {
446         [self keyDown:event];
447         return YES;
448     }
450     // HACK!  Don't handle Cmd-? or the "Help" menu does not work on Leopard.
451     NSString *unmodchars = [event charactersIgnoringModifiers];
452     if ([unmodchars isEqual:@"?"])
453         return NO;
455     //NSLog(@"%s%@", _cmd, event);
457     NSString *chars = [event characters];
458     int len = [unmodchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
459     NSMutableData *data = [NSMutableData data];
461     if (len <= 0)
462         return NO;
464     // If 'chars' and 'unmodchars' differs when shift flag is present, then we
465     // can clear the shift flag as it is already included in 'unmodchars'.
466     // Failing to clear the shift flag means <D-Bar> turns into <S-D-Bar> (on
467     // an English keyboard).
468     if (flags & NSShiftKeyMask && ![chars isEqual:unmodchars])
469         flags &= ~NSShiftKeyMask;
471     if (0x3 == [unmodchars characterAtIndex:0]) {
472         // HACK! AppKit turns enter (not return) into Ctrl-C, so we need to
473         // handle it separately (else Cmd-enter turns into Ctrl-C).
474         unmodchars = MMKeypadEnterString;
475         len = [unmodchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
476     }
478     [data appendBytes:&flags length:sizeof(int)];
479     [data appendBytes:&len length:sizeof(int)];
480     [data appendBytes:[unmodchars UTF8String] length:len];
482     [[self vimController] sendMessage:CmdKeyMsgID data:data];
484     return YES;
490 - (BOOL)acceptsFirstResponder
492     return YES;
495 - (BOOL)isFlipped
497     return NO;
500 - (void)drawRect:(NSRect)rect
502     NSRect srcRect = NSMakeRect(0, 0, imageSize.width, imageSize.height);
503     NSRect dstRect = srcRect;
505     dstRect.origin.x += insetSize.width;
506     dstRect.origin.y += insetSize.height;
508     [defaultBackgroundColor set];
509     NSRectFill(rect);
511     [contentImage drawInRect: dstRect
512                     fromRect: srcRect
513                    operation: NSCompositeCopy
514                     fraction: 1.0];
517 - (BOOL) wantsDefaultClipping
519     return NO;
523 #define MM_DEBUG_DRAWING 0
525 - (void)performBatchDrawWithData:(NSData *)data
527     const void *bytes = [data bytes];
528     const void *end = bytes + [data length];
530     if (! NSEqualSizes(imageSize, [self textAreaSize]))
531         [self resizeContentImage];
533 #if MM_DEBUG_DRAWING
534     NSLog(@"====> BEGIN %s", _cmd);
535 #endif
536     [self beginDrawing];
538     // TODO: Sanity check input
540     while (bytes < end) {
541         int type = *((int*)bytes);  bytes += sizeof(int);
543         if (ClearAllDrawType == type) {
544 #if MM_DEBUG_DRAWING
545             NSLog(@"   Clear all");
546 #endif
547             [self clearAll];
548         } else if (ClearBlockDrawType == type) {
549             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
550             int row1 = *((int*)bytes);  bytes += sizeof(int);
551             int col1 = *((int*)bytes);  bytes += sizeof(int);
552             int row2 = *((int*)bytes);  bytes += sizeof(int);
553             int col2 = *((int*)bytes);  bytes += sizeof(int);
555 #if MM_DEBUG_DRAWING
556             NSLog(@"   Clear block (%d,%d) -> (%d,%d)", row1, col1,
557                     row2,col2);
558 #endif
559             [self clearBlockFromRow:row1 column:col1
560                     toRow:row2 column:col2
561                     color:[NSColor colorWithArgbInt:color]];
562         } else if (DeleteLinesDrawType == type) {
563             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
564             int row = *((int*)bytes);  bytes += sizeof(int);
565             int count = *((int*)bytes);  bytes += sizeof(int);
566             int bot = *((int*)bytes);  bytes += sizeof(int);
567             int left = *((int*)bytes);  bytes += sizeof(int);
568             int right = *((int*)bytes);  bytes += sizeof(int);
570 #if MM_DEBUG_DRAWING
571             NSLog(@"   Delete %d line(s) from %d", count, row);
572 #endif
573             [self deleteLinesFromRow:row lineCount:count
574                     scrollBottom:bot left:left right:right
575                            color:[NSColor colorWithArgbInt:color]];
576         } else if (DrawStringDrawType == type) {
577             int bg = *((int*)bytes);  bytes += sizeof(int);
578             int fg = *((int*)bytes);  bytes += sizeof(int);
579             int sp = *((int*)bytes);  bytes += sizeof(int);
580             int row = *((int*)bytes);  bytes += sizeof(int);
581             int col = *((int*)bytes);  bytes += sizeof(int);
582             int cells = *((int*)bytes);  bytes += sizeof(int);
583             int flags = *((int*)bytes);  bytes += sizeof(int);
584             int len = *((int*)bytes);  bytes += sizeof(int);
585             // UniChar *string = (UniChar*)bytes;  bytes += len;
586             NSString *string = [[NSString alloc]
587                     initWithBytesNoCopy:(void*)bytes
588                                  length:len
589                                encoding:NSUTF8StringEncoding
590                            freeWhenDone:NO];
591             bytes += len;
592 #if MM_DEBUG_DRAWING
593             NSLog(@"   Draw string at (%d,%d) length=%d flags=%d fg=0x%x "
594                     "bg=0x%x sp=0x%x", row, col, len, flags, fg, bg, sp);
595 #endif
596             unichar *characters = malloc(sizeof(unichar) * [string length]);
597             [string getCharacters:characters];
599             [self drawString:characters
600                              length:[string length]
601                               atRow:row
602                              column:col
603                               cells:cells withFlags:flags
604                     foregroundColor:[NSColor colorWithRgbInt:fg]
605                     backgroundColor:[NSColor colorWithArgbInt:bg]
606                        specialColor:[NSColor colorWithRgbInt:sp]];
607             free(characters);
608             [string release];
609         } else if (InsertLinesDrawType == type) {
610             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
611             int row = *((int*)bytes);  bytes += sizeof(int);
612             int count = *((int*)bytes);  bytes += sizeof(int);
613             int bot = *((int*)bytes);  bytes += sizeof(int);
614             int left = *((int*)bytes);  bytes += sizeof(int);
615             int right = *((int*)bytes);  bytes += sizeof(int);
617 #if MM_DEBUG_DRAWING
618             NSLog(@"   Insert %d line(s) at row %d", count, row);
619 #endif
620             [self insertLinesAtRow:row lineCount:count
621                              scrollBottom:bot left:left right:right
622                                     color:[NSColor colorWithArgbInt:color]];
623         } else if (DrawCursorDrawType == type) {
624             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
625             int row = *((int*)bytes);  bytes += sizeof(int);
626             int col = *((int*)bytes);  bytes += sizeof(int);
627             int shape = *((int*)bytes);  bytes += sizeof(int);
628             int percent = *((int*)bytes);  bytes += sizeof(int);
630 #if MM_DEBUG_DRAWING
631             NSLog(@"   Draw cursor at (%d,%d)", row, col);
632 #endif
633             [self drawInsertionPointAtRow:row column:col shape:shape
634                                      fraction:percent
635                                         color:[NSColor colorWithRgbInt:color]];
636         } else if (DrawInvertedRectDrawType == type) {
637             int row = *((int*)bytes);  bytes += sizeof(int);
638             int col = *((int*)bytes);  bytes += sizeof(int);
639             int nr = *((int*)bytes);  bytes += sizeof(int);
640             int nc = *((int*)bytes);  bytes += sizeof(int);
641             /*int invert = *((int*)bytes);*/  bytes += sizeof(int);
643 #if MM_DEBUG_DRAWING
644             NSLog(@"   Draw inverted rect: row=%d col=%d nrows=%d ncols=%d",
645                     row, col, nr, nc);
646 #endif
647             [self drawInvertedRectAtRow:row column:col numRows:nr
648                              numColumns:nc];
649         } else if (SetCursorPosDrawType == type) {
650             // TODO: This is used for Voice Over support in MMTextView,
651             // MMAtsuiTextView currently does not support Voice Over.
652             /*cursorRow = *((int*)bytes);*/  bytes += sizeof(int);
653             /*cursorCol = *((int*)bytes);*/  bytes += sizeof(int);
654         } else {
655             NSLog(@"WARNING: Unknown draw type (type=%d)", type);
656         }
657     }
659     [self endDrawing];
661     // NOTE: During resizing, Cocoa only sends draw messages before Vim's rows
662     // and columns are changed (due to ipc delays). Force a redraw here.
663     [self setNeedsDisplay:YES];
664     // [self displayIfNeeded];
666 #if MM_DEBUG_DRAWING
667     NSLog(@"<==== END   %s", _cmd);
668 #endif
671 - (NSSize)constrainRows:(int *)rows columns:(int *)cols toSize:(NSSize)size
673     // TODO:
674     // - Rounding errors may cause size change when there should be none
675     // - Desired rows/columns shold not be 'too small'
677     // Constrain the desired size to the given size.  Values for the minimum
678     // rows and columns is taken from Vim.
679     NSSize desiredSize = [self desiredSize];
680     int desiredRows = maxRows;
681     int desiredCols = maxColumns;
683     if (size.height != desiredSize.height) {
684         float fh = cellSize.height;
685         float ih = 2 * insetSize.height;
686         if (fh < 1.0f) fh = 1.0f;
688         desiredRows = floor((size.height - ih)/fh);
689         desiredSize.height = fh*desiredRows + ih;
690     }
692     if (size.width != desiredSize.width) {
693         float fw = cellSize.width;
694         float iw = 2 * insetSize.width;
695         if (fw < 1.0f) fw = 1.0f;
697         desiredCols = floor((size.width - iw)/fw);
698         desiredSize.width = fw*desiredCols + iw;
699     }
701     if (rows) *rows = desiredRows;
702     if (cols) *cols = desiredCols;
704     return desiredSize;
707 - (NSSize)desiredSize
709     // Compute the size the text view should be for the entire text area and
710     // inset area to be visible with the present number of rows and columns.
711     return NSMakeSize(maxColumns * cellSize.width + 2 * insetSize.width,
712                       maxRows * cellSize.height + 2 * insetSize.height);
715 - (NSSize)minSize
717     // Compute the smallest size the text view is allowed to be.
718     return NSMakeSize(MMMinColumns * cellSize.width + 2 * insetSize.width,
719                       MMMinRows * cellSize.height + 2 * insetSize.height);
722 - (void)changeFont:(id)sender
724     NSFont *newFont = [sender convertFont:font];
726     if (newFont) {
727         NSString *name = [newFont displayName];
728         unsigned len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
729         if (len > 0) {
730             NSMutableData *data = [NSMutableData data];
731             float pointSize = [newFont pointSize];
733             [data appendBytes:&pointSize length:sizeof(float)];
735             ++len;  // include NUL byte
736             [data appendBytes:&len length:sizeof(unsigned)];
737             [data appendBytes:[name UTF8String] length:len];
739             [[self vimController] sendMessage:SetFontMsgID data:data];
740         }
741     }
744 - (void)scrollWheel:(NSEvent *)event
746     if ([event deltaY] == 0)
747         return;
749     int row, col;
750     NSPoint pt = [self convertPoint:[event locationInWindow] fromView:nil];
752     // View is not flipped, instead the atsui code draws to a flipped image;
753     // thus we need to 'flip' the coordinate here since the column number
754     // increases in an up-to-down order.
755     pt.y = [self frame].size.height - pt.y;
757     if (![self convertPoint:pt toRow:&row column:&col])
758         return;
760     int flags = [event modifierFlags];
761     float dy = [event deltaY];
762     NSMutableData *data = [NSMutableData data];
764     [data appendBytes:&row length:sizeof(int)];
765     [data appendBytes:&col length:sizeof(int)];
766     [data appendBytes:&flags length:sizeof(int)];
767     [data appendBytes:&dy length:sizeof(float)];
769     [[self vimController] sendMessage:ScrollWheelMsgID data:data];
774 // NOTE: The menu items cut/copy/paste/undo/redo/select all/... must be bound
775 // to the same actions as in IB otherwise they will not work with dialogs.  All
776 // we do here is forward these actions to the Vim process.
778 - (IBAction)cut:(id)sender
780     [[self windowController] vimMenuItemAction:sender];
783 - (IBAction)copy:(id)sender
785     [[self windowController] vimMenuItemAction:sender];
788 - (IBAction)paste:(id)sender
790     [[self windowController] vimMenuItemAction:sender];
793 - (IBAction)undo:(id)sender
795     [[self windowController] vimMenuItemAction:sender];
798 - (IBAction)redo:(id)sender
800     [[self windowController] vimMenuItemAction:sender];
803 - (IBAction)selectAll:(id)sender
805     [[self windowController] vimMenuItemAction:sender];
808 @end // MMAtsuiTextView
813 @implementation MMAtsuiTextView (Private)
815 - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column
817     NSPoint origin = { insetSize.width, insetSize.height };
819     if (!(cellSize.width > 0 && cellSize.height > 0))
820         return NO;
822     if (row) *row = floor((point.y-origin.y-1) / cellSize.height);
823     if (column) *column = floor((point.x-origin.x-1) / cellSize.width);
825     //NSLog(@"convertPoint:%@ toRow:%d column:%d", NSStringFromPoint(point),
826     //        *row, *column);
828     return YES;
831 - (void)initAtsuStyles
833     int i;
834     for (i = 0; i < MMMaxCellsPerChar; i++)
835         ATSUCreateStyle(&atsuStyles[i]);
838 - (void)disposeAtsuStyles
840     int i;
842     for (i = 0; i < MMMaxCellsPerChar; i++)
843         if (atsuStyles[i] != NULL)
844         {
845             if (ATSUDisposeStyle(atsuStyles[i]) != noErr)
846                 atsuStyles[i] = NULL;
847         }
850 - (void)updateAtsuStyles
852     ATSUFontID        fontID;
853     Fixed             fontSize;
854     Fixed             fontWidth;
855     int               i;
856     CGAffineTransform transform = CGAffineTransformMakeScale(1, -1);
857     ATSStyleRenderingOptions options;
859     fontID    = [font _atsFontID];
860     fontSize  = Long2Fix([font pointSize]);
861     options   = kATSStyleApplyAntiAliasing;
863     ATSUAttributeTag attribTags[] =
864     {
865         kATSUFontTag, kATSUSizeTag, kATSUImposeWidthTag,
866         kATSUFontMatrixTag, kATSUStyleRenderingOptionsTag,
867         kATSUMaxATSUITagValue + 1
868     };
870     ByteCount attribSizes[] =
871     {
872         sizeof(ATSUFontID), sizeof(Fixed), sizeof(fontWidth),
873         sizeof(CGAffineTransform), sizeof(ATSStyleRenderingOptions),
874         sizeof(font)
875     };
877     ATSUAttributeValuePtr attribValues[] =
878     {
879         &fontID, &fontSize, &fontWidth, &transform, &options, &font
880     };
882     ATSUFontFeatureType featureTypes[] = {
883         kLigaturesType, kLigaturesType
884     };
886     ATSUFontFeatureSelector featureSelectors[] = {
887         kCommonLigaturesOffSelector, kRareLigaturesOffSelector
888     };
890     for (i = 0; i < MMMaxCellsPerChar; i++)
891     {
892         fontWidth = Long2Fix(cellSize.width * (i + 1));
894         if (ATSUSetAttributes(atsuStyles[i],
895                               (sizeof attribTags) / sizeof(ATSUAttributeTag),
896                               attribTags, attribSizes, attribValues) != noErr)
897         {
898             ATSUDisposeStyle(atsuStyles[i]);
899             atsuStyles[i] = NULL;
900         }
902         // Turn off ligatures by default
903         ATSUSetFontFeatures(atsuStyles[i],
904                             sizeof(featureTypes) / sizeof(featureTypes[0]),
905                             featureTypes, featureSelectors);
906     }
909 - (void)dispatchKeyEvent:(NSEvent *)event
911     // Only handle the command if it came from a keyDown event
912     if ([event type] != NSKeyDown)
913         return;
915     NSString *chars = [event characters];
916     NSString *unmodchars = [event charactersIgnoringModifiers];
917     unichar c = [chars characterAtIndex:0];
918     unichar imc = [unmodchars characterAtIndex:0];
919     int len = 0;
920     const char *bytes = 0;
921     int mods = [event modifierFlags];
923     //NSLog(@"%s chars[0]=0x%x unmodchars[0]=0x%x (chars=%@ unmodchars=%@)",
924     //        _cmd, c, imc, chars, unmodchars);
926     if (' ' == imc && 0xa0 != c) {
927         // HACK!  The AppKit turns <C-Space> into <C-@> which is not standard
928         // Vim behaviour, so bypass this problem.  (0xa0 is <M-Space>, which
929         // should be passed on as is.)
930         len = [unmodchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
931         bytes = [unmodchars UTF8String];
932     } else if (imc == c && '2' == c) {
933         // HACK!  Translate Ctrl+2 to <C-@>.
934         static char ctrl_at = 0;
935         len = 1;  bytes = &ctrl_at;
936     } else if (imc == c && '6' == c) {
937         // HACK!  Translate Ctrl+6 to <C-^>.
938         static char ctrl_hat = 0x1e;
939         len = 1;  bytes = &ctrl_hat;
940     } else if (c == 0x19 && imc == 0x19) {
941         // HACK! AppKit turns back tab into Ctrl-Y, so we need to handle it
942         // separately (else Ctrl-Y doesn't work).
943         static char tab = 0x9;
944         len = 1;  bytes = &tab;  mods |= NSShiftKeyMask;
945     } else {
946         len = [chars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
947         bytes = [chars UTF8String];
948     }
950     [self sendKeyDown:bytes length:len modifiers:mods];
953 - (void)sendKeyDown:(const char *)chars length:(int)len modifiers:(int)flags
955     if (chars && len > 0) {
956         NSMutableData *data = [NSMutableData data];
958         [data appendBytes:&flags length:sizeof(int)];
959         [data appendBytes:&len length:sizeof(int)];
960         [data appendBytes:chars length:len];
962         [self hideMouseCursor];
964         //NSLog(@"%s len=%d chars=0x%x", _cmd, len, chars[0]);
965         [[self vimController] sendMessage:KeyDownMsgID data:data];
966     }
969 - (void)hideMouseCursor
971     // Check 'mousehide' option
972     id mh = [[[self vimController] vimState] objectForKey:@"p_mh"];
973     if (mh && ![mh boolValue])
974         [NSCursor setHiddenUntilMouseMoves:NO];
975     else
976         [NSCursor setHiddenUntilMouseMoves:YES];
979 - (MMWindowController *)windowController
981     id windowController = [[self window] windowController];
982     if ([windowController isKindOfClass:[MMWindowController class]])
983         return (MMWindowController*)windowController;
984     return nil;
987 - (MMVimController *)vimController
989     return [[self windowController] vimController];
992 @end // MMAtsuiTextView (Private)
997 @implementation MMAtsuiTextView (Drawing)
999 - (NSPoint)originForRow:(int)row column:(int)col
1001     return NSMakePoint(col * cellSize.width, row * cellSize.height);
1004 - (NSRect)rectFromRow:(int)row1 column:(int)col1
1005                 toRow:(int)row2 column:(int)col2
1007     NSPoint origin = [self originForRow: row1 column: col1];
1008     return NSMakeRect(origin.x, origin.y,
1009                       (col2 + 1 - col1) * cellSize.width,
1010                       (row2 + 1 - row1) * cellSize.height);
1013 - (NSSize)textAreaSize
1015     // Calculate the (desired) size of the text area, i.e. the text view area
1016     // minus the inset area.
1017     return NSMakeSize(maxColumns * cellSize.width, maxRows * cellSize.height);
1020 - (void)resizeContentImage
1022     //NSLog(@"resizeContentImage");
1023     [contentImage release];
1024     contentImage = [[NSImage alloc] initWithSize:[self textAreaSize]];
1025     [contentImage setFlipped: YES];
1026     imageSize = [self textAreaSize];
1029 - (void)beginDrawing
1031     [contentImage lockFocus];
1034 - (void)endDrawing
1036     [contentImage unlockFocus];
1039 #define atsu_style_set_bool(s, t, b) \
1040     ATSUSetAttributes(s, 1, &t, &(sizeof(Boolean)), &&b);
1041 #define FILL_Y(y)    (y * cellSize.height)
1043 - (void)drawString:(UniChar *)string length:(UniCharCount)length
1044              atRow:(int)row column:(int)col cells:(int)cells
1045          withFlags:(int)flags foregroundColor:(NSColor *)fg
1046    backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp
1048     // 'string' consists of 'length' utf-16 code pairs and should cover 'cells'
1049     // display cells (a normal character takes up one display cell, a wide
1050     // character takes up two)
1051     ATSUStyle       style = (flags & DRAW_WIDE) ? atsuStyles[1] : atsuStyles[0];
1052     ATSUTextLayout  layout;
1054     // Font selection and rendering options for ATSUI
1055     ATSUAttributeTag      attribTags[3] = { kATSUQDBoldfaceTag,
1056                                             kATSUFontMatrixTag,
1057                                             kATSUStyleRenderingOptionsTag };
1059     ByteCount             attribSizes[] = { sizeof(Boolean),
1060                                             sizeof(CGAffineTransform),
1061                                             sizeof(UInt32) };
1062     Boolean               useBold;
1063     CGAffineTransform     theTransform = CGAffineTransformMakeScale(1.0, -1.0);
1064     UInt32                useAntialias;
1066     ATSUAttributeValuePtr attribValues[3] = { &useBold, &theTransform,
1067                                               &useAntialias };
1069     useBold      = (flags & DRAW_BOLD) ? true : false;
1071     if (flags & DRAW_ITALIC)
1072         theTransform.c = Fix2X(kATSItalicQDSkew);
1074     useAntialias = antialias ? kATSStyleApplyAntiAliasing
1075                              : kATSStyleNoAntiAliasing;
1077     ATSUSetAttributes(style, sizeof(attribValues) / sizeof(attribValues[0]),
1078                       attribTags, attribSizes, attribValues);
1080     // NSLog(@"drawString: %d", length);
1082     ATSUCreateTextLayout(&layout);
1083     ATSUSetTextPointerLocation(layout, string,
1084                                kATSUFromTextBeginning, kATSUToTextEnd,
1085                                length);
1086     ATSUSetRunStyle(layout, style, kATSUFromTextBeginning, kATSUToTextEnd);
1088     NSRect rect = NSMakeRect(col * cellSize.width, row * cellSize.height,
1089                              length * cellSize.width, cellSize.height);
1090     if (flags & DRAW_WIDE)
1091         rect.size.width = rect.size.width * 2;
1092     CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
1094     ATSUAttributeTag tags[] = { kATSUCGContextTag };
1095     ByteCount sizes[] = { sizeof(CGContextRef) };
1096     ATSUAttributeValuePtr values[] = { &context };
1097     ATSUSetLayoutControls(layout, 1, tags, sizes, values);
1099     if (! (flags & DRAW_TRANSP))
1100     {
1101         [bg set];
1102         NSRectFill(rect);
1103     }
1105     [fg set];
1107     ATSUSetTransientFontMatching(layout, TRUE);
1108     ATSUDrawText(layout,
1109                  kATSUFromTextBeginning,
1110                  kATSUToTextEnd,
1111                  X2Fix(rect.origin.x),
1112                  X2Fix(rect.origin.y + [font ascender]));
1113     ATSUDisposeTextLayout(layout);
1115     if (flags & DRAW_UNDERL)
1116     {
1117         [fg set];
1118         NSRectFill(NSMakeRect(rect.origin.x,
1119                               (row + 1) * cellSize.height + kUnderlineOffset,
1120                               rect.size.width, kUnderlineHeight));
1121     }
1123     if (flags & DRAW_UNDERC)
1124     {
1125         [sp set];
1127         float line_end_x = rect.origin.x + rect.size.width;
1128         int i = 0;
1129         NSRect line_rect = NSMakeRect(
1130                 rect.origin.x,
1131                 (row + 1) * cellSize.height + kUndercurlOffset,
1132                 kUndercurlDotWidth, kUndercurlHeight);
1134         while (line_rect.origin.x < line_end_x)
1135         {
1136             if (i % 2)
1137                 NSRectFill(line_rect);
1139             line_rect.origin.x += kUndercurlDotDistance;
1140             i++;
1141         }
1142     }
1145 - (void)scrollRect:(NSRect)rect lineCount:(int)count
1147     NSPoint destPoint = rect.origin;
1148     destPoint.y += count * cellSize.height;
1150     NSCopyBits(0, rect, destPoint);
1153 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
1154               scrollBottom:(int)bottom left:(int)left right:(int)right
1155                      color:(NSColor *)color
1157     NSRect rect = [self rectFromRow:row + count
1158                              column:left
1159                               toRow:bottom
1160                              column:right];
1161     [color set];
1162     // move rect up for count lines
1163     [self scrollRect:rect lineCount:-count];
1164     [self clearBlockFromRow:bottom - count + 1
1165                      column:left
1166                       toRow:bottom
1167                      column:right
1168                       color:color];
1171 - (void)insertLinesAtRow:(int)row lineCount:(int)count
1172             scrollBottom:(int)bottom left:(int)left right:(int)right
1173                    color:(NSColor *)color
1175     NSRect rect = [self rectFromRow:row
1176                              column:left
1177                               toRow:bottom - count
1178                              column:right];
1179     [color set];
1180     // move rect down for count lines
1181     [self scrollRect:rect lineCount:count];
1182     [self clearBlockFromRow:row
1183                      column:left
1184                       toRow:row + count - 1
1185                      column:right
1186                       color:color];
1189 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
1190                    column:(int)col2 color:(NSColor *)color
1192     [color set];
1193     NSRectFill([self rectFromRow:row1 column:col1 toRow:row2 column:col2]);
1196 - (void)clearAll
1198     [defaultBackgroundColor set];
1199     NSRectFill(NSMakeRect(0, 0, imageSize.width, imageSize.height));
1202 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
1203                        fraction:(int)percent color:(NSColor *)color
1205     NSPoint origin = [self originForRow:row column:col];
1206     NSRect rect = NSMakeRect(origin.x, origin.y,
1207                              cellSize.width, cellSize.height);
1209     // NSLog(@"shape = %d, fraction: %d", shape, percent);
1211     if (MMInsertionPointHorizontal == shape) {
1212         int frac = (cellSize.height * percent + 99)/100;
1213         rect.origin.y += rect.size.height - frac;
1214         rect.size.height = frac;
1215     } else if (MMInsertionPointVertical == shape) {
1216         int frac = (cellSize.width * percent + 99)/100;
1217         rect.size.width = frac;
1218     } else if (MMInsertionPointVerticalRight == shape) {
1219         int frac = (cellSize.width * percent + 99)/100;
1220         rect.origin.x += rect.size.width - frac;
1221         rect.size.width = frac;
1222     }
1224     [color set];
1225     if (MMInsertionPointHollow == shape) {
1226         NSFrameRect(rect);
1227     } else {
1228         NSRectFill(rect);
1229     }
1232 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nrows
1233                    numColumns:(int)ncols
1235     // TODO: THIS CODE HAS NOT BEEN TESTED!
1236     CGContextRef cgctx = [[NSGraphicsContext currentContext] graphicsPort];
1237     CGContextSaveGState(cgctx);
1238     CGContextSetBlendMode(cgctx, kCGBlendModeDifference);
1239     CGContextSetRGBFillColor(cgctx, 1.0, 1.0, 1.0, 1.0);
1241     CGRect rect = { col * cellSize.width, row * cellSize.height,
1242                     ncols * cellSize.width, nrows * cellSize.height };
1243     CGContextFillRect(cgctx, rect);
1245     CGContextRestoreGState(cgctx);
1248 @end // MMAtsuiTextView (Drawing)