Update Advance preferences pane
[MacVim.git] / src / MacVim / MMAtsuiTextView.m
blobc0f59c5ab7caa0ebe4fd64ef842b15a78a074f6b
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
6  * Do ":help uganda"  in Vim to read copying and usage conditions.
7  * Do ":help credits" in Vim to see a list of people who contributed.
8  * See README.txt for an overview of the Vim source code.
9  */
11  * MMAtsuiTextView
12  *
13  * Dispatches keyboard and mouse input to the backend.  Handles drag-n-drop of
14  * files onto window.  The rendering is done using ATSUI.
15  *
16  * The text view area consists of two parts:
17  *   1. The text area - this is where text is rendered; the size is governed by
18  *      the current number of rows and columns.
19  *   2. The inset area - this is a border around the text area; the size is
20  *      governed by the user defaults MMTextInset[Left|Right|Top|Bottom].
21  *
22  * The current size of the text view frame does not always match the desired
23  * area, i.e. the area determined by the number of rows, columns plus text
24  * inset.  This distinction is particularly important when the view is being
25  * resized.
26  */
28 #if ENABLE_ATSUI
30 #import "MMAppController.h"
31 #import "MMAtsuiTextView.h"
32 #import "MMTextViewHelper.h"
33 #import "MMVimController.h"
34 #import "MMWindowController.h"
35 #import "Miscellaneous.h"
38 // TODO: What does DRAW_TRANSP flag do?  If the background isn't drawn when
39 // this flag is set, then sometimes the character after the cursor becomes
40 // blank.  Everything seems to work fine by just ignoring this flag.
41 #define DRAW_TRANSP               0x01    /* draw with transparant bg */
42 #define DRAW_BOLD                 0x02    /* draw bold text */
43 #define DRAW_UNDERL               0x04    /* draw underline text */
44 #define DRAW_UNDERC               0x08    /* draw undercurl text */
45 #define DRAW_ITALIC               0x10    /* draw italic text */
46 #define DRAW_CURSOR               0x20
48 #define kUnderlineOffset            (-2)
49 #define kUnderlineHeight            1
50 #define kUndercurlHeight            2
51 #define kUndercurlOffset            (-2)
52 #define kUndercurlDotWidth          2
53 #define kUndercurlDotDistance       2
56 @interface NSFont (AppKitPrivate)
57 - (ATSUFontID) _atsFontID;
58 @end
61 @interface MMAtsuiTextView (Private)
62 - (void)initAtsuStyles;
63 - (void)disposeAtsuStyles;
64 - (void)updateAtsuStyles;
65 - (MMWindowController *)windowController;
66 - (MMVimController *)vimController;
67 @end
70 @interface MMAtsuiTextView (Drawing)
71 - (NSPoint)originForRow:(int)row column:(int)column;
72 - (NSRect)rectFromRow:(int)row1 column:(int)col1
73                 toRow:(int)row2 column:(int)col2;
74 - (NSSize)textAreaSize;
75 - (void)resizeContentImage;
76 - (void)beginDrawing;
77 - (void)endDrawing;
78 - (void)drawString:(UniChar *)string length:(UniCharCount)length
79              atRow:(int)row column:(int)col cells:(int)cells
80          withFlags:(int)flags foregroundColor:(NSColor *)fg
81    backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp;
82 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
83               scrollBottom:(int)bottom left:(int)left right:(int)right
84                      color:(NSColor *)color;
85 - (void)insertLinesAtRow:(int)row lineCount:(int)count
86             scrollBottom:(int)bottom left:(int)left right:(int)right
87                    color:(NSColor *)color;
88 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
89                    column:(int)col2 color:(NSColor *)color;
90 - (void)clearAll;
91 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
92                        fraction:(int)percent;
93 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nrows
94                    numColumns:(int)ncols;
95 @end
99     static float
100 defaultLineHeightForFont(NSFont *font)
102     // HACK: -[NSFont defaultLineHeightForFont] is deprecated but since the
103     // ATSUI renderer does not use NSLayoutManager we create one temporarily.
104     NSLayoutManager *lm = [[NSLayoutManager alloc] init];
105     float height = [lm defaultLineHeightForFont:font];
106     [lm release];
108     return height;
111 @implementation MMAtsuiTextView
113 - (id)initWithFrame:(NSRect)frame
115     if (!(self = [super initWithFrame:frame]))
116         return nil;
118     // NOTE!  It does not matter which font is set here, Vim will set its
119     // own font on startup anyway.  Just set some bogus values.
120     font = [[NSFont userFixedPitchFontOfSize:0] retain];
121     ascender = 0;
122     cellSize.width = cellSize.height = 1;
123     contentImage = nil;
124     imageSize = NSZeroSize;
125     insetSize = NSZeroSize;
127     // NOTE: If the default changes to 'NO' then the intialization of
128     // p_antialias in option.c must change as well.
129     antialias = YES;
131     helper = [[MMTextViewHelper alloc] init];
132     [helper setTextView:self];
134     [self initAtsuStyles];
136     [self registerForDraggedTypes:[NSArray arrayWithObjects:
137             NSFilenamesPboardType, NSStringPboardType, nil]];
139     return self;
142 - (void)dealloc
144     ASLogDebug(@"");
146     [self disposeAtsuStyles];
147     [font release];  font = nil;
148     [defaultBackgroundColor release];  defaultBackgroundColor = nil;
149     [defaultForegroundColor release];  defaultForegroundColor = nil;
151     [helper setTextView:nil];
152     [helper release];  helper = nil;
154     [super dealloc];
157 - (int)maxRows
159     return maxRows;
162 - (int)maxColumns
164     return maxColumns;
167 - (void)getMaxRows:(int*)rows columns:(int*)cols
169     if (rows) *rows = maxRows;
170     if (cols) *cols = maxColumns;
173 - (void)setMaxRows:(int)rows columns:(int)cols
175     // NOTE: Just remember the new values, the actual resizing is done lazily.
176     maxRows = rows;
177     maxColumns = cols;
180 - (void)setDefaultColorsBackground:(NSColor *)bgColor
181                         foreground:(NSColor *)fgColor
183     if (defaultBackgroundColor != bgColor) {
184         [defaultBackgroundColor release];
185         defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
186     }
188     if (defaultForegroundColor != fgColor) {
189         [defaultForegroundColor release];
190         defaultForegroundColor = fgColor ? [fgColor retain] : nil;
191     }
194 - (NSColor *)defaultBackgroundColor
196     return defaultBackgroundColor;
199 - (NSColor *)defaultForegroundColor
201     return defaultForegroundColor;
204 - (void)setTextContainerInset:(NSSize)size
206     insetSize = size;
209 - (NSRect)rectForRowsInRange:(NSRange)range
211     NSRect rect = { 0, 0, 0, 0 };
212     unsigned start = range.location > maxRows ? maxRows : range.location;
213     unsigned length = range.length;
215     if (start + length > maxRows)
216         length = maxRows - start;
218     rect.origin.y = cellSize.height * start + insetSize.height;
219     rect.size.height = cellSize.height * length;
221     return rect;
224 - (NSRect)rectForColumnsInRange:(NSRange)range
226     NSRect rect = { 0, 0, 0, 0 };
227     unsigned start = range.location > maxColumns ? maxColumns : range.location;
228     unsigned length = range.length;
230     if (start+length > maxColumns)
231         length = maxColumns - start;
233     rect.origin.x = cellSize.width * start + insetSize.width;
234     rect.size.width = cellSize.width * length;
236     return rect;
240 - (void)setFont:(NSFont *)newFont
242     if (newFont && font != newFont) {
243         [font release];
244         font = [newFont retain];
245         ascender = roundf([font ascender]);
247         float em = [@"m" sizeWithAttributes:
248                 [NSDictionary dictionaryWithObject:newFont
249                                             forKey:NSFontAttributeName]].width;
250         float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
251                 floatForKey:MMCellWidthMultiplierKey];
253         // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
254         // only render at integer sizes.  Hence, we restrict the cell width to
255         // an integer here, otherwise the window width and the actual text
256         // width will not match.
257         cellSize.width = ceilf(em * cellWidthMultiplier);
258         cellSize.height = linespace + defaultLineHeightForFont(newFont);
260         [self updateAtsuStyles];
261     }
264 - (void)setWideFont:(NSFont *)newFont
266     if (!newFont) {
267         if (font) [self setWideFont:font];
268     } else if (newFont != fontWide) {
269         float pointSize = [newFont pointSize];
270         NSFontDescriptor *desc = [newFont fontDescriptor];
271         NSDictionary *dictWide = [NSDictionary
272             dictionaryWithObject:[NSNumber numberWithFloat:2*cellSize.width]
273                           forKey:NSFontFixedAdvanceAttribute];
274         desc = [desc fontDescriptorByAddingAttributes:dictWide];
275         fontWide = [NSFont fontWithDescriptor:desc size:pointSize];
276         [fontWide retain];
277     }
280 - (NSFont *)font
282     return font;
285 - (NSFont *)fontWide
287     return fontWide;
290 - (NSSize)cellSize
292     return cellSize;
295 - (void)setLinespace:(float)newLinespace
297     linespace = newLinespace;
299     // NOTE: The linespace is added to the cell height in order for a multiline
300     // selection not to have white (background color) gaps between lines.  Also
301     // this simplifies the code a lot because there is no need to check the
302     // linespace when calculating the size of the text view etc.  When the
303     // linespace is non-zero the baseline will be adjusted as well; check
304     // MMTypesetter.
305     cellSize.height = linespace + defaultLineHeightForFont(font);
310 - (void)setShouldDrawInsertionPoint:(BOOL)on
314 - (void)setPreEditRow:(int)row column:(int)col
316     [helper setPreEditRow:row column:col];
319 - (void)setMouseShape:(int)shape
321     [helper setMouseShape:shape];
324 - (void)setAntialias:(BOOL)state
326     antialias = state;
329 - (void)setImControl:(BOOL)enable
331     [helper setImControl:enable];
334 - (void)activateIm:(BOOL)enable
336     [helper activateIm:enable];
339 - (void)keyDown:(NSEvent *)event
341     [helper keyDown:event];
344 - (void)insertText:(id)string
346     [helper insertText:string];
349 - (void)doCommandBySelector:(SEL)selector
351     [helper doCommandBySelector:selector];
354 - (BOOL)performKeyEquivalent:(NSEvent *)event
356     return [helper performKeyEquivalent:event];
359 - (BOOL)hasMarkedText
361     return [helper hasMarkedText];
364 - (NSRange)markedRange
366     return [helper markedRange];
369 - (NSDictionary *)markedTextAttributes
371     return [helper markedTextAttributes];
374 - (void)setMarkedTextAttributes:(NSDictionary *)attr
376     [helper setMarkedTextAttributes:attr];
379 - (void)setMarkedText:(id)text selectedRange:(NSRange)range
381     [helper setMarkedText:text selectedRange:range];
384 - (void)unmarkText
386     [helper unmarkText];
389 - (void)scrollWheel:(NSEvent *)event
391     [helper scrollWheel:event];
394 - (void)mouseDown:(NSEvent *)event
396     [helper mouseDown:event];
399 - (void)rightMouseDown:(NSEvent *)event
401     [helper mouseDown:event];
404 - (void)otherMouseDown:(NSEvent *)event
406     [helper mouseDown:event];
409 - (void)mouseUp:(NSEvent *)event
411     [helper mouseUp:event];
414 - (void)rightMouseUp:(NSEvent *)event
416     [helper mouseUp:event];
419 - (void)otherMouseUp:(NSEvent *)event
421     [helper mouseUp:event];
424 - (void)mouseDragged:(NSEvent *)event
426     [helper mouseDragged:event];
429 - (void)rightMouseDragged:(NSEvent *)event
431     [helper mouseDragged:event];
434 - (void)otherMouseDragged:(NSEvent *)event
436     [helper mouseDragged:event];
439 - (void)mouseMoved:(NSEvent *)event
441     [helper mouseMoved:event];
444 - (void)mouseEntered:(NSEvent *)event
446     [helper mouseEntered:event];
449 - (void)mouseExited:(NSEvent *)event
451     [helper mouseExited:event];
454 - (void)setFrame:(NSRect)frame
456     [super setFrame:frame];
457     [helper setFrame:frame];
460 - (void)viewDidMoveToWindow
462     [helper viewDidMoveToWindow];
465 - (void)viewWillMoveToWindow:(NSWindow *)newWindow
467     [helper viewWillMoveToWindow:newWindow];
470 - (NSMenu*)menuForEvent:(NSEvent *)event
472     // HACK! Return nil to disable default popup menus (Vim provides its own).
473     // Called when user Ctrl-clicks in the view (this is already handled in
474     // rightMouseDown:).
475     return nil;
478 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
480     return [helper performDragOperation:sender];
483 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
485     return [helper draggingEntered:sender];
488 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
490     return [helper draggingUpdated:sender];
495 - (BOOL)mouseDownCanMoveWindow
497     return NO;
500 - (BOOL)isOpaque
502     return YES;
505 - (BOOL)acceptsFirstResponder
507     return YES;
510 - (BOOL)isFlipped
512     return NO;
515 - (void)drawRect:(NSRect)rect
517     [defaultBackgroundColor set];
518     NSRectFill(rect);
520     NSPoint pt = { insetSize.width, insetSize.height };
521     [contentImage compositeToPoint:pt operation:NSCompositeCopy];
523 #ifdef INCLUDE_OLD_IM_CODE
524     if ([self hasMarkedText] && ![helper useInlineIm]) {
525         int len = [[helper markedText] length];
526         int rows = 0;
527         int cols = maxColumns - [helper preEditColumn];
528         NSFont *theFont = [[self markedTextAttributes]
529             valueForKey:NSFontAttributeName];
530         if (theFont == [self fontWide])
531             cols = cols / 2;
532         int done = 0;
533         int lend = cols > len ? len : cols;
534         NSAttributedString *aString = [[helper markedText]
535                 attributedSubstringFromRange:NSMakeRange(done, lend)];
536         NSPoint pt = [self pointForRow:[helper preEditRow]
537                                 column:[helper preEditColumn]];
538         [aString drawAtPoint:pt];
539         done = lend;
540         if (done != len) {
541             int r;
542             rows = (len - done) / (maxColumns / 2) + 1;
543             for (r = 1; r <= rows; r++) {
544             lend = len - done > maxColumns / 2
545                 ? maxColumns / 2 : len - done;
546                 aString = [[helper markedText] attributedSubstringFromRange:
547                         NSMakeRange(done, lend)];
548                 NSPoint pt = [self pointForRow:[helper preEditRow]+r
549                                         column:0];
550                 [aString drawAtPoint:pt];
551                 done += lend;
552             }
553         }
555         rows = maxRows - 1 - [helper preEditRow];
556         cols = [helper preEditColumn];
557         if (theFont == fontWide) {
558             cols += ([helper imRange].location+[helper imRange].length) * 2;
559             if (cols >= maxColumns - 1) {
560                 rows -= cols / maxColumns;
561                 cols = cols % 2 ? cols % maxColumns + 1 :
562                                   cols % maxColumns;
563             }
564         } else {
565             cols += ([helper imRange].location+[helper imRange].length);
566             if (cols >= maxColumns) {
567                 rows -= cols / maxColumns;
568                 cols = cols % 2 ? cols % maxColumns + 1 :
569                                   cols % maxColumns;
570             }
571         }
573         // TODO: Could IM be in "right-left" mode?  If so the insertion point
574         // will be on the wrong side.
575         [self drawInsertionPointAtRow:rows
576                                column:cols
577                                 shape:MMInsertionPointVertical
578                              fraction:25];
579     }
580 #endif // INCLUDE_OLD_IM_CODE
583 - (BOOL) wantsDefaultClipping
585     return NO;
589 #define MM_DEBUG_DRAWING 0
591 - (void)performBatchDrawWithData:(NSData *)data
593     const void *bytes = [data bytes];
594     const void *end = bytes + [data length];
596     if (! NSEqualSizes(imageSize, [self textAreaSize]))
597         [self resizeContentImage];
599 #if MM_DEBUG_DRAWING
600     ASLogDebug(@"====> BEGIN %s", _cmd);
601 #endif
602     [self beginDrawing];
604     // TODO: Sanity check input
606     while (bytes < end) {
607         int type = *((int*)bytes);  bytes += sizeof(int);
609         if (ClearAllDrawType == type) {
610 #if MM_DEBUG_DRAWING
611             ASLogDebug(@"   Clear all");
612 #endif
613             [self clearAll];
614         } else if (ClearBlockDrawType == type) {
615             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
616             int row1 = *((int*)bytes);  bytes += sizeof(int);
617             int col1 = *((int*)bytes);  bytes += sizeof(int);
618             int row2 = *((int*)bytes);  bytes += sizeof(int);
619             int col2 = *((int*)bytes);  bytes += sizeof(int);
621 #if MM_DEBUG_DRAWING
622             ASLogDebug(@"   Clear block (%d,%d) -> (%d,%d)", row1, col1,
623                        row2,col2);
624 #endif
625             [self clearBlockFromRow:row1 column:col1
626                     toRow:row2 column:col2
627                     color:[NSColor colorWithArgbInt:color]];
628         } else if (DeleteLinesDrawType == type) {
629             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
630             int row = *((int*)bytes);  bytes += sizeof(int);
631             int count = *((int*)bytes);  bytes += sizeof(int);
632             int bot = *((int*)bytes);  bytes += sizeof(int);
633             int left = *((int*)bytes);  bytes += sizeof(int);
634             int right = *((int*)bytes);  bytes += sizeof(int);
636 #if MM_DEBUG_DRAWING
637             ASLogDebug(@"   Delete %d line(s) from %d", count, row);
638 #endif
639             [self deleteLinesFromRow:row lineCount:count
640                     scrollBottom:bot left:left right:right
641                            color:[NSColor colorWithArgbInt:color]];
642         } else if (DrawStringDrawType == type) {
643             int bg = *((int*)bytes);  bytes += sizeof(int);
644             int fg = *((int*)bytes);  bytes += sizeof(int);
645             int sp = *((int*)bytes);  bytes += sizeof(int);
646             int row = *((int*)bytes);  bytes += sizeof(int);
647             int col = *((int*)bytes);  bytes += sizeof(int);
648             int cells = *((int*)bytes);  bytes += sizeof(int);
649             int flags = *((int*)bytes);  bytes += sizeof(int);
650             int len = *((int*)bytes);  bytes += sizeof(int);
651             // UniChar *string = (UniChar*)bytes;  bytes += len;
652             NSString *string = [[NSString alloc]
653                     initWithBytesNoCopy:(void*)bytes
654                                  length:len
655                                encoding:NSUTF8StringEncoding
656                            freeWhenDone:NO];
657             bytes += len;
658 #if MM_DEBUG_DRAWING
659             ASLogDebug(@"   Draw string at (%d,%d) length=%d flags=%d fg=0x%x "
660                        "bg=0x%x sp=0x%x", row, col, len, flags, fg, bg, sp);
661 #endif
662             unichar *characters = malloc(sizeof(unichar) * [string length]);
663             [string getCharacters:characters];
665             [self drawString:characters
666                              length:[string length]
667                               atRow:row
668                              column:col
669                               cells:cells withFlags:flags
670                     foregroundColor:[NSColor colorWithRgbInt:fg]
671                     backgroundColor:[NSColor colorWithArgbInt:bg]
672                        specialColor:[NSColor colorWithRgbInt:sp]];
673             free(characters);
674             [string release];
675         } else if (InsertLinesDrawType == type) {
676             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
677             int row = *((int*)bytes);  bytes += sizeof(int);
678             int count = *((int*)bytes);  bytes += sizeof(int);
679             int bot = *((int*)bytes);  bytes += sizeof(int);
680             int left = *((int*)bytes);  bytes += sizeof(int);
681             int right = *((int*)bytes);  bytes += sizeof(int);
683 #if MM_DEBUG_DRAWING
684             ASLogDebug(@"   Insert %d line(s) at row %d", count, row);
685 #endif
686             [self insertLinesAtRow:row lineCount:count
687                              scrollBottom:bot left:left right:right
688                                     color:[NSColor colorWithArgbInt:color]];
689         } else if (DrawCursorDrawType == type) {
690             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
691             int row = *((int*)bytes);  bytes += sizeof(int);
692             int col = *((int*)bytes);  bytes += sizeof(int);
693             int shape = *((int*)bytes);  bytes += sizeof(int);
694             int percent = *((int*)bytes);  bytes += sizeof(int);
696 #if MM_DEBUG_DRAWING
697             ASLogDebug(@"   Draw cursor at (%d,%d)", row, col);
698 #endif
699             [helper setInsertionPointColor:[NSColor colorWithRgbInt:color]];
700             [self drawInsertionPointAtRow:row column:col shape:shape
701                                  fraction:percent];
702         } else if (DrawInvertedRectDrawType == type) {
703             int row = *((int*)bytes);  bytes += sizeof(int);
704             int col = *((int*)bytes);  bytes += sizeof(int);
705             int nr = *((int*)bytes);  bytes += sizeof(int);
706             int nc = *((int*)bytes);  bytes += sizeof(int);
707             /*int invert = *((int*)bytes);*/  bytes += sizeof(int);
709 #if MM_DEBUG_DRAWING
710             ASLogDebug(@"   Draw inverted rect: row=%d col=%d nrows=%d "
711                        "ncols=%d", row, col, nr, nc);
712 #endif
713             [self drawInvertedRectAtRow:row column:col numRows:nr
714                              numColumns:nc];
715         } else if (SetCursorPosDrawType == type) {
716             // TODO: This is used for Voice Over support in MMTextView,
717             // MMAtsuiTextView currently does not support Voice Over.
718             /*cursorRow = *((int*)bytes);*/  bytes += sizeof(int);
719             /*cursorCol = *((int*)bytes);*/  bytes += sizeof(int);
720         } else {
721             ASLogWarn(@"Unknown draw type (type=%d)", type);
722         }
723     }
725     [self endDrawing];
727     [self setNeedsDisplay:YES];
729     // NOTE: During resizing, Cocoa only sends draw messages before Vim's rows
730     // and columns are changed (due to ipc delays). Force a redraw here.
731     if ([self inLiveResize])
732         [self display];
734 #if MM_DEBUG_DRAWING
735     ASLogDebug(@"<==== END   %s", _cmd);
736 #endif
739 - (NSSize)constrainRows:(int *)rows columns:(int *)cols toSize:(NSSize)size
741     // TODO:
742     // - Rounding errors may cause size change when there should be none
743     // - Desired rows/columns shold not be 'too small'
745     // Constrain the desired size to the given size.  Values for the minimum
746     // rows and columns is taken from Vim.
747     NSSize desiredSize = [self desiredSize];
748     int desiredRows = maxRows;
749     int desiredCols = maxColumns;
751     if (size.height != desiredSize.height) {
752         float fh = cellSize.height;
753         float ih = 2 * insetSize.height;
754         if (fh < 1.0f) fh = 1.0f;
756         desiredRows = floor((size.height - ih)/fh);
757         desiredSize.height = fh*desiredRows + ih;
758     }
760     if (size.width != desiredSize.width) {
761         float fw = cellSize.width;
762         float iw = 2 * insetSize.width;
763         if (fw < 1.0f) fw = 1.0f;
765         desiredCols = floor((size.width - iw)/fw);
766         desiredSize.width = fw*desiredCols + iw;
767     }
769     if (rows) *rows = desiredRows;
770     if (cols) *cols = desiredCols;
772     return desiredSize;
775 - (NSSize)desiredSize
777     // Compute the size the text view should be for the entire text area and
778     // inset area to be visible with the present number of rows and columns.
779     return NSMakeSize(maxColumns * cellSize.width + 2 * insetSize.width,
780                       maxRows * cellSize.height + 2 * insetSize.height);
783 - (NSSize)minSize
785     // Compute the smallest size the text view is allowed to be.
786     return NSMakeSize(MMMinColumns * cellSize.width + 2 * insetSize.width,
787                       MMMinRows * cellSize.height + 2 * insetSize.height);
790 - (void)changeFont:(id)sender
792     [helper changeFont:sender];
797 // NOTE: The menu items cut/copy/paste/undo/redo/select all/... must be bound
798 // to the same actions as in IB otherwise they will not work with dialogs.  All
799 // we do here is forward these actions to the Vim process.
801 - (IBAction)cut:(id)sender
803     [[self windowController] vimMenuItemAction:sender];
806 - (IBAction)copy:(id)sender
808     [[self windowController] vimMenuItemAction:sender];
811 - (IBAction)paste:(id)sender
813     [[self windowController] vimMenuItemAction:sender];
816 - (IBAction)undo:(id)sender
818     [[self windowController] vimMenuItemAction:sender];
821 - (IBAction)redo:(id)sender
823     [[self windowController] vimMenuItemAction:sender];
826 - (IBAction)selectAll:(id)sender
828     [[self windowController] vimMenuItemAction:sender];
831 - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column
833     // View is not flipped, instead the atsui code draws to a flipped image;
834     // thus we need to 'flip' the coordinate here since the column number
835     // increases in an up-to-down order.
836     point.y = [self frame].size.height - point.y;
838     NSPoint origin = { insetSize.width, insetSize.height };
840     if (!(cellSize.width > 0 && cellSize.height > 0))
841         return NO;
843     if (row) *row = floor((point.y-origin.y-1) / cellSize.height);
844     if (column) *column = floor((point.x-origin.x-1) / cellSize.width);
846     return YES;
849 - (NSPoint)pointForRow:(int)row column:(int)col
851     // Return the lower left coordinate of the cell at (row,column).
852     NSPoint pt;
854     pt.x = insetSize.width + col*cellSize.width;
855     pt.y = [self frame].size.height -
856            (insetSize.height + (1+row)*cellSize.height);
858     return pt;
861 - (NSRect)rectForRow:(int)row column:(int)col numRows:(int)nr
862           numColumns:(int)nc
864     // Return the rect for the block which covers the specified rows and
865     // columns.  The lower-left corner is the origin of this rect.
866     NSRect rect;
868     rect.origin.x = insetSize.width + col*cellSize.width;
869     rect.origin.y = [self frame].size.height -
870                     (insetSize.height + (nr+row)*cellSize.height);
871     rect.size.width = nc*cellSize.width;
872     rect.size.height = nr*cellSize.height;
874     return rect;
877 - (NSArray *)validAttributesForMarkedText
879     return nil;
882 - (NSAttributedString *)attributedSubstringFromRange:(NSRange)range
884     return nil;
887 - (NSUInteger)characterIndexForPoint:(NSPoint)point
889     return NSNotFound;
892 // The return type of this message changed with OS X 10.5 so we need this
893 // kludge in order to avoid compiler warnings on OS X 10.4.
894 #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
895 - (NSInteger)conversationIdentifier
897     return (NSInteger)self;
899 #else
900 - (long)conversationIdentifier
902     return (long)self;
904 #endif
906 - (NSRange)selectedRange
908     return [helper imRange];
911 - (NSRect)firstRectForCharacterRange:(NSRange)range
913     return [helper firstRectForCharacterRange:range];
916 @end // MMAtsuiTextView
921 @implementation MMAtsuiTextView (Private)
923 - (void)initAtsuStyles
925     int i;
926     for (i = 0; i < MMMaxCellsPerChar; i++)
927         ATSUCreateStyle(&atsuStyles[i]);
930 - (void)disposeAtsuStyles
932     int i;
934     for (i = 0; i < MMMaxCellsPerChar; i++)
935         if (atsuStyles[i] != NULL)
936         {
937             if (ATSUDisposeStyle(atsuStyles[i]) != noErr)
938                 atsuStyles[i] = NULL;
939         }
942 - (void)updateAtsuStyles
944     ATSUFontID        fontID;
945     Fixed             fontSize;
946     Fixed             fontWidth;
947     int               i;
948     CGAffineTransform transform = CGAffineTransformMakeScale(1, -1);
949     ATSStyleRenderingOptions options;
951     fontID    = [font _atsFontID];
952     fontSize  = Long2Fix([font pointSize]);
953     options   = kATSStyleApplyAntiAliasing;
955     ATSUAttributeTag attribTags[] =
956     {
957         kATSUFontTag, kATSUSizeTag, kATSUImposeWidthTag,
958         kATSUFontMatrixTag, kATSUStyleRenderingOptionsTag,
959         kATSUMaxATSUITagValue + 1
960     };
962     ByteCount attribSizes[] =
963     {
964         sizeof(ATSUFontID), sizeof(Fixed), sizeof(fontWidth),
965         sizeof(CGAffineTransform), sizeof(ATSStyleRenderingOptions),
966         sizeof(font)
967     };
969     ATSUAttributeValuePtr attribValues[] =
970     {
971         &fontID, &fontSize, &fontWidth, &transform, &options, &font
972     };
974     ATSUFontFeatureType featureTypes[] = {
975         kLigaturesType, kLigaturesType
976     };
978     ATSUFontFeatureSelector featureSelectors[] = {
979         kCommonLigaturesOffSelector, kRareLigaturesOffSelector
980     };
982     for (i = 0; i < MMMaxCellsPerChar; i++)
983     {
984         fontWidth = Long2Fix(cellSize.width * (i + 1));
986         if (ATSUSetAttributes(atsuStyles[i],
987                               (sizeof attribTags) / sizeof(ATSUAttributeTag),
988                               attribTags, attribSizes, attribValues) != noErr)
989         {
990             ATSUDisposeStyle(atsuStyles[i]);
991             atsuStyles[i] = NULL;
992         }
994         // Turn off ligatures by default
995         ATSUSetFontFeatures(atsuStyles[i],
996                             sizeof(featureTypes) / sizeof(featureTypes[0]),
997                             featureTypes, featureSelectors);
998     }
1001 - (MMWindowController *)windowController
1003     id windowController = [[self window] windowController];
1004     if ([windowController isKindOfClass:[MMWindowController class]])
1005         return (MMWindowController*)windowController;
1006     return nil;
1009 - (MMVimController *)vimController
1011     return [[self windowController] vimController];
1014 @end // MMAtsuiTextView (Private)
1019 @implementation MMAtsuiTextView (Drawing)
1021 - (NSPoint)originForRow:(int)row column:(int)col
1023     return NSMakePoint(col * cellSize.width, row * cellSize.height);
1026 - (NSRect)rectFromRow:(int)row1 column:(int)col1
1027                 toRow:(int)row2 column:(int)col2
1029     NSPoint origin = [self originForRow:row1 column:col1];
1030     return NSMakeRect(origin.x, origin.y,
1031                       (col2 + 1 - col1) * cellSize.width,
1032                       (row2 + 1 - row1) * cellSize.height);
1035 - (NSSize)textAreaSize
1037     // Calculate the (desired) size of the text area, i.e. the text view area
1038     // minus the inset area.
1039     return NSMakeSize(maxColumns * cellSize.width, maxRows * cellSize.height);
1042 - (void)resizeContentImage
1044     [contentImage release];
1045     contentImage = [[NSImage alloc] initWithSize:[self textAreaSize]];
1046     [contentImage setFlipped:YES];
1047     imageSize = [self textAreaSize];
1050 - (void)beginDrawing
1052     [contentImage lockFocus];
1055 - (void)endDrawing
1057     [contentImage unlockFocus];
1060 #define atsu_style_set_bool(s, t, b) \
1061     ATSUSetAttributes(s, 1, &t, &(sizeof(Boolean)), &&b);
1062 #define FILL_Y(y)    (y * cellSize.height)
1064 - (void)drawString:(UniChar *)string length:(UniCharCount)length
1065              atRow:(int)row column:(int)col cells:(int)cells
1066          withFlags:(int)flags foregroundColor:(NSColor *)fg
1067    backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp
1069     // 'string' consists of 'length' utf-16 code pairs and should cover 'cells'
1070     // display cells (a normal character takes up one display cell, a wide
1071     // character takes up two)
1072     ATSUStyle       style = (flags & DRAW_WIDE) ? atsuStyles[1] : atsuStyles[0];
1073     ATSUTextLayout  layout;
1075     // Font selection and rendering options for ATSUI
1076     ATSUAttributeTag      attribTags[3] = { kATSUQDBoldfaceTag,
1077                                             kATSUFontMatrixTag,
1078                                             kATSUStyleRenderingOptionsTag };
1080     ByteCount             attribSizes[] = { sizeof(Boolean),
1081                                             sizeof(CGAffineTransform),
1082                                             sizeof(UInt32) };
1083     Boolean               useBold;
1084     CGAffineTransform     theTransform = CGAffineTransformMakeScale(1.0, -1.0);
1085     UInt32                useAntialias;
1087     ATSUAttributeValuePtr attribValues[3] = { &useBold, &theTransform,
1088                                               &useAntialias };
1090     useBold      = (flags & DRAW_BOLD) ? true : false;
1092     if (flags & DRAW_ITALIC)
1093         theTransform.c = Fix2X(kATSItalicQDSkew);
1095     useAntialias = antialias ? kATSStyleApplyAntiAliasing
1096                              : kATSStyleNoAntiAliasing;
1098     ATSUSetAttributes(style, sizeof(attribValues) / sizeof(attribValues[0]),
1099                       attribTags, attribSizes, attribValues);
1101     ATSUCreateTextLayout(&layout);
1102     ATSUSetTextPointerLocation(layout, string,
1103                                kATSUFromTextBeginning, kATSUToTextEnd,
1104                                length);
1105     ATSUSetRunStyle(layout, style, kATSUFromTextBeginning, kATSUToTextEnd);
1107     NSRect rect = NSMakeRect(col * cellSize.width, row * cellSize.height,
1108                              length * cellSize.width, cellSize.height);
1109     if (flags & DRAW_WIDE)
1110         rect.size.width = rect.size.width * 2;
1111     CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
1113     // Clip drawing to avoid text bleeding into adjacent display cells when
1114     // antialiasing is enabled.
1115     CGContextSaveGState(context);
1116     CGContextClipToRect(context, *(CGRect*)&rect);
1118     ATSUAttributeTag tags[] = { kATSUCGContextTag };
1119     ByteCount sizes[] = { sizeof(CGContextRef) };
1120     ATSUAttributeValuePtr values[] = { &context };
1121     ATSUSetLayoutControls(layout, 1, tags, sizes, values);
1123     if (! (flags & DRAW_TRANSP))
1124     {
1125         [bg set];
1126         NSRectFill(rect);
1127     }
1129     [fg set];
1131     ATSUSetTransientFontMatching(layout, TRUE);
1132     ATSUDrawText(layout,
1133                  kATSUFromTextBeginning,
1134                  kATSUToTextEnd,
1135                  X2Fix(rect.origin.x),
1136                  X2Fix(rect.origin.y + ascender));
1137     ATSUDisposeTextLayout(layout);
1139     if (flags & DRAW_UNDERL)
1140     {
1141         [sp set];
1142         NSRectFill(NSMakeRect(rect.origin.x,
1143                               (row + 1) * cellSize.height + kUnderlineOffset,
1144                               rect.size.width, kUnderlineHeight));
1145     }
1147     if (flags & DRAW_UNDERC)
1148     {
1149         [sp set];
1151         float line_end_x = rect.origin.x + rect.size.width;
1152         int i = 0;
1153         NSRect line_rect = NSMakeRect(
1154                 rect.origin.x,
1155                 (row + 1) * cellSize.height + kUndercurlOffset,
1156                 kUndercurlDotWidth, kUndercurlHeight);
1158         while (line_rect.origin.x < line_end_x)
1159         {
1160             if (i % 2)
1161                 NSRectFill(line_rect);
1163             line_rect.origin.x += kUndercurlDotDistance;
1164             i++;
1165         }
1166     }
1168     CGContextRestoreGState(context);
1171 - (void)scrollRect:(NSRect)rect lineCount:(int)count
1173     NSPoint destPoint = rect.origin;
1174     destPoint.y += count * cellSize.height;
1176     NSCopyBits(0, rect, destPoint);
1179 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
1180               scrollBottom:(int)bottom left:(int)left right:(int)right
1181                      color:(NSColor *)color
1183     NSRect rect = [self rectFromRow:row + count
1184                              column:left
1185                               toRow:bottom
1186                              column:right];
1187     [color set];
1188     // move rect up for count lines
1189     [self scrollRect:rect lineCount:-count];
1190     [self clearBlockFromRow:bottom - count + 1
1191                      column:left
1192                       toRow:bottom
1193                      column:right
1194                       color:color];
1197 - (void)insertLinesAtRow:(int)row lineCount:(int)count
1198             scrollBottom:(int)bottom left:(int)left right:(int)right
1199                    color:(NSColor *)color
1201     NSRect rect = [self rectFromRow:row
1202                              column:left
1203                               toRow:bottom - count
1204                              column:right];
1205     [color set];
1206     // move rect down for count lines
1207     [self scrollRect:rect lineCount:count];
1208     [self clearBlockFromRow:row
1209                      column:left
1210                       toRow:row + count - 1
1211                      column:right
1212                       color:color];
1215 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
1216                    column:(int)col2 color:(NSColor *)color
1218     [color set];
1219     NSRectFill([self rectFromRow:row1 column:col1 toRow:row2 column:col2]);
1222 - (void)clearAll
1224     [defaultBackgroundColor set];
1225     NSRectFill(NSMakeRect(0, 0, imageSize.width, imageSize.height));
1228 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
1229                        fraction:(int)percent
1231     NSPoint origin = [self originForRow:row column:col];
1232     NSRect rect = NSMakeRect(origin.x, origin.y,
1233                              cellSize.width, cellSize.height);
1235     if (MMInsertionPointHorizontal == shape) {
1236         int frac = (cellSize.height * percent + 99)/100;
1237         rect.origin.y += rect.size.height - frac;
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;
1246     }
1248     [[helper insertionPointColor] set];
1249     if (MMInsertionPointHollow == shape) {
1250         NSFrameRect(rect);
1251     } else {
1252         NSRectFill(rect);
1253     }
1256 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nrows
1257                    numColumns:(int)ncols
1259     // TODO: THIS CODE HAS NOT BEEN TESTED!
1260     CGContextRef cgctx = [[NSGraphicsContext currentContext] graphicsPort];
1261     CGContextSaveGState(cgctx);
1262     CGContextSetBlendMode(cgctx, kCGBlendModeDifference);
1263     CGContextSetRGBFillColor(cgctx, 1.0, 1.0, 1.0, 1.0);
1265     CGRect rect = { col * cellSize.width, row * cellSize.height,
1266                     ncols * cellSize.width, nrows * cellSize.height };
1267     CGContextFillRect(cgctx, rect);
1269     CGContextRestoreGState(cgctx);
1272 @end // MMAtsuiTextView (Drawing)
1274 #endif // ENABLE_ATSUI