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 "MMTextViewHelper.h"
31 #import "MMVimController.h"
32 #import "MMWindowController.h"
36 // TODO: What does DRAW_TRANSP flag do? If the background isn't drawn when
37 // this flag is set, then sometimes the character after the cursor becomes
38 // blank. Everything seems to work fine by just ignoring this flag.
39 #define DRAW_TRANSP 0x01 /* draw with transparant bg */
40 #define DRAW_BOLD 0x02 /* draw bold text */
41 #define DRAW_UNDERL 0x04 /* draw underline text */
42 #define DRAW_UNDERC 0x08 /* draw undercurl text */
43 #define DRAW_ITALIC 0x10 /* draw italic text */
44 #define DRAW_CURSOR 0x20
45 #define DRAW_WIDE 0x40 /* draw wide text */
47 #define kUnderlineOffset (-2)
48 #define kUnderlineHeight 1
49 #define kUndercurlHeight 2
50 #define kUndercurlOffset (-2)
51 #define kUndercurlDotWidth 2
52 #define kUndercurlDotDistance 2
55 @interface NSFont (AppKitPrivate)
56 - (ATSUFontID) _atsFontID;
60 @interface MMAtsuiTextView (Private)
61 - (void)initAtsuStyles;
62 - (void)disposeAtsuStyles;
63 - (void)updateAtsuStyles;
64 - (MMWindowController *)windowController;
65 - (MMVimController *)vimController;
69 @interface MMAtsuiTextView (Drawing)
70 - (NSPoint)originForRow:(int)row column:(int)column;
71 - (NSRect)rectFromRow:(int)row1 column:(int)col1
72 toRow:(int)row2 column:(int)col2;
73 - (NSSize)textAreaSize;
74 - (void)resizeContentImage;
77 - (void)drawString:(UniChar *)string length:(UniCharCount)length
78 atRow:(int)row column:(int)col cells:(int)cells
79 withFlags:(int)flags foregroundColor:(NSColor *)fg
80 backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp;
81 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
82 scrollBottom:(int)bottom left:(int)left right:(int)right
83 color:(NSColor *)color;
84 - (void)insertLinesAtRow:(int)row lineCount:(int)count
85 scrollBottom:(int)bottom left:(int)left right:(int)right
86 color:(NSColor *)color;
87 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
88 column:(int)col2 color:(NSColor *)color;
90 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
91 fraction:(int)percent;
92 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nrows
93 numColumns:(int)ncols;
99 defaultLineHeightForFont(NSFont *font)
101 // HACK: -[NSFont defaultLineHeightForFont] is deprecated but since the
102 // ATSUI renderer does not use NSLayoutManager we create one temporarily.
103 NSLayoutManager *lm = [[NSLayoutManager alloc] init];
104 float height = [lm defaultLineHeightForFont:font];
110 @implementation MMAtsuiTextView
112 - (id)initWithFrame:(NSRect)frame
114 if (!(self = [super initWithFrame:frame]))
117 // NOTE! It does not matter which font is set here, Vim will set its
118 // own font on startup anyway. Just set some bogus values.
119 font = [[NSFont userFixedPitchFontOfSize:0] retain];
121 cellSize.width = cellSize.height = 1;
123 imageSize = NSZeroSize;
124 insetSize = NSZeroSize;
126 // NOTE: If the default changes to 'NO' then the intialization of
127 // p_antialias in option.c must change as well.
130 helper = [[MMTextViewHelper alloc] init];
131 [helper setTextView:self];
133 [self initAtsuStyles];
135 [self registerForDraggedTypes:[NSArray arrayWithObjects:
136 NSFilenamesPboardType, NSStringPboardType, nil]];
145 [self disposeAtsuStyles];
146 [font release]; font = nil;
147 [defaultBackgroundColor release]; defaultBackgroundColor = nil;
148 [defaultForegroundColor release]; defaultForegroundColor = nil;
150 [helper setTextView:nil];
151 [helper release]; helper = nil;
166 - (void)getMaxRows:(int*)rows columns:(int*)cols
168 if (rows) *rows = maxRows;
169 if (cols) *cols = maxColumns;
172 - (void)setMaxRows:(int)rows columns:(int)cols
174 // NOTE: Just remember the new values, the actual resizing is done lazily.
179 - (void)setDefaultColorsBackground:(NSColor *)bgColor
180 foreground:(NSColor *)fgColor
182 if (defaultBackgroundColor != bgColor) {
183 [defaultBackgroundColor release];
184 defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
187 if (defaultForegroundColor != fgColor) {
188 [defaultForegroundColor release];
189 defaultForegroundColor = fgColor ? [fgColor retain] : nil;
193 - (NSColor *)defaultBackgroundColor
195 return defaultBackgroundColor;
198 - (NSColor *)defaultForegroundColor
200 return defaultForegroundColor;
203 - (void)setTextContainerInset:(NSSize)size
208 - (NSRect)rectForRowsInRange:(NSRange)range
210 NSRect rect = { 0, 0, 0, 0 };
211 unsigned start = range.location > maxRows ? maxRows : range.location;
212 unsigned length = range.length;
214 if (start + length > maxRows)
215 length = maxRows - start;
217 rect.origin.y = cellSize.height * start + insetSize.height;
218 rect.size.height = cellSize.height * length;
223 - (NSRect)rectForColumnsInRange:(NSRange)range
225 NSRect rect = { 0, 0, 0, 0 };
226 unsigned start = range.location > maxColumns ? maxColumns : range.location;
227 unsigned length = range.length;
229 if (start+length > maxColumns)
230 length = maxColumns - start;
232 rect.origin.x = cellSize.width * start + insetSize.width;
233 rect.size.width = cellSize.width * length;
239 - (void)setFont:(NSFont *)newFont
241 if (newFont && font != newFont) {
243 font = [newFont retain];
244 ascender = roundf([font ascender]);
246 float em = [@"m" sizeWithAttributes:
247 [NSDictionary dictionaryWithObject:newFont
248 forKey:NSFontAttributeName]].width;
249 float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
250 floatForKey:MMCellWidthMultiplierKey];
252 // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
253 // only render at integer sizes. Hence, we restrict the cell width to
254 // an integer here, otherwise the window width and the actual text
255 // width will not match.
256 cellSize.width = ceilf(em * cellWidthMultiplier);
257 cellSize.height = linespace + defaultLineHeightForFont(newFont);
259 [self updateAtsuStyles];
263 - (void)setWideFont:(NSFont *)newFont
266 if (font) [self setWideFont:font];
267 } else if (newFont != fontWide) {
268 float pointSize = [newFont pointSize];
269 NSFontDescriptor *desc = [newFont fontDescriptor];
270 NSDictionary *dictWide = [NSDictionary
271 dictionaryWithObject:[NSNumber numberWithFloat:2*cellSize.width]
272 forKey:NSFontFixedAdvanceAttribute];
273 desc = [desc fontDescriptorByAddingAttributes:dictWide];
274 fontWide = [NSFont fontWithDescriptor:desc size:pointSize];
294 - (void)setLinespace:(float)newLinespace
296 linespace = newLinespace;
298 // NOTE: The linespace is added to the cell height in order for a multiline
299 // selection not to have white (background color) gaps between lines. Also
300 // this simplifies the code a lot because there is no need to check the
301 // linespace when calculating the size of the text view etc. When the
302 // linespace is non-zero the baseline will be adjusted as well; check
304 cellSize.height = linespace + defaultLineHeightForFont(font);
309 - (void)setShouldDrawInsertionPoint:(BOOL)on
313 - (void)setPreEditRow:(int)row column:(int)col
315 [helper setPreEditRow:row column:col];
318 - (void)setMouseShape:(int)shape
320 [helper setMouseShape:shape];
323 - (void)setAntialias:(BOOL)state
328 - (void)setImControl:(BOOL)enable
330 [helper setImControl:enable];
333 - (void)activateIm:(BOOL)enable
335 [helper activateIm:enable];
338 - (BOOL)_wantsKeyDownForEvent:(id)event
340 // HACK! This is an undocumented method which is called from within
341 // -[NSWindow sendEvent] (and perhaps in other places as well) when the
342 // user presses e.g. Ctrl-Tab or Ctrl-Esc . Returning YES here effectively
343 // disables the Cocoa "key view loop" (which is undesirable). It may have
344 // other side-effects, but we really _do_ want to process all key down
345 // events so it seems safe to always return YES.
349 - (void)keyDown:(NSEvent *)event
351 [helper keyDown:event];
354 - (void)insertText:(id)string
356 [helper insertText:string];
359 - (void)doCommandBySelector:(SEL)selector
361 [helper doCommandBySelector:selector];
364 - (BOOL)performKeyEquivalent:(NSEvent *)event
366 return [helper performKeyEquivalent:event];
369 - (BOOL)hasMarkedText
371 return [helper hasMarkedText];
374 - (NSRange)markedRange
376 return [helper markedRange];
379 - (NSDictionary *)markedTextAttributes
381 return [helper markedTextAttributes];
384 - (void)setMarkedTextAttributes:(NSDictionary *)attr
386 [helper setMarkedTextAttributes:attr];
389 - (void)setMarkedText:(id)text selectedRange:(NSRange)range
391 [helper setMarkedText:text selectedRange:range];
399 - (void)scrollWheel:(NSEvent *)event
401 [helper scrollWheel:event];
404 - (void)mouseDown:(NSEvent *)event
406 [helper mouseDown:event];
409 - (void)rightMouseDown:(NSEvent *)event
411 [helper mouseDown:event];
414 - (void)otherMouseDown:(NSEvent *)event
416 [helper mouseDown:event];
419 - (void)mouseUp:(NSEvent *)event
421 [helper mouseUp:event];
424 - (void)rightMouseUp:(NSEvent *)event
426 [helper mouseUp:event];
429 - (void)otherMouseUp:(NSEvent *)event
431 [helper mouseUp:event];
434 - (void)mouseDragged:(NSEvent *)event
436 [helper mouseDragged:event];
439 - (void)rightMouseDragged:(NSEvent *)event
441 [helper mouseDragged:event];
444 - (void)otherMouseDragged:(NSEvent *)event
446 [helper mouseDragged:event];
449 - (void)mouseMoved:(NSEvent *)event
451 [helper mouseMoved:event];
454 - (void)mouseEntered:(NSEvent *)event
456 [helper mouseEntered:event];
459 - (void)mouseExited:(NSEvent *)event
461 [helper mouseExited:event];
464 - (void)setFrame:(NSRect)frame
466 [super setFrame:frame];
467 [helper setFrame:frame];
470 - (void)viewDidMoveToWindow
472 [helper viewDidMoveToWindow];
475 - (void)viewWillMoveToWindow:(NSWindow *)newWindow
477 [helper viewWillMoveToWindow:newWindow];
480 - (NSMenu*)menuForEvent:(NSEvent *)event
482 // HACK! Return nil to disable default popup menus (Vim provides its own).
483 // Called when user Ctrl-clicks in the view (this is already handled in
488 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
490 return [helper performDragOperation:sender];
493 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
495 return [helper draggingEntered:sender];
498 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
500 return [helper draggingUpdated:sender];
505 - (BOOL)mouseDownCanMoveWindow
515 - (BOOL)acceptsFirstResponder
525 - (void)drawRect:(NSRect)rect
527 [defaultBackgroundColor set];
530 NSPoint pt = { insetSize.width, insetSize.height };
531 [contentImage compositeToPoint:pt operation:NSCompositeCopy];
533 #ifdef INCLUDE_OLD_IM_CODE
534 if ([self hasMarkedText] && ![helper useInlineIm]) {
535 int len = [[helper markedText] length];
537 int cols = maxColumns - [helper preEditColumn];
538 NSFont *theFont = [[self markedTextAttributes]
539 valueForKey:NSFontAttributeName];
540 if (theFont == [self fontWide])
543 int lend = cols > len ? len : cols;
544 NSAttributedString *aString = [[helper markedText]
545 attributedSubstringFromRange:NSMakeRange(done, lend)];
546 NSPoint pt = [self pointForRow:[helper preEditRow]
547 column:[helper preEditColumn]];
548 [aString drawAtPoint:pt];
552 rows = (len - done) / (maxColumns / 2) + 1;
553 for (r = 1; r <= rows; r++) {
554 lend = len - done > maxColumns / 2
555 ? maxColumns / 2 : len - done;
556 aString = [[helper markedText] attributedSubstringFromRange:
557 NSMakeRange(done, lend)];
558 NSPoint pt = [self pointForRow:[helper preEditRow]+r
560 [aString drawAtPoint:pt];
565 rows = maxRows - 1 - [helper preEditRow];
566 cols = [helper preEditColumn];
567 if (theFont == fontWide) {
568 cols += ([helper imRange].location+[helper imRange].length) * 2;
569 if (cols >= maxColumns - 1) {
570 rows -= cols / maxColumns;
571 cols = cols % 2 ? cols % maxColumns + 1 :
575 cols += ([helper imRange].location+[helper imRange].length);
576 if (cols >= maxColumns) {
577 rows -= cols / maxColumns;
578 cols = cols % 2 ? cols % maxColumns + 1 :
583 // TODO: Could IM be in "right-left" mode? If so the insertion point
584 // will be on the wrong side.
585 [self drawInsertionPointAtRow:rows
587 shape:MMInsertionPointVertical
590 #endif // INCLUDE_OLD_IM_CODE
593 - (BOOL) wantsDefaultClipping
599 #define MM_DEBUG_DRAWING 0
601 - (void)performBatchDrawWithData:(NSData *)data
603 const void *bytes = [data bytes];
604 const void *end = bytes + [data length];
606 if (! NSEqualSizes(imageSize, [self textAreaSize]))
607 [self resizeContentImage];
610 ASLogDebug(@"====> BEGIN %s", _cmd);
614 // TODO: Sanity check input
616 while (bytes < end) {
617 int type = *((int*)bytes); bytes += sizeof(int);
619 if (ClearAllDrawType == type) {
621 ASLogDebug(@" Clear all");
624 } else if (ClearBlockDrawType == type) {
625 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
626 int row1 = *((int*)bytes); bytes += sizeof(int);
627 int col1 = *((int*)bytes); bytes += sizeof(int);
628 int row2 = *((int*)bytes); bytes += sizeof(int);
629 int col2 = *((int*)bytes); bytes += sizeof(int);
632 ASLogDebug(@" Clear block (%d,%d) -> (%d,%d)", row1, col1,
635 [self clearBlockFromRow:row1 column:col1
636 toRow:row2 column:col2
637 color:[NSColor colorWithArgbInt:color]];
638 } else if (DeleteLinesDrawType == type) {
639 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
640 int row = *((int*)bytes); bytes += sizeof(int);
641 int count = *((int*)bytes); bytes += sizeof(int);
642 int bot = *((int*)bytes); bytes += sizeof(int);
643 int left = *((int*)bytes); bytes += sizeof(int);
644 int right = *((int*)bytes); bytes += sizeof(int);
647 ASLogDebug(@" Delete %d line(s) from %d", count, row);
649 [self deleteLinesFromRow:row lineCount:count
650 scrollBottom:bot left:left right:right
651 color:[NSColor colorWithArgbInt:color]];
652 } else if (DrawStringDrawType == type) {
653 int bg = *((int*)bytes); bytes += sizeof(int);
654 int fg = *((int*)bytes); bytes += sizeof(int);
655 int sp = *((int*)bytes); bytes += sizeof(int);
656 int row = *((int*)bytes); bytes += sizeof(int);
657 int col = *((int*)bytes); bytes += sizeof(int);
658 int cells = *((int*)bytes); bytes += sizeof(int);
659 int flags = *((int*)bytes); bytes += sizeof(int);
660 int len = *((int*)bytes); bytes += sizeof(int);
661 // UniChar *string = (UniChar*)bytes; bytes += len;
662 NSString *string = [[NSString alloc]
663 initWithBytesNoCopy:(void*)bytes
665 encoding:NSUTF8StringEncoding
669 ASLogDebug(@" Draw string at (%d,%d) length=%d flags=%d fg=0x%x "
670 "bg=0x%x sp=0x%x", row, col, len, flags, fg, bg, sp);
672 unichar *characters = malloc(sizeof(unichar) * [string length]);
673 [string getCharacters:characters];
675 [self drawString:characters
676 length:[string length]
679 cells:cells withFlags:flags
680 foregroundColor:[NSColor colorWithRgbInt:fg]
681 backgroundColor:[NSColor colorWithArgbInt:bg]
682 specialColor:[NSColor colorWithRgbInt:sp]];
685 } else if (InsertLinesDrawType == type) {
686 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
687 int row = *((int*)bytes); bytes += sizeof(int);
688 int count = *((int*)bytes); bytes += sizeof(int);
689 int bot = *((int*)bytes); bytes += sizeof(int);
690 int left = *((int*)bytes); bytes += sizeof(int);
691 int right = *((int*)bytes); bytes += sizeof(int);
694 ASLogDebug(@" Insert %d line(s) at row %d", count, row);
696 [self insertLinesAtRow:row lineCount:count
697 scrollBottom:bot left:left right:right
698 color:[NSColor colorWithArgbInt:color]];
699 } else if (DrawCursorDrawType == type) {
700 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
701 int row = *((int*)bytes); bytes += sizeof(int);
702 int col = *((int*)bytes); bytes += sizeof(int);
703 int shape = *((int*)bytes); bytes += sizeof(int);
704 int percent = *((int*)bytes); bytes += sizeof(int);
707 ASLogDebug(@" Draw cursor at (%d,%d)", row, col);
709 [helper setInsertionPointColor:[NSColor colorWithRgbInt:color]];
710 [self drawInsertionPointAtRow:row column:col shape:shape
712 } else if (DrawInvertedRectDrawType == type) {
713 int row = *((int*)bytes); bytes += sizeof(int);
714 int col = *((int*)bytes); bytes += sizeof(int);
715 int nr = *((int*)bytes); bytes += sizeof(int);
716 int nc = *((int*)bytes); bytes += sizeof(int);
717 /*int invert = *((int*)bytes);*/ bytes += sizeof(int);
720 ASLogDebug(@" Draw inverted rect: row=%d col=%d nrows=%d "
721 "ncols=%d", row, col, nr, nc);
723 [self drawInvertedRectAtRow:row column:col numRows:nr
725 } else if (SetCursorPosDrawType == type) {
726 // TODO: This is used for Voice Over support in MMTextView,
727 // MMAtsuiTextView currently does not support Voice Over.
728 /*cursorRow = *((int*)bytes);*/ bytes += sizeof(int);
729 /*cursorCol = *((int*)bytes);*/ bytes += sizeof(int);
731 ASLogWarn(@"Unknown draw type (type=%d)", type);
737 [self setNeedsDisplay:YES];
739 // NOTE: During resizing, Cocoa only sends draw messages before Vim's rows
740 // and columns are changed (due to ipc delays). Force a redraw here.
741 if ([self inLiveResize])
745 ASLogDebug(@"<==== END %s", _cmd);
749 - (NSSize)constrainRows:(int *)rows columns:(int *)cols toSize:(NSSize)size
752 // - Rounding errors may cause size change when there should be none
753 // - Desired rows/columns shold not be 'too small'
755 // Constrain the desired size to the given size. Values for the minimum
756 // rows and columns are taken from Vim.
757 NSSize desiredSize = [self desiredSize];
758 int desiredRows = maxRows;
759 int desiredCols = maxColumns;
761 if (size.height != desiredSize.height) {
762 float fh = cellSize.height;
763 float ih = 2 * insetSize.height;
764 if (fh < 1.0f) fh = 1.0f;
766 desiredRows = floor((size.height - ih)/fh);
767 desiredSize.height = fh*desiredRows + ih;
770 if (size.width != desiredSize.width) {
771 float fw = cellSize.width;
772 float iw = 2 * insetSize.width;
773 if (fw < 1.0f) fw = 1.0f;
775 desiredCols = floor((size.width - iw)/fw);
776 desiredSize.width = fw*desiredCols + iw;
779 if (rows) *rows = desiredRows;
780 if (cols) *cols = desiredCols;
785 - (NSSize)desiredSize
787 // Compute the size the text view should be for the entire text area and
788 // inset area to be visible with the present number of rows and columns.
789 return NSMakeSize(maxColumns * cellSize.width + 2 * insetSize.width,
790 maxRows * cellSize.height + 2 * insetSize.height);
795 // Compute the smallest size the text view is allowed to be.
796 return NSMakeSize(MMMinColumns * cellSize.width + 2 * insetSize.width,
797 MMMinRows * cellSize.height + 2 * insetSize.height);
800 - (void)changeFont:(id)sender
802 [helper changeFont:sender];
807 // NOTE: The menu items cut/copy/paste/undo/redo/select all/... must be bound
808 // to the same actions as in IB otherwise they will not work with dialogs. All
809 // we do here is forward these actions to the Vim process.
811 - (IBAction)cut:(id)sender
813 [[self windowController] vimMenuItemAction:sender];
816 - (IBAction)copy:(id)sender
818 [[self windowController] vimMenuItemAction:sender];
821 - (IBAction)paste:(id)sender
823 [[self windowController] vimMenuItemAction:sender];
826 - (IBAction)undo:(id)sender
828 [[self windowController] vimMenuItemAction:sender];
831 - (IBAction)redo:(id)sender
833 [[self windowController] vimMenuItemAction:sender];
836 - (IBAction)selectAll:(id)sender
838 [[self windowController] vimMenuItemAction:sender];
841 - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column
843 // View is not flipped, instead the atsui code draws to a flipped image;
844 // thus we need to 'flip' the coordinate here since the column number
845 // increases in an up-to-down order.
846 point.y = [self frame].size.height - point.y;
848 NSPoint origin = { insetSize.width, insetSize.height };
850 if (!(cellSize.width > 0 && cellSize.height > 0))
853 if (row) *row = floor((point.y-origin.y-1) / cellSize.height);
854 if (column) *column = floor((point.x-origin.x-1) / cellSize.width);
859 - (NSPoint)pointForRow:(int)row column:(int)col
861 // Return the lower left coordinate of the cell at (row,column).
864 pt.x = insetSize.width + col*cellSize.width;
865 pt.y = [self frame].size.height -
866 (insetSize.height + (1+row)*cellSize.height);
871 - (NSRect)rectForRow:(int)row column:(int)col numRows:(int)nr
874 // Return the rect for the block which covers the specified rows and
875 // columns. The lower-left corner is the origin of this rect.
878 rect.origin.x = insetSize.width + col*cellSize.width;
879 rect.origin.y = [self frame].size.height -
880 (insetSize.height + (nr+row)*cellSize.height);
881 rect.size.width = nc*cellSize.width;
882 rect.size.height = nr*cellSize.height;
887 - (NSArray *)validAttributesForMarkedText
892 - (NSAttributedString *)attributedSubstringFromRange:(NSRange)range
897 - (NSUInteger)characterIndexForPoint:(NSPoint)point
902 // The return type of this message changed with OS X 10.5 so we need this
903 // kludge in order to avoid compiler warnings on OS X 10.4.
904 #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
905 - (NSInteger)conversationIdentifier
907 return (NSInteger)self;
910 - (long)conversationIdentifier
916 - (NSRange)selectedRange
918 return [helper imRange];
921 - (NSRect)firstRectForCharacterRange:(NSRange)range
923 return [helper firstRectForCharacterRange:range];
926 @end // MMAtsuiTextView
931 @implementation MMAtsuiTextView (Private)
933 - (void)initAtsuStyles
936 for (i = 0; i < MMMaxCellsPerChar; i++)
937 ATSUCreateStyle(&atsuStyles[i]);
940 - (void)disposeAtsuStyles
944 for (i = 0; i < MMMaxCellsPerChar; i++)
945 if (atsuStyles[i] != NULL)
947 if (ATSUDisposeStyle(atsuStyles[i]) != noErr)
948 atsuStyles[i] = NULL;
952 - (void)updateAtsuStyles
958 CGAffineTransform transform = CGAffineTransformMakeScale(1, -1);
959 ATSStyleRenderingOptions options;
961 fontID = [font _atsFontID];
962 fontSize = Long2Fix([font pointSize]);
963 options = kATSStyleApplyAntiAliasing;
965 ATSUAttributeTag attribTags[] =
967 kATSUFontTag, kATSUSizeTag, kATSUImposeWidthTag,
968 kATSUFontMatrixTag, kATSUStyleRenderingOptionsTag,
969 kATSUMaxATSUITagValue + 1
972 ByteCount attribSizes[] =
974 sizeof(ATSUFontID), sizeof(Fixed), sizeof(fontWidth),
975 sizeof(CGAffineTransform), sizeof(ATSStyleRenderingOptions),
979 ATSUAttributeValuePtr attribValues[] =
981 &fontID, &fontSize, &fontWidth, &transform, &options, &font
984 ATSUFontFeatureType featureTypes[] = {
985 kLigaturesType, kLigaturesType
988 ATSUFontFeatureSelector featureSelectors[] = {
989 kCommonLigaturesOffSelector, kRareLigaturesOffSelector
992 for (i = 0; i < MMMaxCellsPerChar; i++)
994 fontWidth = Long2Fix(cellSize.width * (i + 1));
996 if (ATSUSetAttributes(atsuStyles[i],
997 (sizeof attribTags) / sizeof(ATSUAttributeTag),
998 attribTags, attribSizes, attribValues) != noErr)
1000 ATSUDisposeStyle(atsuStyles[i]);
1001 atsuStyles[i] = NULL;
1004 // Turn off ligatures by default
1005 ATSUSetFontFeatures(atsuStyles[i],
1006 sizeof(featureTypes) / sizeof(featureTypes[0]),
1007 featureTypes, featureSelectors);
1011 - (MMWindowController *)windowController
1013 id windowController = [[self window] windowController];
1014 if ([windowController isKindOfClass:[MMWindowController class]])
1015 return (MMWindowController*)windowController;
1019 - (MMVimController *)vimController
1021 return [[self windowController] vimController];
1024 @end // MMAtsuiTextView (Private)
1029 @implementation MMAtsuiTextView (Drawing)
1031 - (NSPoint)originForRow:(int)row column:(int)col
1033 return NSMakePoint(col * cellSize.width, row * cellSize.height);
1036 - (NSRect)rectFromRow:(int)row1 column:(int)col1
1037 toRow:(int)row2 column:(int)col2
1039 NSPoint origin = [self originForRow:row1 column:col1];
1040 return NSMakeRect(origin.x, origin.y,
1041 (col2 + 1 - col1) * cellSize.width,
1042 (row2 + 1 - row1) * cellSize.height);
1045 - (NSSize)textAreaSize
1047 // Calculate the (desired) size of the text area, i.e. the text view area
1048 // minus the inset area.
1049 return NSMakeSize(maxColumns * cellSize.width, maxRows * cellSize.height);
1052 - (void)resizeContentImage
1054 [contentImage release];
1055 contentImage = [[NSImage alloc] initWithSize:[self textAreaSize]];
1056 [contentImage setFlipped:YES];
1057 imageSize = [self textAreaSize];
1060 - (void)beginDrawing
1062 [contentImage lockFocus];
1067 [contentImage unlockFocus];
1070 #define atsu_style_set_bool(s, t, b) \
1071 ATSUSetAttributes(s, 1, &t, &(sizeof(Boolean)), &&b);
1072 #define FILL_Y(y) (y * cellSize.height)
1074 - (void)drawString:(UniChar *)string length:(UniCharCount)length
1075 atRow:(int)row column:(int)col cells:(int)cells
1076 withFlags:(int)flags foregroundColor:(NSColor *)fg
1077 backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp
1079 // 'string' consists of 'length' utf-16 code pairs and should cover 'cells'
1080 // display cells (a normal character takes up one display cell, a wide
1081 // character takes up two)
1082 ATSUStyle style = (flags & DRAW_WIDE) ? atsuStyles[1] : atsuStyles[0];
1083 ATSUTextLayout layout;
1085 // Font selection and rendering options for ATSUI
1086 ATSUAttributeTag attribTags[3] = { kATSUQDBoldfaceTag,
1088 kATSUStyleRenderingOptionsTag };
1090 ByteCount attribSizes[] = { sizeof(Boolean),
1091 sizeof(CGAffineTransform),
1094 CGAffineTransform theTransform = CGAffineTransformMakeScale(1.0, -1.0);
1095 UInt32 useAntialias;
1097 ATSUAttributeValuePtr attribValues[3] = { &useBold, &theTransform,
1100 useBold = (flags & DRAW_BOLD) ? true : false;
1102 if (flags & DRAW_ITALIC)
1103 theTransform.c = Fix2X(kATSItalicQDSkew);
1105 useAntialias = antialias ? kATSStyleApplyAntiAliasing
1106 : kATSStyleNoAntiAliasing;
1108 ATSUSetAttributes(style, sizeof(attribValues) / sizeof(attribValues[0]),
1109 attribTags, attribSizes, attribValues);
1111 ATSUCreateTextLayout(&layout);
1112 ATSUSetTextPointerLocation(layout, string,
1113 kATSUFromTextBeginning, kATSUToTextEnd,
1115 ATSUSetRunStyle(layout, style, kATSUFromTextBeginning, kATSUToTextEnd);
1117 NSRect rect = NSMakeRect(col * cellSize.width, row * cellSize.height,
1118 length * cellSize.width, cellSize.height);
1119 if (flags & DRAW_WIDE)
1120 rect.size.width = rect.size.width * 2;
1121 CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
1123 // Clip drawing to avoid text bleeding into adjacent display cells when
1124 // antialiasing is enabled.
1125 CGContextSaveGState(context);
1126 CGContextClipToRect(context, *(CGRect*)&rect);
1128 ATSUAttributeTag tags[] = { kATSUCGContextTag };
1129 ByteCount sizes[] = { sizeof(CGContextRef) };
1130 ATSUAttributeValuePtr values[] = { &context };
1131 ATSUSetLayoutControls(layout, 1, tags, sizes, values);
1133 if (! (flags & DRAW_TRANSP))
1141 ATSUSetTransientFontMatching(layout, TRUE);
1142 ATSUDrawText(layout,
1143 kATSUFromTextBeginning,
1145 X2Fix(rect.origin.x),
1146 X2Fix(rect.origin.y + ascender));
1147 ATSUDisposeTextLayout(layout);
1149 if (flags & DRAW_UNDERL)
1152 NSRectFill(NSMakeRect(rect.origin.x,
1153 (row + 1) * cellSize.height + kUnderlineOffset,
1154 rect.size.width, kUnderlineHeight));
1157 if (flags & DRAW_UNDERC)
1161 float line_end_x = rect.origin.x + rect.size.width;
1163 NSRect line_rect = NSMakeRect(
1165 (row + 1) * cellSize.height + kUndercurlOffset,
1166 kUndercurlDotWidth, kUndercurlHeight);
1168 while (line_rect.origin.x < line_end_x)
1171 NSRectFill(line_rect);
1173 line_rect.origin.x += kUndercurlDotDistance;
1178 CGContextRestoreGState(context);
1181 - (void)scrollRect:(NSRect)rect lineCount:(int)count
1183 NSPoint destPoint = rect.origin;
1184 destPoint.y += count * cellSize.height;
1186 NSCopyBits(0, rect, destPoint);
1189 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
1190 scrollBottom:(int)bottom left:(int)left right:(int)right
1191 color:(NSColor *)color
1193 NSRect rect = [self rectFromRow:row + count
1198 // move rect up for count lines
1199 [self scrollRect:rect lineCount:-count];
1200 [self clearBlockFromRow:bottom - count + 1
1207 - (void)insertLinesAtRow:(int)row lineCount:(int)count
1208 scrollBottom:(int)bottom left:(int)left right:(int)right
1209 color:(NSColor *)color
1211 NSRect rect = [self rectFromRow:row
1213 toRow:bottom - count
1216 // move rect down for count lines
1217 [self scrollRect:rect lineCount:count];
1218 [self clearBlockFromRow:row
1220 toRow:row + count - 1
1225 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
1226 column:(int)col2 color:(NSColor *)color
1229 NSRectFill([self rectFromRow:row1 column:col1 toRow:row2 column:col2]);
1234 [defaultBackgroundColor set];
1235 NSRectFill(NSMakeRect(0, 0, imageSize.width, imageSize.height));
1238 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
1239 fraction:(int)percent
1241 NSPoint origin = [self originForRow:row column:col];
1242 NSRect rect = NSMakeRect(origin.x, origin.y,
1243 cellSize.width, cellSize.height);
1245 if (MMInsertionPointHorizontal == shape) {
1246 int frac = (cellSize.height * percent + 99)/100;
1247 rect.origin.y += rect.size.height - frac;
1248 rect.size.height = frac;
1249 } else if (MMInsertionPointVertical == shape) {
1250 int frac = (cellSize.width * percent + 99)/100;
1251 rect.size.width = frac;
1252 } else if (MMInsertionPointVerticalRight == shape) {
1253 int frac = (cellSize.width * percent + 99)/100;
1254 rect.origin.x += rect.size.width - frac;
1255 rect.size.width = frac;
1258 [[helper insertionPointColor] set];
1259 if (MMInsertionPointHollow == shape) {
1266 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nrows
1267 numColumns:(int)ncols
1269 // TODO: THIS CODE HAS NOT BEEN TESTED!
1270 CGContextRef cgctx = [[NSGraphicsContext currentContext] graphicsPort];
1271 CGContextSaveGState(cgctx);
1272 CGContextSetBlendMode(cgctx, kCGBlendModeDifference);
1273 CGContextSetRGBFillColor(cgctx, 1.0, 1.0, 1.0, 1.0);
1275 CGRect rect = { col * cellSize.width, row * cellSize.height,
1276 ncols * cellSize.width, nrows * cellSize.height };
1277 CGContextFillRect(cgctx, rect);
1279 CGContextRestoreGState(cgctx);
1282 @end // MMAtsuiTextView (Drawing)
1284 #endif // MM_ENABLE_ATSUI