1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
3 * VIM - Vi IMproved by Bram Moolenaar
4 * MacVim GUI port by Bjorn Winckler
6 * Do ":help uganda" in Vim to read copying and usage conditions.
7 * Do ":help credits" in Vim to see a list of people who contributed.
8 * See README.txt for an overview of the Vim source code.
13 * Dispatches keyboard and mouse input to the backend. Handles drag-n-drop of
14 * files onto window. The rendering is done using ATSUI.
16 * The text view area consists of two parts:
17 * 1. The text area - this is where text is rendered; the size is governed by
18 * the current number of rows and columns.
19 * 2. The inset area - this is a border around the text area; the size is
20 * governed by the user defaults MMTextInset[Left|Right|Top|Bottom].
22 * The current size of the text view frame does not always match the desired
23 * area, i.e. the area determined by the number of rows, columns plus text
24 * inset. This distinction is particularly important when the view is being
28 #import "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";
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).
64 @interface NSFont (AppKitPrivate)
65 - (ATSUFontID) _atsFontID;
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;
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;
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;
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;
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];
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;
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.
140 [self initAtsuStyles];
148 [self disposeAtsuStyles];
149 [font release]; font = nil;
150 [defaultBackgroundColor release]; defaultBackgroundColor = nil;
151 [defaultForegroundColor release]; defaultForegroundColor = nil;
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.
169 - (void)setDefaultColorsBackground:(NSColor *)bgColor
170 foreground:(NSColor *)fgColor
172 if (defaultBackgroundColor != bgColor) {
173 [defaultBackgroundColor release];
174 defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
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
180 if (defaultForegroundColor != fgColor) {
181 [defaultForegroundColor release];
182 defaultForegroundColor = fgColor ? [fgColor retain] : nil;
186 - (void)setTextContainerInset:(NSSize)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;
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;
222 - (void)setFont:(NSFont *)newFont
224 if (newFont && font != newFont) {
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];
245 - (void)setWideFont:(NSFont *)newFont
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
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
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.
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]];
323 [self dispatchKeyEvent:event];
326 [self interpretKeyEvents:[NSArray arrayWithObject:event]];
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];
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
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
382 // We usually end up here if the user pressed Ctrl+key (but not
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
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;
406 [self sendKeyDown:chars length:len modifiers:[event modifierFlags]];
408 [self dispatchKeyEvent:event];
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.
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]))
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)
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])
444 // HACK! On Leopard Ctrl-key events end up here instead of keyDown:.
445 if (flags & NSControlKeyMask) {
446 [self keyDown:event];
450 // HACK! Don't handle Cmd-? or the "Help" menu does not work on Leopard.
451 NSString *unmodchars = [event charactersIgnoringModifiers];
452 if ([unmodchars isEqual:@"?"])
455 //NSLog(@"%s%@", _cmd, event);
457 NSString *chars = [event characters];
458 int len = [unmodchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
459 NSMutableData *data = [NSMutableData data];
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];
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];
490 - (BOOL)acceptsFirstResponder
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];
511 [contentImage drawInRect: dstRect
513 operation: NSCompositeCopy
517 - (BOOL) wantsDefaultClipping
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];
534 NSLog(@"====> BEGIN %s", _cmd);
538 // TODO: Sanity check input
540 while (bytes < end) {
541 int type = *((int*)bytes); bytes += sizeof(int);
543 if (ClearAllDrawType == type) {
545 NSLog(@" Clear all");
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);
556 NSLog(@" Clear block (%d,%d) -> (%d,%d)", row1, col1,
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);
571 NSLog(@" Delete %d line(s) from %d", count, row);
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
589 encoding:NSUTF8StringEncoding
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);
596 unichar *characters = malloc(sizeof(unichar) * [string length]);
597 [string getCharacters:characters];
599 [self drawString:characters
600 length:[string length]
603 cells:cells withFlags:flags
604 foregroundColor:[NSColor colorWithRgbInt:fg]
605 backgroundColor:[NSColor colorWithArgbInt:bg]
606 specialColor:[NSColor colorWithRgbInt:sp]];
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);
618 NSLog(@" Insert %d line(s) at row %d", count, row);
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);
631 NSLog(@" Draw cursor at (%d,%d)", row, col);
633 [self drawInsertionPointAtRow:row column:col shape:shape
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);
644 NSLog(@" Draw inverted rect: row=%d col=%d nrows=%d ncols=%d",
647 [self drawInvertedRectAtRow:row column:col numRows:nr
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);
655 NSLog(@"WARNING: Unknown draw type (type=%d)", type);
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];
667 NSLog(@"<==== END %s", _cmd);
671 - (NSSize)constrainRows:(int *)rows columns:(int *)cols toSize:(NSSize)size
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;
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;
701 if (rows) *rows = desiredRows;
702 if (cols) *cols = desiredCols;
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);
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];
727 NSString *name = [newFont displayName];
728 unsigned len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
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];
744 - (void)scrollWheel:(NSEvent *)event
746 if ([event deltaY] == 0)
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])
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))
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),
831 - (void)initAtsuStyles
834 for (i = 0; i < MMMaxCellsPerChar; i++)
835 ATSUCreateStyle(&atsuStyles[i]);
838 - (void)disposeAtsuStyles
842 for (i = 0; i < MMMaxCellsPerChar; i++)
843 if (atsuStyles[i] != NULL)
845 if (ATSUDisposeStyle(atsuStyles[i]) != noErr)
846 atsuStyles[i] = NULL;
850 - (void)updateAtsuStyles
856 CGAffineTransform transform = CGAffineTransformMakeScale(1, -1);
857 ATSStyleRenderingOptions options;
859 fontID = [font _atsFontID];
860 fontSize = Long2Fix([font pointSize]);
861 options = kATSStyleApplyAntiAliasing;
863 ATSUAttributeTag attribTags[] =
865 kATSUFontTag, kATSUSizeTag, kATSUImposeWidthTag,
866 kATSUFontMatrixTag, kATSUStyleRenderingOptionsTag,
867 kATSUMaxATSUITagValue + 1
870 ByteCount attribSizes[] =
872 sizeof(ATSUFontID), sizeof(Fixed), sizeof(fontWidth),
873 sizeof(CGAffineTransform), sizeof(ATSStyleRenderingOptions),
877 ATSUAttributeValuePtr attribValues[] =
879 &fontID, &fontSize, &fontWidth, &transform, &options, &font
882 ATSUFontFeatureType featureTypes[] = {
883 kLigaturesType, kLigaturesType
886 ATSUFontFeatureSelector featureSelectors[] = {
887 kCommonLigaturesOffSelector, kRareLigaturesOffSelector
890 for (i = 0; i < MMMaxCellsPerChar; i++)
892 fontWidth = Long2Fix(cellSize.width * (i + 1));
894 if (ATSUSetAttributes(atsuStyles[i],
895 (sizeof attribTags) / sizeof(ATSUAttributeTag),
896 attribTags, attribSizes, attribValues) != noErr)
898 ATSUDisposeStyle(atsuStyles[i]);
899 atsuStyles[i] = NULL;
902 // Turn off ligatures by default
903 ATSUSetFontFeatures(atsuStyles[i],
904 sizeof(featureTypes) / sizeof(featureTypes[0]),
905 featureTypes, featureSelectors);
909 - (void)dispatchKeyEvent:(NSEvent *)event
911 // Only handle the command if it came from a keyDown event
912 if ([event type] != NSKeyDown)
915 NSString *chars = [event characters];
916 NSString *unmodchars = [event charactersIgnoringModifiers];
917 unichar c = [chars characterAtIndex:0];
918 unichar imc = [unmodchars characterAtIndex: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;
946 len = [chars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
947 bytes = [chars UTF8String];
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];
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];
976 [NSCursor setHiddenUntilMouseMoves:YES];
979 - (MMWindowController *)windowController
981 id windowController = [[self window] windowController];
982 if ([windowController isKindOfClass:[MMWindowController class]])
983 return (MMWindowController*)windowController;
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];
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,
1057 kATSUStyleRenderingOptionsTag };
1059 ByteCount attribSizes[] = { sizeof(Boolean),
1060 sizeof(CGAffineTransform),
1063 CGAffineTransform theTransform = CGAffineTransformMakeScale(1.0, -1.0);
1064 UInt32 useAntialias;
1066 ATSUAttributeValuePtr attribValues[3] = { &useBold, &theTransform,
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,
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))
1107 ATSUSetTransientFontMatching(layout, TRUE);
1108 ATSUDrawText(layout,
1109 kATSUFromTextBeginning,
1111 X2Fix(rect.origin.x),
1112 X2Fix(rect.origin.y + [font ascender]));
1113 ATSUDisposeTextLayout(layout);
1115 if (flags & DRAW_UNDERL)
1118 NSRectFill(NSMakeRect(rect.origin.x,
1119 (row + 1) * cellSize.height + kUnderlineOffset,
1120 rect.size.width, kUnderlineHeight));
1123 if (flags & DRAW_UNDERC)
1127 float line_end_x = rect.origin.x + rect.size.width;
1129 NSRect line_rect = NSMakeRect(
1131 (row + 1) * cellSize.height + kUndercurlOffset,
1132 kUndercurlDotWidth, kUndercurlHeight);
1134 while (line_rect.origin.x < line_end_x)
1137 NSRectFill(line_rect);
1139 line_rect.origin.x += kUndercurlDotDistance;
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
1162 // move rect up for count lines
1163 [self scrollRect:rect lineCount:-count];
1164 [self clearBlockFromRow:bottom - count + 1
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
1177 toRow:bottom - count
1180 // move rect down for count lines
1181 [self scrollRect:rect lineCount:count];
1182 [self clearBlockFromRow:row
1184 toRow:row + count - 1
1189 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
1190 column:(int)col2 color:(NSColor *)color
1193 NSRectFill([self rectFromRow:row1 column:col1 toRow:row2 column:col2]);
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;
1225 if (MMInsertionPointHollow == shape) {
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)