Place scrollbars correctly for positive inset
[MacVim/KaoriYa.git] / src / MacVim / MMCoreTextView.m
blob2c16ae6ec442d7e0fd102a559542471ae751e6d4
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
6  * Do ":help uganda"  in Vim to read copying and usage conditions.
7  * Do ":help credits" in Vim to see a list of people who contributed.
8  * See README.txt for an overview of the Vim source code.
9  */
11  * MMCoreTextView
12  *
13  * Dispatches keyboard and mouse input to the backend.  Handles drag-n-drop of
14  * files onto window.  The rendering is done using CoreText.
15  *
16  * The text view area consists of two parts:
17  *   1. The text area - this is where text is rendered; the size is governed by
18  *      the current number of rows and columns.
19  *   2. The inset area - this is a border around the text area; the size is
20  *      governed by the user defaults MMTextInset[Left|Right|Top|Bottom].
21  *
22  * The current size of the text view frame does not always match the desired
23  * area, i.e. the area determined by the number of rows, columns plus text
24  * inset.  This distinction is particularly important when the view is being
25  * resized.
26  */
28 #import "Miscellaneous.h" // Defines MM_ENABLE_ATSUI
30 #if !MM_ENABLE_ATSUI
32 #import "MMAppController.h"
33 #import "MMCoreTextView.h"
34 #import "MMTextViewHelper.h"
35 #import "MMVimController.h"
36 #import "MMWindowController.h"
39 // TODO: What does DRAW_TRANSP flag do?  If the background isn't drawn when
40 // this flag is set, then sometimes the character after the cursor becomes
41 // blank.  Everything seems to work fine by just ignoring this flag.
42 #define DRAW_TRANSP               0x01    /* draw with transparent bg */
43 #define DRAW_BOLD                 0x02    /* draw bold text */
44 #define DRAW_UNDERL               0x04    /* draw underline text */
45 #define DRAW_UNDERC               0x08    /* draw undercurl text */
46 #define DRAW_ITALIC               0x10    /* draw italic text */
47 #define DRAW_CURSOR               0x20
48 #define DRAW_WIDE                 0x40    /* draw wide text */
51 #define BLUE(argb)      ((argb & 0xff)/255.0f)
52 #define GREEN(argb)     (((argb>>8) & 0xff)/255.0f)
53 #define RED(argb)       (((argb>>16) & 0xff)/255.0f)
54 #define ALPHA(argb)     (((argb>>24) & 0xff)/255.0f)
58 @interface MMCoreTextView (Private)
59 - (MMWindowController *)windowController;
60 - (MMVimController *)vimController;
61 @end
64 @interface MMCoreTextView (Drawing)
65 - (NSPoint)pointForRow:(int)row column:(int)column;
66 - (NSRect)rectForRow:(int)row column:(int)column numRows:(int)nr
67           numColumns:(int)nc;
68 - (NSRect)rectFromRow:(int)row1 column:(int)col1
69                 toRow:(int)row2 column:(int)col2;
70 - (NSSize)textAreaSize;
71 - (void)batchDrawData:(NSData *)data;
72 - (void)drawString:(const UniChar *)chars length:(UniCharCount)length
73              atRow:(int)row column:(int)col cells:(int)cells
74          withFlags:(int)flags foregroundColor:(int)fg
75    backgroundColor:(int)bg specialColor:(int)sp;
76 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
77               scrollBottom:(int)bottom left:(int)left right:(int)right
78                      color:(int)color;
79 - (void)insertLinesAtRow:(int)row lineCount:(int)count
80             scrollBottom:(int)bottom left:(int)left right:(int)right
81                    color:(int)color;
82 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
83                    column:(int)col2 color:(int)color;
84 - (void)clearAll;
85 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
86                        fraction:(int)percent color:(int)color;
87 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nrows
88                    numColumns:(int)ncols;
89 @end
93     static float
94 defaultLineHeightForFont(NSFont *font)
96     // HACK: -[NSFont defaultLineHeightForFont] is deprecated but since the
97     // CoreText renderer does not use NSLayoutManager we create one
98     // temporarily.
99     NSLayoutManager *lm = [[NSLayoutManager alloc] init];
100     float height = [lm defaultLineHeightForFont:font];
101     [lm release];
103     return height;
106     static double
107 defaultAdvanceForFont(CTFontRef fontRef)
109     // We measure the default advance of a font as the advance of its glyph for
110     // 'm'.
111     double advance = 0.0;
112     UniChar characters[] = { 'm' };
113     int count = 1;
114     CGGlyph glyphs[] = { 0 };
116     if (CTFontGetGlyphsForCharacters(fontRef, characters, glyphs, count)) {
117         advance = CTFontGetAdvancesForGlyphs(fontRef,
118                                              kCTFontDefaultOrientation,
119                                              glyphs,
120                                              NULL,
121                                              count);
122     }
124     if (advance < 1.0) {
125         ASLogWarn(@"Could not determine default advance for current font");
126         advance = 10.0f;
127     }
129     return advance;
132 @implementation MMCoreTextView
134 - (id)initWithFrame:(NSRect)frame
136     if (!(self = [super initWithFrame:frame]))
137         return nil;
139     // NOTE!  It does not matter which font is set here, Vim will set its
140     // own font on startup anyway.  Just set some bogus values.
141     font = [[NSFont userFixedPitchFontOfSize:0] retain];
142     cellSize.width = cellSize.height = 1;
144     // NOTE: If the default changes to 'NO' then the intialization of
145     // p_antialias in option.c must change as well.
146     antialias = YES;
148     drawData = [[NSMutableArray alloc] init];
150     helper = [[MMTextViewHelper alloc] init];
151     [helper setTextView:self];
153     [self registerForDraggedTypes:[NSArray arrayWithObjects:
154             NSFilenamesPboardType, NSStringPboardType, nil]];
156     return self;
159 - (void)dealloc
161     [font release];  font = nil;
162     [defaultBackgroundColor release];  defaultBackgroundColor = nil;
163     [defaultForegroundColor release];  defaultForegroundColor = nil;
164     [drawData release];  drawData = nil;
166     [helper setTextView:nil];
167     [helper release];  helper = nil;
169     if (glyphs) { free(glyphs); glyphs = NULL; }
170     if (advances) { free(advances); advances = NULL; }
172     [super dealloc];
175 - (int)maxRows
177     return maxRows;
180 - (int)maxColumns
182     return maxColumns;
185 - (void)getMaxRows:(int*)rows columns:(int*)cols
187     if (rows) *rows = maxRows;
188     if (cols) *cols = maxColumns;
191 - (void)setMaxRows:(int)rows columns:(int)cols
193     // NOTE: Just remember the new values, the actual resizing is done lazily.
194     maxRows = rows;
195     maxColumns = cols;
198 - (void)setDefaultColorsBackground:(NSColor *)bgColor
199                         foreground:(NSColor *)fgColor
201     if (defaultBackgroundColor != bgColor) {
202         [defaultBackgroundColor release];
203         defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
204     }
206     // NOTE: The default foreground color isn't actually used for anything, but
207     // other class instances might want to be able to access it so it is stored
208     // here.
209     if (defaultForegroundColor != fgColor) {
210         [defaultForegroundColor release];
211         defaultForegroundColor = fgColor ? [fgColor retain] : nil;
212     }
215 - (NSColor *)defaultBackgroundColor
217     return defaultBackgroundColor;
220 - (NSColor *)defaultForegroundColor
222     return defaultForegroundColor;
225 - (void)setTextContainerInset:(NSSize)size
227     insetSize = size;
230 - (NSRect)rectForRowsInRange:(NSRange)range
232     // Compute rect whose vertical dimensions cover the rows in the given
233     // range.
234     // NOTE: The rect should be in _flipped_ coordinates and the first row must
235     // include the top inset as well.  (This method is only used to place the
236     // scrollbars inside MMVimView.)
238     NSRect rect = { {0, 0}, {0, 0} };
239     unsigned start = range.location > maxRows ? maxRows : range.location;
240     unsigned length = range.length;
242     if (start + length > maxRows)
243         length = maxRows - start;
245     if (start > 0) {
246         rect.origin.y = cellSize.height * start + insetSize.height;
247         rect.size.height = cellSize.height * length;
248     } else {
249         // Include top inset
250         rect.origin.y = 0;
251         rect.size.height = cellSize.height * length + insetSize.height;
252     }
254     return rect;
257 - (NSRect)rectForColumnsInRange:(NSRange)range
259     // Compute rect whose horizontal dimensions cover the columns in the given
260     // range.
261     // NOTE: The first column must include the left inset.  (This method is
262     // only used to place the scrollbars inside MMVimView.)
264     NSRect rect = { {0, 0}, {0, 0} };
265     unsigned start = range.location > maxColumns ? maxColumns : range.location;
266     unsigned length = range.length;
268     if (start+length > maxColumns)
269         length = maxColumns - start;
271     if (start > 0) {
272         rect.origin.x = cellSize.width * start + insetSize.width;
273         rect.size.width = cellSize.width * length;
274     } else {
275         // Include left inset
276         rect.origin.x = 0;
277         rect.size.width = cellSize.width * length + insetSize.width;
278     }
280     return rect;
284 - (void)setFont:(NSFont *)newFont
286 #if 0
287     if (newFont && font != newFont) {
288         [font release];
289         font = [newFont retain];
291         float em = 7.0; //[newFont widthOfString:@"m"];
292         float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
293                 floatForKey:MMCellWidthMultiplierKey];
295         // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
296         // only render at integer sizes.  Hence, we restrict the cell width to
297         // an integer here, otherwise the window width and the actual text
298         // width will not match.
299         cellSize.width = ceilf(em * cellWidthMultiplier);
300         cellSize.height = linespace + 15.0; //[newFont defaultLineHeightForFont];
301     }
302 #else
303     if (!(newFont && font != newFont))
304         return;
306     double em = round(defaultAdvanceForFont((CTFontRef)newFont));
307     double pt = round([newFont pointSize]);
309     NSDictionary *attr = [NSDictionary dictionaryWithObjectsAndKeys:
310         [newFont displayName], (NSString*)kCTFontNameAttribute,
311         [NSNumber numberWithFloat:pt], (NSString*)kCTFontSizeAttribute,
312         //[NSNumber numberWithFloat:em], (NSString*)kCTFontFixedAdvanceAttribute,
313         nil];
314     CTFontDescriptorRef desc = CTFontDescriptorCreateWithAttributes(
315             (CFDictionaryRef)attr);
316     CTFontRef fontRef = CTFontCreateWithFontDescriptor(desc, pt, NULL);
317     CFRelease(desc);
319     [font release];
320     font = (NSFont*)fontRef;
322     float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
323             floatForKey:MMCellWidthMultiplierKey];
325     // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
326     // only render at integer sizes.  Hence, we restrict the cell width to
327     // an integer here, otherwise the window width and the actual text
328     // width will not match.
329     cellSize.width = ceil(em * cellWidthMultiplier);
330     cellSize.height = linespace + defaultLineHeightForFont(font);
332     fontDescent = ceil(CTFontGetDescent(fontRef));
333 #endif
336 - (void)setWideFont:(NSFont *)newFont
340 - (NSFont *)font
342     return font;
345 - (NSFont *)fontWide
347     return nil;
350 - (NSSize)cellSize
352     return cellSize;
355 - (void)setLinespace:(float)newLinespace
357     linespace = newLinespace;
359     // NOTE: The linespace is added to the cell height in order for a multiline
360     // selection not to have white (background color) gaps between lines.  Also
361     // this simplifies the code a lot because there is no need to check the
362     // linespace when calculating the size of the text view etc.  When the
363     // linespace is non-zero the baseline will be adjusted as well; check
364     // MMTypesetter.
365     cellSize.height = linespace + defaultLineHeightForFont(font);
371 - (void)setShouldDrawInsertionPoint:(BOOL)on
375 - (void)setPreEditRow:(int)row column:(int)col
379 - (void)setMouseShape:(int)shape
381     [helper setMouseShape:shape];
384 - (void)setAntialias:(BOOL)state
386     antialias = state;
389 - (void)setImControl:(BOOL)enable
391     [helper setImControl:enable];
394 - (void)activateIm:(BOOL)enable
396     [helper activateIm:enable];
399 - (BOOL)_wantsKeyDownForEvent:(id)event
401     // HACK! This is an undocumented method which is called from within
402     // -[NSWindow sendEvent] (and perhaps in other places as well) when the
403     // user presses e.g. Ctrl-Tab or Ctrl-Esc .  Returning YES here effectively
404     // disables the Cocoa "key view loop" (which is undesirable).  It may have
405     // other side-effects, but we really _do_ want to process all key down
406     // events so it seems safe to always return YES.
407     return YES;
410 - (void)keyDown:(NSEvent *)event
412     [helper keyDown:event];
415 - (void)insertText:(id)string
417     [helper insertText:string];
420 - (void)doCommandBySelector:(SEL)selector
422     [helper doCommandBySelector:selector];
425 - (BOOL)performKeyEquivalent:(NSEvent *)event
427     return [helper performKeyEquivalent:event];
430 - (BOOL)hasMarkedText
432     return [helper hasMarkedText];
435 - (NSRange)markedRange
437     return [helper markedRange];
440 - (NSDictionary *)markedTextAttributes
442     return [helper markedTextAttributes];
445 - (void)setMarkedTextAttributes:(NSDictionary *)attr
447     [helper setMarkedTextAttributes:attr];
450 - (void)setMarkedText:(id)text selectedRange:(NSRange)range
452     [helper setMarkedText:text selectedRange:range];
455 - (void)unmarkText
457     [helper unmarkText];
460 - (void)scrollWheel:(NSEvent *)event
462     [helper scrollWheel:event];
465 - (void)mouseDown:(NSEvent *)event
467     [helper mouseDown:event];
470 - (void)rightMouseDown:(NSEvent *)event
472     [helper mouseDown:event];
475 - (void)otherMouseDown:(NSEvent *)event
477     [helper mouseDown:event];
480 - (void)mouseUp:(NSEvent *)event
482     [helper mouseUp:event];
485 - (void)rightMouseUp:(NSEvent *)event
487     [helper mouseUp:event];
490 - (void)otherMouseUp:(NSEvent *)event
492     [helper mouseUp:event];
495 - (void)mouseDragged:(NSEvent *)event
497     [helper mouseDragged:event];
500 - (void)rightMouseDragged:(NSEvent *)event
502     [helper mouseDragged:event];
505 - (void)otherMouseDragged:(NSEvent *)event
507     [helper mouseDragged:event];
510 - (void)mouseMoved:(NSEvent *)event
512     [helper mouseMoved:event];
515 - (void)mouseEntered:(NSEvent *)event
517     [helper mouseEntered:event];
520 - (void)mouseExited:(NSEvent *)event
522     [helper mouseExited:event];
525 - (void)setFrame:(NSRect)frame
527     [super setFrame:frame];
528     [helper setFrame:frame];
531 - (void)viewDidMoveToWindow
533     [helper viewDidMoveToWindow];
536 - (void)viewWillMoveToWindow:(NSWindow *)newWindow
538     [helper viewWillMoveToWindow:newWindow];
541 - (NSMenu*)menuForEvent:(NSEvent *)event
543     // HACK! Return nil to disable default popup menus (Vim provides its own).
544     // Called when user Ctrl-clicks in the view (this is already handled in
545     // rightMouseDown:).
546     return nil;
549 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
551     return [helper performDragOperation:sender];
554 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
556     return [helper draggingEntered:sender];
559 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
561     return [helper draggingUpdated:sender];
566 - (BOOL)mouseDownCanMoveWindow
568     return NO;
571 - (BOOL)isOpaque
573     return YES;
576 - (BOOL)acceptsFirstResponder
578     return YES;
581 - (BOOL)isFlipped
583     return NO;
586 - (void)drawRect:(NSRect)rect
588     //ASLogTmp(@"count=%d  rect=%@", [drawData count],
589     //        NSStringFromRect(rect));
591     NSGraphicsContext *context = [NSGraphicsContext currentContext];
592     [context setShouldAntialias:antialias];
594     id data;
595     NSEnumerator *e = [drawData objectEnumerator];
596     while ((data = [e nextObject]))
597         [self batchDrawData:data];
599     [drawData removeAllObjects];
602 - (void)performBatchDrawWithData:(NSData *)data
604     [drawData addObject:data];
605     [self setNeedsDisplay:YES];
607     // NOTE: During resizing, Cocoa only sends draw messages before Vim's rows
608     // and columns are changed (due to ipc delays). Force a redraw here.
609     if ([self inLiveResize])
610         [self display];
613 - (NSSize)constrainRows:(int *)rows columns:(int *)cols toSize:(NSSize)size
615     // TODO:
616     // - Rounding errors may cause size change when there should be none
617     // - Desired rows/columns shold not be 'too small'
619     // Constrain the desired size to the given size.  Values for the minimum
620     // rows and columns are taken from Vim.
621     NSSize desiredSize = [self desiredSize];
622     int desiredRows = maxRows;
623     int desiredCols = maxColumns;
624     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
625     int right = [ud integerForKey:MMTextInsetRightKey];
626     int bot = [ud integerForKey:MMTextInsetBottomKey];
628     if (size.height != desiredSize.height) {
629         float fh = cellSize.height;
630         float ih = insetSize.height + bot;
631         if (fh < 1.0f) fh = 1.0f;
633         desiredRows = floor((size.height - ih)/fh);
634         desiredSize.height = fh*desiredRows + ih;
635     }
637     if (size.width != desiredSize.width) {
638         float fw = cellSize.width;
639         float iw = insetSize.width + right;
640         if (fw < 1.0f) fw = 1.0f;
642         desiredCols = floor((size.width - iw)/fw);
643         desiredSize.width = fw*desiredCols + iw;
644     }
646     if (rows) *rows = desiredRows;
647     if (cols) *cols = desiredCols;
649     return desiredSize;
652 - (NSSize)desiredSize
654     // Compute the size the text view should be for the entire text area and
655     // inset area to be visible with the present number of rows and columns.
656     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
657     int right = [ud integerForKey:MMTextInsetRightKey];
658     int bot = [ud integerForKey:MMTextInsetBottomKey];
660     return NSMakeSize(maxColumns * cellSize.width + insetSize.width + right,
661                       maxRows * cellSize.height + insetSize.height + bot);
664 - (NSSize)minSize
666     // Compute the smallest size the text view is allowed to be.
667     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
668     int right = [ud integerForKey:MMTextInsetRightKey];
669     int bot = [ud integerForKey:MMTextInsetBottomKey];
671     return NSMakeSize(MMMinColumns * cellSize.width + insetSize.width + right,
672                       MMMinRows * cellSize.height + insetSize.height + bot);
675 - (void)changeFont:(id)sender
677     NSFont *newFont = [sender convertFont:font];
679     if (newFont) {
680         NSString *name = [newFont displayName];
681         unsigned len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
682         if (len > 0) {
683             NSMutableData *data = [NSMutableData data];
684             float pointSize = [newFont pointSize];
686             [data appendBytes:&pointSize length:sizeof(float)];
688             ++len;  // include NUL byte
689             [data appendBytes:&len length:sizeof(unsigned)];
690             [data appendBytes:[name UTF8String] length:len];
692             [[self vimController] sendMessage:SetFontMsgID data:data];
693         }
694     }
699 // NOTE: The menu items cut/copy/paste/undo/redo/select all/... must be bound
700 // to the same actions as in IB otherwise they will not work with dialogs.  All
701 // we do here is forward these actions to the Vim process.
703 - (IBAction)cut:(id)sender
705     [[self windowController] vimMenuItemAction:sender];
708 - (IBAction)copy:(id)sender
710     [[self windowController] vimMenuItemAction:sender];
713 - (IBAction)paste:(id)sender
715     [[self windowController] vimMenuItemAction:sender];
718 - (IBAction)undo:(id)sender
720     [[self windowController] vimMenuItemAction:sender];
723 - (IBAction)redo:(id)sender
725     [[self windowController] vimMenuItemAction:sender];
728 - (IBAction)selectAll:(id)sender
730     [[self windowController] vimMenuItemAction:sender];
733 - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column
735     // View is not flipped, instead the atsui code draws to a flipped image;
736     // thus we need to 'flip' the coordinate here since the column number
737     // increases in an up-to-down order.
738     point.y = [self bounds].size.height - point.y;
740     NSPoint origin = { insetSize.width, insetSize.height };
742     if (!(cellSize.width > 0 && cellSize.height > 0))
743         return NO;
745     if (row) *row = floor((point.y-origin.y-1) / cellSize.height);
746     if (column) *column = floor((point.x-origin.x-1) / cellSize.width);
748     //ASLogDebug(@"point=%@ row=%d col=%d",
749     //      NSStringFromPoint(point), *row, *column);
751     return YES;
754 - (NSArray *)validAttributesForMarkedText
756     return nil;
759 - (NSAttributedString *)attributedSubstringFromRange:(NSRange)range
761     return nil;
764 - (NSUInteger)characterIndexForPoint:(NSPoint)point
766     return NSNotFound;
769 - (NSInteger)conversationIdentifier
771     return (NSInteger)self;
774 - (NSRange)selectedRange
776     return [helper imRange];
779 - (NSRect)firstRectForCharacterRange:(NSRange)range
781     return [helper firstRectForCharacterRange:range];
784 @end // MMCoreTextView
789 @implementation MMCoreTextView (Private)
791 - (MMWindowController *)windowController
793     id windowController = [[self window] windowController];
794     if ([windowController isKindOfClass:[MMWindowController class]])
795         return (MMWindowController*)windowController;
796     return nil;
799 - (MMVimController *)vimController
801     return [[self windowController] vimController];
804 @end // MMCoreTextView (Private)
809 @implementation MMCoreTextView (Drawing)
811 - (NSPoint)pointForRow:(int)row column:(int)col
813     NSRect frame = [self bounds];
814     return NSMakePoint(
815             col*cellSize.width + insetSize.width,
816             frame.size.height - (row+1)*cellSize.height - insetSize.height);
819 - (NSRect)rectForRow:(int)row column:(int)col numRows:(int)nr
820           numColumns:(int)nc
822     NSRect rect;
823     NSRect frame = [self bounds];
825     rect.origin.x = col*cellSize.width + insetSize.width;
826     rect.origin.y = frame.size.height - (row+nr)*cellSize.height -
827                     insetSize.height;
828     rect.size.width = nc*cellSize.width;
829     rect.size.height = nr*cellSize.height;
831     return rect;
834 - (NSRect)rectFromRow:(int)row1 column:(int)col1
835                 toRow:(int)row2 column:(int)col2
837     NSRect frame = [self bounds];
838     return NSMakeRect(
839             insetSize.width + col1*cellSize.width,
840             frame.size.height - insetSize.height - (row2+1)*cellSize.height,
841             (col2 + 1 - col1) * cellSize.width,
842             (row2 + 1 - row1) * cellSize.height);
845 - (NSSize)textAreaSize
847     // Calculate the (desired) size of the text area, i.e. the text view area
848     // minus the inset area.
849     return NSMakeSize(maxColumns * cellSize.width, maxRows * cellSize.height);
852 #define MM_DEBUG_DRAWING 0
854 - (void)batchDrawData:(NSData *)data
856     const void *bytes = [data bytes];
857     const void *end = bytes + [data length];
859 #if MM_DEBUG_DRAWING
860     ASLogNotice(@"====> BEGIN %s", _cmd);
861 #endif
862     // TODO: Sanity check input
864     while (bytes < end) {
865         int type = *((int*)bytes);  bytes += sizeof(int);
867         if (ClearAllDrawType == type) {
868 #if MM_DEBUG_DRAWING
869             ASLogNotice(@"   Clear all");
870 #endif
871             [self clearAll];
872         } else if (ClearBlockDrawType == type) {
873             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
874             int row1 = *((int*)bytes);  bytes += sizeof(int);
875             int col1 = *((int*)bytes);  bytes += sizeof(int);
876             int row2 = *((int*)bytes);  bytes += sizeof(int);
877             int col2 = *((int*)bytes);  bytes += sizeof(int);
879 #if MM_DEBUG_DRAWING
880             ASLogNotice(@"   Clear block (%d,%d) -> (%d,%d)", row1, col1,
881                     row2,col2);
882 #endif
883             [self clearBlockFromRow:row1 column:col1
884                     toRow:row2 column:col2
885                     color:color];
886         } else if (DeleteLinesDrawType == type) {
887             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
888             int row = *((int*)bytes);  bytes += sizeof(int);
889             int count = *((int*)bytes);  bytes += sizeof(int);
890             int bot = *((int*)bytes);  bytes += sizeof(int);
891             int left = *((int*)bytes);  bytes += sizeof(int);
892             int right = *((int*)bytes);  bytes += sizeof(int);
894 #if MM_DEBUG_DRAWING
895             ASLogNotice(@"   Delete %d line(s) from %d", count, row);
896 #endif
897             [self deleteLinesFromRow:row lineCount:count
898                     scrollBottom:bot left:left right:right
899                            color:color];
900         } else if (DrawStringDrawType == type) {
901             int bg = *((int*)bytes);  bytes += sizeof(int);
902             int fg = *((int*)bytes);  bytes += sizeof(int);
903             int sp = *((int*)bytes);  bytes += sizeof(int);
904             int row = *((int*)bytes);  bytes += sizeof(int);
905             int col = *((int*)bytes);  bytes += sizeof(int);
906             int cells = *((int*)bytes);  bytes += sizeof(int);
907             int flags = *((int*)bytes);  bytes += sizeof(int);
908             int len = *((int*)bytes);  bytes += sizeof(int);
909             UInt8 *s = (UInt8 *)bytes;  bytes += len;
911 #if MM_DEBUG_DRAWING
912             ASLogNotice(@"   Draw string len=%d row=%d col=%d flags=%#x",
913                     len, row, col, flags);
914 #endif
916             // Convert UTF-8 chars to UTF-16
917             CFStringRef sref = CFStringCreateWithBytesNoCopy(NULL, s, len,
918                                 kCFStringEncodingUTF8, false, kCFAllocatorNull);
919             CFIndex unilength = CFStringGetLength(sref);
920             const UniChar *unichars = CFStringGetCharactersPtr(sref);
921             UniChar *buffer = NULL;
922             if (unichars == NULL) {
923                 buffer = malloc(unilength * sizeof(UniChar));
924                 CFStringGetCharacters(sref, CFRangeMake(0, unilength), buffer);
925                 unichars = buffer;
926             }
928             [self drawString:unichars length:unilength
929                        atRow:row column:col cells:cells
930                               withFlags:flags
931                         foregroundColor:fg
932                         backgroundColor:bg
933                            specialColor:sp];
935             if (buffer) {
936                 free(buffer);
937                 buffer = NULL;
938             }
939             CFRelease(sref);
940         } else if (InsertLinesDrawType == type) {
941             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
942             int row = *((int*)bytes);  bytes += sizeof(int);
943             int count = *((int*)bytes);  bytes += sizeof(int);
944             int bot = *((int*)bytes);  bytes += sizeof(int);
945             int left = *((int*)bytes);  bytes += sizeof(int);
946             int right = *((int*)bytes);  bytes += sizeof(int);
948 #if MM_DEBUG_DRAWING
949             ASLogNotice(@"   Insert %d line(s) at row %d", count, row);
950 #endif
951             [self insertLinesAtRow:row lineCount:count
952                              scrollBottom:bot left:left right:right
953                                     color:color];
954         } else if (DrawCursorDrawType == type) {
955             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
956             int row = *((int*)bytes);  bytes += sizeof(int);
957             int col = *((int*)bytes);  bytes += sizeof(int);
958             int shape = *((int*)bytes);  bytes += sizeof(int);
959             int percent = *((int*)bytes);  bytes += sizeof(int);
961 #if MM_DEBUG_DRAWING
962             ASLogNotice(@"   Draw cursor at (%d,%d)", row, col);
963 #endif
964             [self drawInsertionPointAtRow:row column:col shape:shape
965                                      fraction:percent
966                                         color:color];
967         } else if (DrawInvertedRectDrawType == type) {
968             int row = *((int*)bytes);  bytes += sizeof(int);
969             int col = *((int*)bytes);  bytes += sizeof(int);
970             int nr = *((int*)bytes);  bytes += sizeof(int);
971             int nc = *((int*)bytes);  bytes += sizeof(int);
972             /*int invert = *((int*)bytes);*/  bytes += sizeof(int);
974 #if MM_DEBUG_DRAWING
975             ASLogNotice(@"   Draw inverted rect: row=%d col=%d nrows=%d "
976                    "ncols=%d", row, col, nr, nc);
977 #endif
978             [self drawInvertedRectAtRow:row column:col numRows:nr
979                              numColumns:nc];
980         } else if (SetCursorPosDrawType == type) {
981             // TODO: This is used for Voice Over support in MMTextView,
982             // MMCoreTextView currently does not support Voice Over.
983 #if MM_DEBUG_DRAWING
984             int row = *((int*)bytes);  bytes += sizeof(int);
985             int col = *((int*)bytes);  bytes += sizeof(int);
986             ASLogNotice(@"   Set cursor row=%d col=%d", row, col);
987 #else
988             /*cursorRow = *((int*)bytes);*/  bytes += sizeof(int);
989             /*cursorCol = *((int*)bytes);*/  bytes += sizeof(int);
990 #endif
991         } else {
992             ASLogWarn(@"Unknown draw type (type=%d)", type);
993         }
994     }
996 #if MM_DEBUG_DRAWING
997     ASLogNotice(@"<==== END   %s", _cmd);
998 #endif
1001     static void
1002 recurseDraw(const unichar *chars, CGGlyph *glyphs, CGSize *advances,
1003             UniCharCount length, CGContextRef context, CTFontRef fontRef,
1004             float x, float y)
1006     CGFontRef cgFontRef = CTFontCopyGraphicsFont(fontRef, NULL);
1008     if (CTFontGetGlyphsForCharacters(fontRef, chars, glyphs, length)) {
1009         // All chars were mapped to glyphs, so draw all at once and return.
1010         CGContextSetFont(context, cgFontRef);
1011         CGContextSetTextPosition(context, x, y);
1012         CGContextShowGlyphsWithAdvances(context, glyphs, advances, length);
1013         CGFontRelease(cgFontRef);
1014         return;
1015     }
1017     CGGlyph *glyphsEnd = glyphs+length, *g = glyphs;
1018     CGSize *a = advances;
1019     const unichar *c = chars;
1020     float x0 = x;
1021     while (glyphs < glyphsEnd) {
1022         if (*g) {
1023             // Draw as many consecutive glyphs as possible in the current font
1024             // (if a glyph is 0 that means it does not exist in the current
1025             // font).
1026             while (*g && g < glyphsEnd) {
1027                 ++g;
1028                 ++c;
1029                 x += a->width;
1030                 ++a;
1031             }
1033             int count = g-glyphs;
1034             CGContextSetFont(context, cgFontRef);
1035             CGContextSetTextPosition(context, x0, y);
1036             CGContextShowGlyphsWithAdvances(context, glyphs, advances, count);
1037         } else {
1038             // Skip past as many consecutive chars as possible which cannot be
1039             // drawn in the current font.
1040             while (0 == *g && g < glyphsEnd) {
1041                 ++g;
1042                 ++c;
1043                 x += a->width;
1044                 ++a;
1045             }
1047             // Figure out which font to draw these chars with.
1048             UniCharCount count = c - chars;
1049             CFRange r = { 0, count };
1050             CFStringRef strRef = CFStringCreateWithCharactersNoCopy(
1051                     NULL, chars, count, kCFAllocatorNull);
1052             CTFontRef newFontRef = CTFontCreateForString(fontRef, strRef, r);
1053             if (!newFontRef) {
1054                 ASLogNotice(@"Cannot find font to draw chars: %@", strRef);
1055                 CGFontRelease(cgFontRef);
1056                 return;
1057             }
1058             CFRelease(strRef);
1060             recurseDraw(chars, glyphs, advances, count, context, newFontRef,
1061                         x0, y);
1063             CFRelease(newFontRef);
1064         }
1066         chars = c;
1067         glyphs = g;
1068         advances = a;
1069         x0 = x;
1070     }
1072     CGFontRelease(cgFontRef);
1075 - (void)drawString:(const UniChar *)chars length:(UniCharCount)length
1076              atRow:(int)row column:(int)col cells:(int)cells
1077          withFlags:(int)flags foregroundColor:(int)fg
1078    backgroundColor:(int)bg specialColor:(int)sp
1080     CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
1081     NSRect frame = [self bounds];
1082     float x = col*cellSize.width + insetSize.width;
1083     float y = frame.size.height - insetSize.height - (1+row)*cellSize.height;
1084     float w = cellSize.width;
1086     if (flags & DRAW_WIDE) {
1087         // NOTE: It is assumed that either all characters in 'chars' are wide
1088         // or all are normal width.
1089         w *= 2;
1090     }
1092     CGContextSaveGState(context);
1094     // NOTE!  'cells' is zero if we're drawing a composing character
1095     CGFloat clipWidth = cells > 0 ? cells*cellSize.width : w;
1096     CGRect clipRect = { {x, y}, {clipWidth, cellSize.height} };
1097     CGContextClipToRect(context, clipRect);
1099     if (!(flags & DRAW_TRANSP)) {
1100         // Draw the background of the text.  Note that if we ignore the
1101         // DRAW_TRANSP flag and always draw the background, then the insert
1102         // mode cursor is drawn over.
1103         CGRect rect = { {x, y}, {cells*cellSize.width, cellSize.height} };
1104         CGContextSetRGBFillColor(context, RED(bg), GREEN(bg), BLUE(bg),
1105                                  ALPHA(bg));
1107         // Antialiasing may cause bleeding effects which are highly undesirable
1108         // when clearing the background (this code is also called to draw the
1109         // cursor sometimes) so disable it temporarily.
1110         CGContextSetShouldAntialias(context, NO);
1111         CGContextSetBlendMode(context, kCGBlendModeCopy);
1112         CGContextFillRect(context, rect);
1113         CGContextSetShouldAntialias(context, antialias);
1114         CGContextSetBlendMode(context, kCGBlendModeNormal);
1115     }
1117     if (flags & DRAW_UNDERL) {
1118         // Draw underline
1119         CGRect rect = { {x, y+0.4*fontDescent}, {cells*cellSize.width, 1} };
1120         CGContextSetRGBFillColor(context, RED(sp), GREEN(sp), BLUE(sp),
1121                                  ALPHA(sp));
1122         CGContextFillRect(context, rect);
1123     } else if (flags & DRAW_UNDERC) {
1124         // Draw curly underline
1125         int k;
1126         float x0 = x, y0 = y+1, w = cellSize.width, h = 0.5*fontDescent;
1128         CGContextMoveToPoint(context, x0, y0);
1129         for (k = 0; k < cells; ++k) {
1130             CGContextAddCurveToPoint(context, x0+0.25*w, y0, x0+0.25*w, y0+h,
1131                                      x0+0.5*w, y0+h);
1132             CGContextAddCurveToPoint(context, x0+0.75*w, y0+h, x0+0.75*w, y0,
1133                                      x0+w, y0);
1134             x0 += w;
1135         }
1137         CGContextSetRGBStrokeColor(context, RED(sp), GREEN(sp), BLUE(sp),
1138                                  ALPHA(sp));
1139         CGContextStrokePath(context);
1140     }
1142     if (length > maxlen) {
1143         if (glyphs) free(glyphs);
1144         if (advances) free(advances);
1145         glyphs = (CGGlyph*)malloc(length*sizeof(CGGlyph));
1146         advances = (CGSize*)calloc(length, sizeof(CGSize));
1147         maxlen = length;
1148     }
1150     CGContextSetTextMatrix(context, CGAffineTransformIdentity);
1151     CGContextSetTextDrawingMode(context, kCGTextFill);
1152     CGContextSetRGBFillColor(context, RED(fg), GREEN(fg), BLUE(fg), ALPHA(fg));
1153     CGContextSetFontSize(context, [font pointSize]);
1155     NSUInteger i;
1156     for (i = 0; i < length; ++i)
1157         advances[i].width = w;
1159     CTFontRef fontRef = (CTFontRef)[font retain];
1161     unsigned traits = 0;
1162     if (flags & DRAW_ITALIC)
1163         traits |= kCTFontItalicTrait;
1164     if (flags & DRAW_BOLD)
1165         traits |= kCTFontBoldTrait;
1167     if (traits) {
1168         CTFontRef fr = CTFontCreateCopyWithSymbolicTraits(fontRef, 0.0, NULL,
1169                 traits, traits);
1170         if (fr) {
1171             CFRelease(fontRef);
1172             fontRef = fr;
1173         }
1174     }
1176     recurseDraw(chars, glyphs, advances, length, context, fontRef, x,
1177                 y+fontDescent);
1179     CFRelease(fontRef);
1180     CGContextRestoreGState(context);
1183 - (void)scrollRect:(NSRect)rect lineCount:(int)count
1185     NSPoint destPoint = rect.origin;
1186     destPoint.y -= count * cellSize.height;
1188     NSCopyBits(0, rect, destPoint);
1191 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
1192               scrollBottom:(int)bottom left:(int)left right:(int)right
1193                      color:(int)color
1195     NSRect rect = [self rectFromRow:row + count
1196                              column:left
1197                               toRow:bottom
1198                              column:right];
1200     // move rect up for count lines
1201     [self scrollRect:rect lineCount:-count];
1202     [self clearBlockFromRow:bottom - count + 1
1203                      column:left
1204                       toRow:bottom
1205                      column:right
1206                       color:color];
1209 - (void)insertLinesAtRow:(int)row lineCount:(int)count
1210             scrollBottom:(int)bottom left:(int)left right:(int)right
1211                    color:(int)color
1213     NSRect rect = [self rectFromRow:row
1214                              column:left
1215                               toRow:bottom - count
1216                              column:right];
1218     // move rect down for count lines
1219     [self scrollRect:rect lineCount:count];
1220     [self clearBlockFromRow:row
1221                      column:left
1222                       toRow:row + count - 1
1223                      column:right
1224                       color:color];
1227 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
1228                    column:(int)col2 color:(int)color
1230     CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
1231     NSRect rect = [self rectFromRow:row1 column:col1 toRow:row2 column:col2];
1233     CGContextSetRGBFillColor(context, RED(color), GREEN(color), BLUE(color),
1234                              ALPHA(color));
1236     CGContextSetBlendMode(context, kCGBlendModeCopy);
1237     CGContextFillRect(context, *(CGRect*)&rect);
1238     CGContextSetBlendMode(context, kCGBlendModeNormal);
1241 - (void)clearAll
1243     CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
1244     NSRect rect = [self bounds];
1245     float r = [defaultBackgroundColor redComponent];
1246     float g = [defaultBackgroundColor greenComponent];
1247     float b = [defaultBackgroundColor blueComponent];
1248     float a = [defaultBackgroundColor alphaComponent];
1250     CGContextSetBlendMode(context, kCGBlendModeCopy);
1251     CGContextSetRGBFillColor(context, r, g, b, a);
1252     CGContextFillRect(context, *(CGRect*)&rect);
1253     CGContextSetBlendMode(context, kCGBlendModeNormal);
1256 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
1257                        fraction:(int)percent color:(int)color
1259     CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
1260     NSRect rect = [self rectForRow:row column:col numRows:1 numColumns:1];
1262     if (MMInsertionPointHorizontal == shape) {
1263         int frac = (cellSize.height * percent + 99)/100;
1264         rect.size.height = frac;
1265     } else if (MMInsertionPointVertical == shape) {
1266         int frac = (cellSize.width * percent + 99)/100;
1267         rect.size.width = frac;
1268     } else if (MMInsertionPointVerticalRight == shape) {
1269         int frac = (cellSize.width * percent + 99)/100;
1270         rect.origin.x += rect.size.width - frac;
1271         rect.size.width = frac;
1272     }
1274     // Temporarily disable antialiasing since we are only drawing square
1275     // cursors.  Failing to disable antialiasing can cause the cursor to bleed
1276     // over into adjacent display cells and it may look ugly.
1277     CGContextSetShouldAntialias(context, NO);
1279     if (MMInsertionPointHollow == shape) {
1280         // When stroking a rect its size is effectively 1 pixel wider/higher
1281         // than we want so make it smaller to avoid having it bleed over into
1282         // the adjacent display cell.
1283         rect.size.width -= 1;
1284         rect.size.height -= 1;
1286         CGContextSetRGBStrokeColor(context, RED(color), GREEN(color),
1287                                    BLUE(color), ALPHA(color));
1288         CGContextStrokeRect(context, *(CGRect*)&rect);
1289     } else {
1290         CGContextSetRGBFillColor(context, RED(color), GREEN(color), BLUE(color),
1291                                  ALPHA(color));
1292         CGContextFillRect(context, *(CGRect*)&rect);
1293     }
1295     CGContextSetShouldAntialias(context, antialias);
1298 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nrows
1299                    numColumns:(int)ncols
1301     // TODO: THIS CODE HAS NOT BEEN TESTED!
1302     CGContextRef cgctx = [[NSGraphicsContext currentContext] graphicsPort];
1303     CGContextSaveGState(cgctx);
1304     CGContextSetBlendMode(cgctx, kCGBlendModeDifference);
1305     CGContextSetRGBFillColor(cgctx, 1.0, 1.0, 1.0, 1.0);
1307     NSRect rect = [self rectForRow:row column:col numRows:nrows
1308                         numColumns:ncols];
1309     CGContextFillRect(cgctx, *(CGRect*)&rect);
1311     CGContextRestoreGState(cgctx);
1314 @end // MMCoreTextView (Drawing)
1316 #endif // !MM_ENABLE_ATSUI