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 CoreText.
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 "MMCoreTextView.h"
30 #import "MMTextViewHelper.h"
31 #import "MMVimController.h"
32 #import "MMWindowController.h"
33 #import "Miscellaneous.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 transparent 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 */
48 #define BLUE(argb) ((argb & 0xff)/255.0f)
49 #define GREEN(argb) (((argb>>8) & 0xff)/255.0f)
50 #define RED(argb) (((argb>>16) & 0xff)/255.0f)
51 #define ALPHA(argb) (((argb>>24) & 0xff)/255.0f)
55 @interface MMCoreTextView (Private)
56 - (MMWindowController *)windowController;
57 - (MMVimController *)vimController;
61 @interface MMCoreTextView (Drawing)
62 - (NSPoint)pointForRow:(int)row column:(int)column;
63 - (NSRect)rectForRow:(int)row column:(int)column numRows:(int)nr
65 - (NSRect)rectFromRow:(int)row1 column:(int)col1
66 toRow:(int)row2 column:(int)col2;
67 - (NSSize)textAreaSize;
68 - (void)batchDrawData:(NSData *)data;
69 - (void)drawString:(const UniChar *)chars length:(UniCharCount)length
70 atRow:(int)row column:(int)col cells:(int)cells
71 withFlags:(int)flags foregroundColor:(int)fg
72 backgroundColor:(int)bg specialColor:(int)sp;
73 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
74 scrollBottom:(int)bottom left:(int)left right:(int)right
76 - (void)insertLinesAtRow:(int)row lineCount:(int)count
77 scrollBottom:(int)bottom left:(int)left right:(int)right
79 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
80 column:(int)col2 color:(int)color;
82 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
83 fraction:(int)percent color:(int)color;
84 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nrows
85 numColumns:(int)ncols;
91 defaultLineHeightForFont(NSFont *font)
93 // HACK: -[NSFont defaultLineHeightForFont] is deprecated but since the
94 // CoreText renderer does not use NSLayoutManager we create one
96 NSLayoutManager *lm = [[NSLayoutManager alloc] init];
97 float height = [lm defaultLineHeightForFont:font];
104 defaultAdvanceForFont(CTFontRef fontRef)
106 // We measure the default advance of a font as the advance of its glyph for
108 double advance = 0.0;
109 UniChar characters[] = { 'm' };
111 CGGlyph glyphs[] = { 0 };
113 if (CTFontGetGlyphsForCharacters(fontRef, characters, glyphs, count)) {
114 advance = CTFontGetAdvancesForGlyphs(fontRef,
115 kCTFontDefaultOrientation,
122 ASLogWarn(@"Could not determine default advance for current font");
129 @implementation MMCoreTextView
131 - (id)initWithFrame:(NSRect)frame
133 if (!(self = [super initWithFrame:frame]))
136 // NOTE! It does not matter which font is set here, Vim will set its
137 // own font on startup anyway. Just set some bogus values.
138 font = [[NSFont userFixedPitchFontOfSize:0] retain];
139 cellSize.width = cellSize.height = 1;
141 // NOTE: If the default changes to 'NO' then the intialization of
142 // p_antialias in option.c must change as well.
145 drawData = [[NSMutableArray alloc] init];
147 helper = [[MMTextViewHelper alloc] init];
148 [helper setTextView:self];
150 [self registerForDraggedTypes:[NSArray arrayWithObjects:
151 NSFilenamesPboardType, NSStringPboardType, nil]];
158 [font release]; font = nil;
159 [defaultBackgroundColor release]; defaultBackgroundColor = nil;
160 [defaultForegroundColor release]; defaultForegroundColor = nil;
161 [drawData release]; drawData = nil;
163 [helper setTextView:nil];
164 [helper release]; helper = nil;
166 if (glyphs) { free(glyphs); glyphs = NULL; }
167 if (advances) { free(advances); advances = NULL; }
182 - (void)getMaxRows:(int*)rows columns:(int*)cols
184 if (rows) *rows = maxRows;
185 if (cols) *cols = maxColumns;
188 - (void)setMaxRows:(int)rows columns:(int)cols
190 // NOTE: Just remember the new values, the actual resizing is done lazily.
195 - (void)setDefaultColorsBackground:(NSColor *)bgColor
196 foreground:(NSColor *)fgColor
198 if (defaultBackgroundColor != bgColor) {
199 [defaultBackgroundColor release];
200 defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
203 // NOTE: The default foreground color isn't actually used for anything, but
204 // other class instances might want to be able to access it so it is stored
206 if (defaultForegroundColor != fgColor) {
207 [defaultForegroundColor release];
208 defaultForegroundColor = fgColor ? [fgColor retain] : nil;
212 - (NSColor *)defaultBackgroundColor
214 return defaultBackgroundColor;
217 - (NSColor *)defaultForegroundColor
219 return defaultForegroundColor;
222 - (void)setTextContainerInset:(NSSize)size
227 - (NSRect)rectForRowsInRange:(NSRange)range
229 NSRect rect = { {0, 0}, {0, 0} };
230 unsigned start = range.location > maxRows ? maxRows : range.location;
231 unsigned length = range.length;
233 if (start + length > maxRows)
234 length = maxRows - start;
236 rect.origin.y = cellSize.height * start + insetSize.height;
237 rect.size.height = cellSize.height * length;
242 - (NSRect)rectForColumnsInRange:(NSRange)range
244 NSRect rect = { {0, 0}, {0, 0} };
245 unsigned start = range.location > maxColumns ? maxColumns : range.location;
246 unsigned length = range.length;
248 if (start+length > maxColumns)
249 length = maxColumns - start;
251 rect.origin.x = cellSize.width * start + insetSize.width;
252 rect.size.width = cellSize.width * length;
258 - (void)setFont:(NSFont *)newFont
261 if (newFont && font != newFont) {
263 font = [newFont retain];
265 float em = 7.0; //[newFont widthOfString:@"m"];
266 float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
267 floatForKey:MMCellWidthMultiplierKey];
269 // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
270 // only render at integer sizes. Hence, we restrict the cell width to
271 // an integer here, otherwise the window width and the actual text
272 // width will not match.
273 cellSize.width = ceilf(em * cellWidthMultiplier);
274 cellSize.height = linespace + 15.0; //[newFont defaultLineHeightForFont];
277 if (!(newFont && font != newFont))
280 double em = round(defaultAdvanceForFont((CTFontRef)newFont));
281 double pt = round([newFont pointSize]);
283 NSDictionary *attr = [NSDictionary dictionaryWithObjectsAndKeys:
284 [newFont displayName], (NSString*)kCTFontNameAttribute,
285 [NSNumber numberWithFloat:pt], (NSString*)kCTFontSizeAttribute,
286 //[NSNumber numberWithFloat:em], (NSString*)kCTFontFixedAdvanceAttribute,
288 CTFontDescriptorRef desc = CTFontDescriptorCreateWithAttributes(
289 (CFDictionaryRef)attr);
290 CTFontRef fontRef = CTFontCreateWithFontDescriptor(desc, pt, NULL);
294 font = (NSFont*)fontRef;
296 float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
297 floatForKey:MMCellWidthMultiplierKey];
299 // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
300 // only render at integer sizes. Hence, we restrict the cell width to
301 // an integer here, otherwise the window width and the actual text
302 // width will not match.
303 cellSize.width = ceil(em * cellWidthMultiplier);
304 cellSize.height = linespace + defaultLineHeightForFont(font);
306 fontDescent = ceil(CTFontGetDescent(fontRef));
310 - (void)setWideFont:(NSFont *)newFont
329 - (void)setLinespace:(float)newLinespace
331 linespace = newLinespace;
333 // NOTE: The linespace is added to the cell height in order for a multiline
334 // selection not to have white (background color) gaps between lines. Also
335 // this simplifies the code a lot because there is no need to check the
336 // linespace when calculating the size of the text view etc. When the
337 // linespace is non-zero the baseline will be adjusted as well; check
339 cellSize.height = linespace + defaultLineHeightForFont(font);
345 - (void)setShouldDrawInsertionPoint:(BOOL)on
349 - (void)setPreEditRow:(int)row column:(int)col
353 - (void)setMouseShape:(int)shape
355 [helper setMouseShape:shape];
358 - (void)setAntialias:(BOOL)state
363 - (void)setImControl:(BOOL)enable
365 [helper setImControl:enable];
368 - (void)activateIm:(BOOL)enable
370 [helper activateIm:enable];
376 - (void)keyDown:(NSEvent *)event
378 [helper keyDown:event];
381 - (void)insertText:(id)string
383 [helper insertText:string];
386 - (void)doCommandBySelector:(SEL)selector
388 [helper doCommandBySelector:selector];
391 - (BOOL)performKeyEquivalent:(NSEvent *)event
393 return [helper performKeyEquivalent:event];
396 - (BOOL)hasMarkedText
398 return [helper hasMarkedText];
401 - (NSRange)markedRange
403 return [helper markedRange];
406 - (NSDictionary *)markedTextAttributes
408 return [helper markedTextAttributes];
411 - (void)setMarkedTextAttributes:(NSDictionary *)attr
413 [helper setMarkedTextAttributes:attr];
416 - (void)setMarkedText:(id)text selectedRange:(NSRange)range
418 [helper setMarkedText:text selectedRange:range];
426 - (void)scrollWheel:(NSEvent *)event
428 [helper scrollWheel:event];
431 - (void)mouseDown:(NSEvent *)event
433 [helper mouseDown:event];
436 - (void)rightMouseDown:(NSEvent *)event
438 [helper mouseDown:event];
441 - (void)otherMouseDown:(NSEvent *)event
443 [helper mouseDown:event];
446 - (void)mouseUp:(NSEvent *)event
448 [helper mouseUp:event];
451 - (void)rightMouseUp:(NSEvent *)event
453 [helper mouseUp:event];
456 - (void)otherMouseUp:(NSEvent *)event
458 [helper mouseUp:event];
461 - (void)mouseDragged:(NSEvent *)event
463 [helper mouseDragged:event];
466 - (void)rightMouseDragged:(NSEvent *)event
468 [helper mouseDragged:event];
471 - (void)otherMouseDragged:(NSEvent *)event
473 [helper mouseDragged:event];
476 - (void)mouseMoved:(NSEvent *)event
478 [helper mouseMoved:event];
481 - (void)mouseEntered:(NSEvent *)event
483 [helper mouseEntered:event];
486 - (void)mouseExited:(NSEvent *)event
488 [helper mouseExited:event];
491 - (void)setFrame:(NSRect)frame
493 [super setFrame:frame];
494 [helper setFrame:frame];
497 - (void)viewDidMoveToWindow
499 [helper viewDidMoveToWindow];
502 - (void)viewWillMoveToWindow:(NSWindow *)newWindow
504 [helper viewWillMoveToWindow:newWindow];
507 - (NSMenu*)menuForEvent:(NSEvent *)event
509 // HACK! Return nil to disable default popup menus (Vim provides its own).
510 // Called when user Ctrl-clicks in the view (this is already handled in
515 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
517 return [helper performDragOperation:sender];
520 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
522 return [helper draggingEntered:sender];
525 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
527 return [helper draggingUpdated:sender];
532 - (BOOL)mouseDownCanMoveWindow
542 - (BOOL)acceptsFirstResponder
552 - (void)drawRect:(NSRect)rect
554 //ASLogNotice(@"drawData count=%d", [drawData count]);
556 NSGraphicsContext *context = [NSGraphicsContext currentContext];
557 [context setShouldAntialias:antialias];
559 NSRect frame = [self frame];
560 if (!NSEqualRects(lastClearRect, frame)) {
561 // HACK! If the view frame changes then clear the entire frame
562 // otherwise the screen flickers e.g. when the window is resized.
566 float y = frame.size.height - lastClearRect.size.height;
567 if (y > 0 && y < frame.size.height) {
568 NSCopyBits(0, lastClearRect, NSMakePoint(0, y));
571 lastClearRect = frame;
575 NSEnumerator *e = [drawData objectEnumerator];
576 while ((data = [e nextObject]))
577 [self batchDrawData:data];
579 [drawData removeAllObjects];
582 - (void)performBatchDrawWithData:(NSData *)data
584 [drawData addObject:data];
585 [self setNeedsDisplay:YES];
587 // NOTE: During resizing, Cocoa only sends draw messages before Vim's rows
588 // and columns are changed (due to ipc delays). Force a redraw here.
589 if ([self inLiveResize])
593 - (NSSize)constrainRows:(int *)rows columns:(int *)cols toSize:(NSSize)size
596 // - Rounding errors may cause size change when there should be none
597 // - Desired rows/columns shold not be 'too small'
599 // Constrain the desired size to the given size. Values for the minimum
600 // rows and columns is taken from Vim.
601 NSSize desiredSize = [self desiredSize];
602 int desiredRows = maxRows;
603 int desiredCols = maxColumns;
604 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
605 int right = [ud integerForKey:MMTextInsetRightKey];
606 int bot = [ud integerForKey:MMTextInsetBottomKey];
608 if (size.height != desiredSize.height) {
609 float fh = cellSize.height;
610 float ih = insetSize.height + bot;
611 if (fh < 1.0f) fh = 1.0f;
613 desiredRows = floor((size.height - ih)/fh);
614 desiredSize.height = fh*desiredRows + ih;
617 if (size.width != desiredSize.width) {
618 float fw = cellSize.width;
619 float iw = insetSize.width + right;
620 if (fw < 1.0f) fw = 1.0f;
622 desiredCols = floor((size.width - iw)/fw);
623 desiredSize.width = fw*desiredCols + iw;
626 if (rows) *rows = desiredRows;
627 if (cols) *cols = desiredCols;
632 - (NSSize)desiredSize
634 // Compute the size the text view should be for the entire text area and
635 // inset area to be visible with the present number of rows and columns.
636 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
637 int right = [ud integerForKey:MMTextInsetRightKey];
638 int bot = [ud integerForKey:MMTextInsetBottomKey];
640 return NSMakeSize(maxColumns * cellSize.width + insetSize.width + right,
641 maxRows * cellSize.height + insetSize.height + bot);
646 // Compute the smallest size the text view is allowed to be.
647 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
648 int right = [ud integerForKey:MMTextInsetRightKey];
649 int bot = [ud integerForKey:MMTextInsetBottomKey];
651 return NSMakeSize(MMMinColumns * cellSize.width + insetSize.width + right,
652 MMMinRows * cellSize.height + insetSize.height + bot);
655 - (void)changeFont:(id)sender
657 NSFont *newFont = [sender convertFont:font];
660 NSString *name = [newFont displayName];
661 unsigned len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
663 NSMutableData *data = [NSMutableData data];
664 float pointSize = [newFont pointSize];
666 [data appendBytes:&pointSize length:sizeof(float)];
668 ++len; // include NUL byte
669 [data appendBytes:&len length:sizeof(unsigned)];
670 [data appendBytes:[name UTF8String] length:len];
672 [[self vimController] sendMessage:SetFontMsgID data:data];
679 // NOTE: The menu items cut/copy/paste/undo/redo/select all/... must be bound
680 // to the same actions as in IB otherwise they will not work with dialogs. All
681 // we do here is forward these actions to the Vim process.
683 - (IBAction)cut:(id)sender
685 [[self windowController] vimMenuItemAction:sender];
688 - (IBAction)copy:(id)sender
690 [[self windowController] vimMenuItemAction:sender];
693 - (IBAction)paste:(id)sender
695 [[self windowController] vimMenuItemAction:sender];
698 - (IBAction)undo:(id)sender
700 [[self windowController] vimMenuItemAction:sender];
703 - (IBAction)redo:(id)sender
705 [[self windowController] vimMenuItemAction:sender];
708 - (IBAction)selectAll:(id)sender
710 [[self windowController] vimMenuItemAction:sender];
713 - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column
715 // View is not flipped, instead the atsui code draws to a flipped image;
716 // thus we need to 'flip' the coordinate here since the column number
717 // increases in an up-to-down order.
718 point.y = [self frame].size.height - point.y;
720 NSPoint origin = { insetSize.width, insetSize.height };
722 if (!(cellSize.width > 0 && cellSize.height > 0))
725 if (row) *row = floor((point.y-origin.y-1) / cellSize.height);
726 if (column) *column = floor((point.x-origin.x-1) / cellSize.width);
728 //ASLogDebug(@"point=%@ row=%d col=%d",
729 // NSStringFromPoint(point), *row, *column);
734 - (NSArray *)validAttributesForMarkedText
739 - (NSAttributedString *)attributedSubstringFromRange:(NSRange)range
744 - (NSUInteger)characterIndexForPoint:(NSPoint)point
749 - (NSInteger)conversationIdentifier
751 return (NSInteger)self;
754 - (NSRange)selectedRange
756 return [helper imRange];
759 - (NSRect)firstRectForCharacterRange:(NSRange)range
761 return [helper firstRectForCharacterRange:range];
764 @end // MMCoreTextView
769 @implementation MMCoreTextView (Private)
771 - (MMWindowController *)windowController
773 id windowController = [[self window] windowController];
774 if ([windowController isKindOfClass:[MMWindowController class]])
775 return (MMWindowController*)windowController;
779 - (MMVimController *)vimController
781 return [[self windowController] vimController];
784 @end // MMCoreTextView (Private)
789 @implementation MMCoreTextView (Drawing)
791 - (NSPoint)pointForRow:(int)row column:(int)col
793 NSRect frame = [self frame];
795 col*cellSize.width + insetSize.width,
796 frame.size.height - (row+1)*cellSize.height - insetSize.height);
799 - (NSRect)rectForRow:(int)row column:(int)col numRows:(int)nr
803 NSRect frame = [self frame];
805 rect.origin.x = col*cellSize.width + insetSize.width;
806 rect.origin.y = frame.size.height - (row+nr)*cellSize.height -
808 rect.size.width = nc*cellSize.width;
809 rect.size.height = nr*cellSize.height;
814 - (NSRect)rectFromRow:(int)row1 column:(int)col1
815 toRow:(int)row2 column:(int)col2
817 NSRect frame = [self frame];
819 insetSize.width + col1*cellSize.width,
820 frame.size.height - insetSize.height - (row2+1)*cellSize.height,
821 (col2 + 1 - col1) * cellSize.width,
822 (row2 + 1 - row1) * cellSize.height);
825 - (NSSize)textAreaSize
827 // Calculate the (desired) size of the text area, i.e. the text view area
828 // minus the inset area.
829 return NSMakeSize(maxColumns * cellSize.width, maxRows * cellSize.height);
832 #define MM_DEBUG_DRAWING 0
834 - (void)batchDrawData:(NSData *)data
836 const void *bytes = [data bytes];
837 const void *end = bytes + [data length];
840 ASLogNotice(@"====> BEGIN %s", _cmd);
842 // TODO: Sanity check input
844 while (bytes < end) {
845 int type = *((int*)bytes); bytes += sizeof(int);
847 if (ClearAllDrawType == type) {
849 ASLogNotice(@" Clear all");
852 } else if (ClearBlockDrawType == type) {
853 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
854 int row1 = *((int*)bytes); bytes += sizeof(int);
855 int col1 = *((int*)bytes); bytes += sizeof(int);
856 int row2 = *((int*)bytes); bytes += sizeof(int);
857 int col2 = *((int*)bytes); bytes += sizeof(int);
860 ASLogNotice(@" Clear block (%d,%d) -> (%d,%d)", row1, col1,
863 [self clearBlockFromRow:row1 column:col1
864 toRow:row2 column:col2
866 } else if (DeleteLinesDrawType == type) {
867 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
868 int row = *((int*)bytes); bytes += sizeof(int);
869 int count = *((int*)bytes); bytes += sizeof(int);
870 int bot = *((int*)bytes); bytes += sizeof(int);
871 int left = *((int*)bytes); bytes += sizeof(int);
872 int right = *((int*)bytes); bytes += sizeof(int);
875 ASLogNotice(@" Delete %d line(s) from %d", count, row);
877 [self deleteLinesFromRow:row lineCount:count
878 scrollBottom:bot left:left right:right
880 } else if (DrawStringDrawType == type) {
881 int bg = *((int*)bytes); bytes += sizeof(int);
882 int fg = *((int*)bytes); bytes += sizeof(int);
883 int sp = *((int*)bytes); bytes += sizeof(int);
884 int row = *((int*)bytes); bytes += sizeof(int);
885 int col = *((int*)bytes); bytes += sizeof(int);
886 int cells = *((int*)bytes); bytes += sizeof(int);
887 int flags = *((int*)bytes); bytes += sizeof(int);
888 int len = *((int*)bytes); bytes += sizeof(int);
889 UInt8 *s = (UInt8 *)bytes; bytes += len;
892 ASLogNotice(@" Draw string len=%d row=%d col=%d flags=%#x",
893 len, row, col, flags);
896 // Convert UTF-8 chars to UTF-16
897 CFStringRef sref = CFStringCreateWithBytesNoCopy(NULL, s, len,
898 kCFStringEncodingUTF8, false, kCFAllocatorNull);
899 CFIndex unilength = CFStringGetLength(sref);
900 const UniChar *unichars = CFStringGetCharactersPtr(sref);
901 UniChar *buffer = NULL;
902 if (unichars == NULL) {
903 buffer = malloc(unilength * sizeof(UniChar));
904 CFStringGetCharacters(sref, CFRangeMake(0, unilength), buffer);
908 [self drawString:unichars length:unilength
909 atRow:row column:col cells:cells
920 } else if (InsertLinesDrawType == type) {
921 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
922 int row = *((int*)bytes); bytes += sizeof(int);
923 int count = *((int*)bytes); bytes += sizeof(int);
924 int bot = *((int*)bytes); bytes += sizeof(int);
925 int left = *((int*)bytes); bytes += sizeof(int);
926 int right = *((int*)bytes); bytes += sizeof(int);
929 ASLogNotice(@" Insert %d line(s) at row %d", count, row);
931 [self insertLinesAtRow:row lineCount:count
932 scrollBottom:bot left:left right:right
934 } else if (DrawCursorDrawType == type) {
935 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
936 int row = *((int*)bytes); bytes += sizeof(int);
937 int col = *((int*)bytes); bytes += sizeof(int);
938 int shape = *((int*)bytes); bytes += sizeof(int);
939 int percent = *((int*)bytes); bytes += sizeof(int);
942 ASLogNotice(@" Draw cursor at (%d,%d)", row, col);
944 [self drawInsertionPointAtRow:row column:col shape:shape
947 } else if (DrawInvertedRectDrawType == type) {
948 int row = *((int*)bytes); bytes += sizeof(int);
949 int col = *((int*)bytes); bytes += sizeof(int);
950 int nr = *((int*)bytes); bytes += sizeof(int);
951 int nc = *((int*)bytes); bytes += sizeof(int);
952 /*int invert = *((int*)bytes);*/ bytes += sizeof(int);
955 ASLogNotice(@" Draw inverted rect: row=%d col=%d nrows=%d "
956 "ncols=%d", row, col, nr, nc);
958 [self drawInvertedRectAtRow:row column:col numRows:nr
960 } else if (SetCursorPosDrawType == type) {
961 // TODO: This is used for Voice Over support in MMTextView,
962 // MMCoreTextView currently does not support Voice Over.
964 int row = *((int*)bytes); bytes += sizeof(int);
965 int col = *((int*)bytes); bytes += sizeof(int);
966 ASLogNotice(@" Set cursor row=%d col=%d", row, col);
968 /*cursorRow = *((int*)bytes);*/ bytes += sizeof(int);
969 /*cursorCol = *((int*)bytes);*/ bytes += sizeof(int);
972 ASLogWarn(@"Unknown draw type (type=%d)", type);
977 ASLogNotice(@"<==== END %s", _cmd);
982 recurseDraw(const unichar *chars, CGGlyph *glyphs, CGSize *advances,
983 UniCharCount length, CGContextRef context, CTFontRef fontRef,
986 CGFontRef cgFontRef = CTFontCopyGraphicsFont(fontRef, NULL);
988 if (CTFontGetGlyphsForCharacters(fontRef, chars, glyphs, length)) {
989 // All chars were mapped to glyphs, so draw all at once and return.
990 CGContextSetFont(context, cgFontRef);
991 CGContextSetTextPosition(context, x, y);
992 CGContextShowGlyphsWithAdvances(context, glyphs, advances, length);
993 CGFontRelease(cgFontRef);
997 CGGlyph *glyphsEnd = glyphs+length, *g = glyphs;
998 CGSize *a = advances;
999 const unichar *c = chars;
1001 while (glyphs < glyphsEnd) {
1003 // Draw as many consecutive glyphs as possible in the current font
1004 // (if a glyph is 0 that means it does not exist in the current
1006 while (*g && g < glyphsEnd) {
1013 int count = g-glyphs;
1014 CGContextSetFont(context, cgFontRef);
1015 CGContextSetTextPosition(context, x0, y);
1016 CGContextShowGlyphsWithAdvances(context, glyphs, advances, count);
1018 // Skip past as many consecutive chars as possible which cannot be
1019 // drawn in the current font.
1020 while (0 == *g && g < glyphsEnd) {
1027 // Figure out which font to draw these chars with.
1028 UniCharCount count = c - chars;
1029 CFRange r = { 0, count };
1030 CFStringRef strRef = CFStringCreateWithCharactersNoCopy(
1031 NULL, chars, count, kCFAllocatorNull);
1032 CTFontRef newFontRef = CTFontCreateForString(fontRef, strRef, r);
1034 ASLogNotice(@"Cannot find font to draw chars: %@", strRef);
1035 CGFontRelease(cgFontRef);
1040 recurseDraw(chars, glyphs, advances, count, context, newFontRef,
1043 CFRelease(newFontRef);
1052 CGFontRelease(cgFontRef);
1055 - (void)drawString:(const UniChar *)chars length:(UniCharCount)length
1056 atRow:(int)row column:(int)col cells:(int)cells
1057 withFlags:(int)flags foregroundColor:(int)fg
1058 backgroundColor:(int)bg specialColor:(int)sp
1060 CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
1061 NSRect frame = [self frame];
1062 float x = col*cellSize.width + insetSize.width;
1063 float y = frame.size.height - insetSize.height - (1+row)*cellSize.height;
1064 float w = cellSize.width;
1066 if (flags & DRAW_WIDE) {
1067 // NOTE: It is assumed that either all characters in 'chars' are wide
1068 // or all are normal width.
1072 CGContextSaveGState(context);
1074 // NOTE! 'cells' is zero if we're drawing a composing character
1075 CGFloat clipWidth = cells > 0 ? cells*cellSize.width : w;
1076 CGRect clipRect = { {x, y}, {clipWidth, cellSize.height} };
1077 CGContextClipToRect(context, clipRect);
1079 if (!(flags & DRAW_TRANSP)) {
1080 // Draw the background of the text. Note that if we ignore the
1081 // DRAW_TRANSP flag and always draw the background, then the insert
1082 // mode cursor is drawn over.
1083 CGRect rect = { {x, y}, {cells*cellSize.width, cellSize.height} };
1084 CGContextSetRGBFillColor(context, RED(bg), GREEN(bg), BLUE(bg),
1087 // Antialiasing may cause bleeding effects which are highly undesirable
1088 // when clearing the background (this code is also called to draw the
1089 // cursor sometimes) so disable it temporarily.
1090 CGContextSetShouldAntialias(context, NO);
1091 CGContextFillRect(context, rect);
1092 CGContextSetShouldAntialias(context, antialias);
1096 if (flags & DRAW_UNDERL) {
1098 CGRect rect = { {x, y+0.4*fontDescent}, {cells*cellSize.width, 1} };
1099 CGContextSetRGBFillColor(context, RED(sp), GREEN(sp), BLUE(sp),
1101 CGContextFillRect(context, rect);
1102 } else if (flags & DRAW_UNDERC) {
1103 // Draw curly underline
1105 float x0 = x, y0 = y+1, w = cellSize.width, h = 0.5*fontDescent;
1107 CGContextMoveToPoint(context, x0, y0);
1108 for (k = 0; k < cells; ++k) {
1109 CGContextAddCurveToPoint(context, x0+0.25*w, y0, x0+0.25*w, y0+h,
1111 CGContextAddCurveToPoint(context, x0+0.75*w, y0+h, x0+0.75*w, y0,
1116 CGContextSetRGBStrokeColor(context, RED(sp), GREEN(sp), BLUE(sp),
1118 CGContextStrokePath(context);
1121 if (length > maxlen) {
1122 if (glyphs) free(glyphs);
1123 if (advances) free(advances);
1124 glyphs = (CGGlyph*)malloc(length*sizeof(CGGlyph));
1125 advances = (CGSize*)calloc(length, sizeof(CGSize));
1129 CGContextSetTextMatrix(context, CGAffineTransformIdentity);
1130 CGContextSetTextDrawingMode(context, kCGTextFill);
1131 CGContextSetRGBFillColor(context, RED(fg), GREEN(fg), BLUE(fg), ALPHA(fg));
1132 CGContextSetFontSize(context, [font pointSize]);
1135 for (i = 0; i < length; ++i)
1136 advances[i].width = w;
1138 CTFontRef fontRef = (CTFontRef)[font retain];
1140 unsigned traits = 0;
1141 if (flags & DRAW_ITALIC)
1142 traits |= kCTFontItalicTrait;
1143 if (flags & DRAW_BOLD)
1144 traits |= kCTFontBoldTrait;
1147 CTFontRef fr = CTFontCreateCopyWithSymbolicTraits(fontRef, 0.0, NULL,
1155 recurseDraw(chars, glyphs, advances, length, context, fontRef, x,
1159 CGContextRestoreGState(context);
1162 - (void)scrollRect:(NSRect)rect lineCount:(int)count
1164 NSPoint destPoint = rect.origin;
1165 destPoint.y -= count * cellSize.height;
1167 NSCopyBits(0, rect, destPoint);
1170 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
1171 scrollBottom:(int)bottom left:(int)left right:(int)right
1174 NSRect rect = [self rectFromRow:row + count
1179 // move rect up for count lines
1180 [self scrollRect:rect lineCount:-count];
1181 [self clearBlockFromRow:bottom - count + 1
1188 - (void)insertLinesAtRow:(int)row lineCount:(int)count
1189 scrollBottom:(int)bottom left:(int)left right:(int)right
1192 NSRect rect = [self rectFromRow:row
1194 toRow:bottom - count
1197 // move rect down for count lines
1198 [self scrollRect:rect lineCount:count];
1199 [self clearBlockFromRow:row
1201 toRow:row + count - 1
1206 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
1207 column:(int)col2 color:(int)color
1209 CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
1210 NSRect rect = [self rectFromRow:row1 column:col1 toRow:row2 column:col2];
1212 CGContextSetRGBFillColor(context, RED(color), GREEN(color), BLUE(color),
1214 CGContextFillRect(context, *(CGRect*)&rect);
1219 CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
1220 NSRect rect = [self frame];
1221 float r = [defaultBackgroundColor redComponent];
1222 float g = [defaultBackgroundColor greenComponent];
1223 float b = [defaultBackgroundColor blueComponent];
1224 float a = [defaultBackgroundColor alphaComponent];
1226 CGContextSetRGBFillColor(context, r, g, b, a);
1227 CGContextFillRect(context, *(CGRect*)&rect);
1230 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
1231 fraction:(int)percent color:(int)color
1233 CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
1234 NSRect rect = [self rectForRow:row column:col numRows:1 numColumns:1];
1236 if (MMInsertionPointHorizontal == shape) {
1237 int frac = (cellSize.height * percent + 99)/100;
1238 rect.size.height = frac;
1239 } else if (MMInsertionPointVertical == shape) {
1240 int frac = (cellSize.width * percent + 99)/100;
1241 rect.size.width = frac;
1242 } else if (MMInsertionPointVerticalRight == shape) {
1243 int frac = (cellSize.width * percent + 99)/100;
1244 rect.origin.x += rect.size.width - frac;
1245 rect.size.width = frac;
1248 // Temporarily disable antialiasing since we are only drawing square
1249 // cursors. Failing to disable antialiasing can cause the cursor to bleed
1250 // over into adjacent display cells and it may look ugly.
1251 CGContextSetShouldAntialias(context, NO);
1253 if (MMInsertionPointHollow == shape) {
1254 // When stroking a rect its size is effectively 1 pixel wider/higher
1255 // than we want so make it smaller to avoid having it bleed over into
1256 // the adjacent display cell.
1257 rect.size.width -= 1;
1258 rect.size.height -= 1;
1260 CGContextSetRGBStrokeColor(context, RED(color), GREEN(color),
1261 BLUE(color), ALPHA(color));
1262 CGContextStrokeRect(context, *(CGRect*)&rect);
1264 CGContextSetRGBFillColor(context, RED(color), GREEN(color), BLUE(color),
1266 CGContextFillRect(context, *(CGRect*)&rect);
1269 CGContextSetShouldAntialias(context, antialias);
1272 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nrows
1273 numColumns:(int)ncols
1275 // TODO: THIS CODE HAS NOT BEEN TESTED!
1276 CGContextRef cgctx = [[NSGraphicsContext currentContext] graphicsPort];
1277 CGContextSaveGState(cgctx);
1278 CGContextSetBlendMode(cgctx, kCGBlendModeDifference);
1279 CGContextSetRGBFillColor(cgctx, 1.0, 1.0, 1.0, 1.0);
1281 NSRect rect = [self rectForRow:row column:col numRows:nrows
1283 CGContextFillRect(cgctx, *(CGRect*)&rect);
1285 CGContextRestoreGState(cgctx);
1288 @end // MMCoreTextView (Drawing)