Add handling IM state
[MacVim/KaoriYa.git] / src / MacVim / MMAtsuiTextView.m
blob643badb0bd9d12b5988d298f30bc5fc46c4ebf83
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 #import "MMAppController.h"
29 #import "MMAtsuiTextView.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 transparant bg */
40 #define DRAW_BOLD                 0x02    /* draw bold text */
41 #define DRAW_UNDERL               0x04    /* draw underline text */
42 #define DRAW_UNDERC               0x08    /* draw undercurl text */
43 #define DRAW_ITALIC               0x10    /* draw italic text */
44 #define DRAW_CURSOR               0x20
46 #define kUnderlineOffset            (-2)
47 #define kUnderlineHeight            1
48 #define kUndercurlHeight            2
49 #define kUndercurlOffset            (-2)
50 #define kUndercurlDotWidth          2
51 #define kUndercurlDotDistance       2
54 @interface NSFont (AppKitPrivate)
55 - (ATSUFontID) _atsFontID;
56 @end
59 @interface MMAtsuiTextView (Private)
60 - (void)initAtsuStyles;
61 - (void)disposeAtsuStyles;
62 - (void)updateAtsuStyles;
63 - (MMWindowController *)windowController;
64 - (MMVimController *)vimController;
65 @end
68 @interface MMAtsuiTextView (Drawing)
69 - (NSPoint)originForRow:(int)row column:(int)column;
70 - (NSRect)rectFromRow:(int)row1 column:(int)col1
71                 toRow:(int)row2 column:(int)col2;
72 - (NSSize)textAreaSize;
73 - (void)resizeContentImage;
74 - (void)beginDrawing;
75 - (void)endDrawing;
76 - (void)drawString:(UniChar *)string length:(UniCharCount)length
77              atRow:(int)row column:(int)col cells:(int)cells
78          withFlags:(int)flags foregroundColor:(NSColor *)fg
79    backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp;
80 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
81               scrollBottom:(int)bottom left:(int)left right:(int)right
82                      color:(NSColor *)color;
83 - (void)insertLinesAtRow:(int)row lineCount:(int)count
84             scrollBottom:(int)bottom left:(int)left right:(int)right
85                    color:(NSColor *)color;
86 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
87                    column:(int)col2 color:(NSColor *)color;
88 - (void)clearAll;
89 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
90                        fraction:(int)percent;
91 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nrows
92                    numColumns:(int)ncols;
93 @end
97     static float
98 defaultLineHeightForFont(NSFont *font)
100     // HACK: -[NSFont defaultLineHeightForFont] is deprecated but since the
101     // ATSUI renderer does not use NSLayoutManager we create one temporarily.
102     NSLayoutManager *lm = [[NSLayoutManager alloc] init];
103     float height = [lm defaultLineHeightForFont:font];
104     [lm release];
106     return height;
109 @implementation MMAtsuiTextView
111 - (id)initWithFrame:(NSRect)frame
113     if (!(self = [super initWithFrame:frame]))
114         return nil;
116     // NOTE!  It does not matter which font is set here, Vim will set its
117     // own font on startup anyway.  Just set some bogus values.
118     font = [[NSFont userFixedPitchFontOfSize:0] retain];
119     ascender = 0;
120     cellSize.width = cellSize.height = 1;
121     contentImage = nil;
122     imageSize = NSZeroSize;
123     insetSize = NSZeroSize;
125     // NOTE: If the default changes to 'NO' then the intialization of
126     // p_antialias in option.c must change as well.
127     antialias = YES;
129     helper = [[MMTextViewHelper alloc] init];
130     [helper setTextView:self];
132     [self initAtsuStyles];
134     [self registerForDraggedTypes:[NSArray arrayWithObjects:
135             NSFilenamesPboardType, NSStringPboardType, nil]];
137     return self;
140 - (void)dealloc
142     LOG_DEALLOC
144     [self disposeAtsuStyles];
145     [font release];  font = nil;
146     [defaultBackgroundColor release];  defaultBackgroundColor = nil;
147     [defaultForegroundColor release];  defaultForegroundColor = nil;
149     [helper setTextView:nil];
150     [helper dealloc];  helper = nil;
152     [super dealloc];
155 - (int)maxRows
157     return maxRows;
160 - (int)maxColumns
162     return maxColumns;
165 - (void)getMaxRows:(int*)rows columns:(int*)cols
167     if (rows) *rows = maxRows;
168     if (cols) *cols = maxColumns;
171 - (void)setMaxRows:(int)rows columns:(int)cols
173     // NOTE: Just remember the new values, the actual resizing is done lazily.
174     maxRows = rows;
175     maxColumns = cols;
178 - (void)setDefaultColorsBackground:(NSColor *)bgColor
179                         foreground:(NSColor *)fgColor
181     if (defaultBackgroundColor != bgColor) {
182         [defaultBackgroundColor release];
183         defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
184     }
186     if (defaultForegroundColor != fgColor) {
187         [defaultForegroundColor release];
188         defaultForegroundColor = fgColor ? [fgColor retain] : nil;
189     }
192 - (NSColor *)defaultBackgroundColor
194     return defaultBackgroundColor;
197 - (NSColor *)defaultForegroundColor
199     return defaultForegroundColor;
202 - (void)setTextContainerInset:(NSSize)size
204     insetSize = size;
207 - (NSRect)rectForRowsInRange:(NSRange)range
209     NSRect rect = { 0, 0, 0, 0 };
210     unsigned start = range.location > maxRows ? maxRows : range.location;
211     unsigned length = range.length;
213     if (start + length > maxRows)
214         length = maxRows - start;
216     rect.origin.y = cellSize.height * start + insetSize.height;
217     rect.size.height = cellSize.height * length;
219     return rect;
222 - (NSRect)rectForColumnsInRange:(NSRange)range
224     NSRect rect = { 0, 0, 0, 0 };
225     unsigned start = range.location > maxColumns ? maxColumns : range.location;
226     unsigned length = range.length;
228     if (start+length > maxColumns)
229         length = maxColumns - start;
231     rect.origin.x = cellSize.width * start + insetSize.width;
232     rect.size.width = cellSize.width * length;
234     return rect;
238 - (void)setFont:(NSFont *)newFont
240     if (newFont && font != newFont) {
241         [font release];
242         font = [newFont retain];
243         ascender = roundf([font ascender]);
245         float em = [@"m" sizeWithAttributes:
246                 [NSDictionary dictionaryWithObject:newFont
247                                             forKey:NSFontAttributeName]].width;
248         float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
249                 floatForKey:MMCellWidthMultiplierKey];
251         // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
252         // only render at integer sizes.  Hence, we restrict the cell width to
253         // an integer here, otherwise the window width and the actual text
254         // width will not match.
255         cellSize.width = ceilf(em * cellWidthMultiplier);
256         cellSize.height = linespace + defaultLineHeightForFont(newFont);
258         [self updateAtsuStyles];
259     }
262 - (void)setWideFont:(NSFont *)newFont
264     if (!newFont) {
265         if (font) [self setWideFont:font];
266     } else if (newFont != fontWide) {
267         float pointSize = [newFont pointSize];
268         NSFontDescriptor *desc = [newFont fontDescriptor];
269         NSDictionary *dictWide = [NSDictionary
270             dictionaryWithObject:[NSNumber numberWithFloat:2*cellSize.width]
271                           forKey:NSFontFixedAdvanceAttribute];
272         desc = [desc fontDescriptorByAddingAttributes:dictWide];
273         fontWide = [NSFont fontWithDescriptor:desc size:pointSize];
274         [fontWide retain];
275     }
278 - (NSFont *)font
280     return font;
283 - (NSFont *)fontWide
285     return fontWide;
288 - (NSSize)cellSize
290     return cellSize;
293 - (void)setLinespace:(float)newLinespace
295     linespace = newLinespace;
297     // NOTE: The linespace is added to the cell height in order for a multiline
298     // selection not to have white (background color) gaps between lines.  Also
299     // this simplifies the code a lot because there is no need to check the
300     // linespace when calculating the size of the text view etc.  When the
301     // linespace is non-zero the baseline will be adjusted as well; check
302     // MMTypesetter.
303     cellSize.height = linespace + defaultLineHeightForFont(font);
308 - (void)setShouldDrawInsertionPoint:(BOOL)on
312 - (void)setPreEditRow:(int)row column:(int)col
314     [helper setPreEditRow:row column:col];
317 - (void)setMouseShape:(int)shape
319     [helper setMouseShape:shape];
322 - (void)setAntialias:(BOOL)state
324     antialias = state;
327 - (void)setImControl:(BOOL)enable
329     [helper setImControl:enable];
332 - (void)keyDown:(NSEvent *)event
334     [helper keyDown:event];
337 - (void)insertText:(id)string
339     [helper insertText:string];
342 - (void)doCommandBySelector:(SEL)selector
344     [helper doCommandBySelector:selector];
347 - (BOOL)performKeyEquivalent:(NSEvent *)event
349     return [helper performKeyEquivalent:event];
352 - (BOOL)hasMarkedText
354     return [helper hasMarkedText];
357 - (NSRange)markedRange
359     return [helper markedRange];
362 - (NSDictionary *)markedTextAttributes
364     return [helper markedTextAttributes];
367 - (void)setMarkedTextAttributes:(NSDictionary *)attr
369     [helper setMarkedTextAttributes:attr];
372 - (void)setMarkedText:(id)text selectedRange:(NSRange)range
374     [helper setMarkedText:text selectedRange:range];
377 - (void)unmarkText
379     [helper unmarkText];
382 - (void)scrollWheel:(NSEvent *)event
384     [helper scrollWheel:event];
387 - (void)mouseDown:(NSEvent *)event
389     [helper mouseDown:event];
392 - (void)rightMouseDown:(NSEvent *)event
394     [helper mouseDown:event];
397 - (void)otherMouseDown:(NSEvent *)event
399     [helper mouseDown:event];
402 - (void)mouseUp:(NSEvent *)event
404     [helper mouseUp:event];
407 - (void)rightMouseUp:(NSEvent *)event
409     [helper mouseUp:event];
412 - (void)otherMouseUp:(NSEvent *)event
414     [helper mouseUp:event];
417 - (void)mouseDragged:(NSEvent *)event
419     [helper mouseDragged:event];
422 - (void)rightMouseDragged:(NSEvent *)event
424     [helper mouseDragged:event];
427 - (void)otherMouseDragged:(NSEvent *)event
429     [helper mouseDragged:event];
432 - (void)mouseMoved:(NSEvent *)event
434     [helper mouseMoved:event];
437 - (void)mouseEntered:(NSEvent *)event
439     [helper mouseEntered:event];
442 - (void)mouseExited:(NSEvent *)event
444     [helper mouseExited:event];
447 - (void)setFrame:(NSRect)frame
449     [super setFrame:frame];
450     [helper setFrame:frame];
453 - (void)viewDidMoveToWindow
455     [helper viewDidMoveToWindow];
458 - (void)viewWillMoveToWindow:(NSWindow *)newWindow
460     [helper viewWillMoveToWindow:newWindow];
463 - (NSMenu*)menuForEvent:(NSEvent *)event
465     // HACK! Return nil to disable default popup menus (Vim provides its own).
466     // Called when user Ctrl-clicks in the view (this is already handled in
467     // rightMouseDown:).
468     return nil;
471 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
473     return [helper performDragOperation:sender];
476 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
478     return [helper draggingEntered:sender];
481 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
483     return [helper draggingUpdated:sender];
488 - (BOOL)mouseDownCanMoveWindow
490     return NO;
493 - (BOOL)isOpaque
495     return YES;
498 - (BOOL)acceptsFirstResponder
500     return YES;
503 - (BOOL)isFlipped
505     return NO;
508 - (void)drawRect:(NSRect)rect
510     [defaultBackgroundColor set];
511     NSRectFill(rect);
513     NSPoint pt = { insetSize.width, insetSize.height };
514     [contentImage compositeToPoint:pt operation:NSCompositeCopy];
516     if ([self hasMarkedText]) {
517         int len = [[helper markedText] length];
518         int rows = 0;
519         int cols = maxColumns - [helper preEditColumn];
520         NSFont *theFont = [[self markedTextAttributes]
521             valueForKey:NSFontAttributeName];
522         if (theFont == [self fontWide])
523             cols = cols / 2;
524         int done = 0;
525         int lend = cols > len ? len : cols;
526         NSAttributedString *aString = [[helper markedText]
527                 attributedSubstringFromRange:NSMakeRange(done, lend)];
528         NSPoint pt = [self pointForRow:[helper preEditRow]
529                                 column:[helper preEditColumn]];
530         [aString drawAtPoint:pt];
531         done = lend;
532         if (done != len) {
533             int r;
534             rows = (len - done) / (maxColumns / 2) + 1;
535             for (r = 1; r <= rows; r++) {
536             lend = len - done > maxColumns / 2
537                 ? maxColumns / 2 : len - done;
538                 aString = [[helper markedText] attributedSubstringFromRange:
539                         NSMakeRange(done, lend)];
540                 NSPoint pt = [self pointForRow:[helper preEditRow]+r
541                                         column:0];
542                 [aString drawAtPoint:pt];
543                 done += lend;
544             }
545         }
547         rows = maxRows - 1 - [helper preEditRow];
548         cols = [helper preEditColumn];
549         if (theFont == fontWide) {
550             cols += ([helper imRange].location+[helper imRange].length) * 2;
551             if (cols >= maxColumns - 1) {
552                 rows -= cols / maxColumns;
553                 cols = cols % 2 ? cols % maxColumns + 1 :
554                                   cols % maxColumns;
555             }
556         } else {
557             cols += ([helper imRange].location+[helper imRange].length);
558             if (cols >= maxColumns) {
559                 rows -= cols / maxColumns;
560                 cols = cols % 2 ? cols % maxColumns + 1 :
561                                   cols % maxColumns;
562             }
563         }
565         // TODO: Could IM be in "right-left" mode?  If so the insertion point
566         // will be on the wrong side.
567         [self drawInsertionPointAtRow:rows
568                                column:cols
569                                 shape:MMInsertionPointVertical
570                              fraction:25];
571     }
574 - (BOOL) wantsDefaultClipping
576     return NO;
580 #define MM_DEBUG_DRAWING 0
582 - (void)performBatchDrawWithData:(NSData *)data
584     const void *bytes = [data bytes];
585     const void *end = bytes + [data length];
587     if (! NSEqualSizes(imageSize, [self textAreaSize]))
588         [self resizeContentImage];
590 #if MM_DEBUG_DRAWING
591     NSLog(@"====> BEGIN %s", _cmd);
592 #endif
593     [self beginDrawing];
595     // TODO: Sanity check input
597     while (bytes < end) {
598         int type = *((int*)bytes);  bytes += sizeof(int);
600         if (ClearAllDrawType == type) {
601 #if MM_DEBUG_DRAWING
602             NSLog(@"   Clear all");
603 #endif
604             [self clearAll];
605         } else if (ClearBlockDrawType == type) {
606             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
607             int row1 = *((int*)bytes);  bytes += sizeof(int);
608             int col1 = *((int*)bytes);  bytes += sizeof(int);
609             int row2 = *((int*)bytes);  bytes += sizeof(int);
610             int col2 = *((int*)bytes);  bytes += sizeof(int);
612 #if MM_DEBUG_DRAWING
613             NSLog(@"   Clear block (%d,%d) -> (%d,%d)", row1, col1,
614                     row2,col2);
615 #endif
616             [self clearBlockFromRow:row1 column:col1
617                     toRow:row2 column:col2
618                     color:[NSColor colorWithArgbInt:color]];
619         } else if (DeleteLinesDrawType == type) {
620             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
621             int row = *((int*)bytes);  bytes += sizeof(int);
622             int count = *((int*)bytes);  bytes += sizeof(int);
623             int bot = *((int*)bytes);  bytes += sizeof(int);
624             int left = *((int*)bytes);  bytes += sizeof(int);
625             int right = *((int*)bytes);  bytes += sizeof(int);
627 #if MM_DEBUG_DRAWING
628             NSLog(@"   Delete %d line(s) from %d", count, row);
629 #endif
630             [self deleteLinesFromRow:row lineCount:count
631                     scrollBottom:bot left:left right:right
632                            color:[NSColor colorWithArgbInt:color]];
633         } else if (DrawStringDrawType == type) {
634             int bg = *((int*)bytes);  bytes += sizeof(int);
635             int fg = *((int*)bytes);  bytes += sizeof(int);
636             int sp = *((int*)bytes);  bytes += sizeof(int);
637             int row = *((int*)bytes);  bytes += sizeof(int);
638             int col = *((int*)bytes);  bytes += sizeof(int);
639             int cells = *((int*)bytes);  bytes += sizeof(int);
640             int flags = *((int*)bytes);  bytes += sizeof(int);
641             int len = *((int*)bytes);  bytes += sizeof(int);
642             // UniChar *string = (UniChar*)bytes;  bytes += len;
643             NSString *string = [[NSString alloc]
644                     initWithBytesNoCopy:(void*)bytes
645                                  length:len
646                                encoding:NSUTF8StringEncoding
647                            freeWhenDone:NO];
648             bytes += len;
649 #if MM_DEBUG_DRAWING
650             NSLog(@"   Draw string at (%d,%d) length=%d flags=%d fg=0x%x "
651                     "bg=0x%x sp=0x%x", row, col, len, flags, fg, bg, sp);
652 #endif
653             unichar *characters = malloc(sizeof(unichar) * [string length]);
654             [string getCharacters:characters];
656             [self drawString:characters
657                              length:[string length]
658                               atRow:row
659                              column:col
660                               cells:cells withFlags:flags
661                     foregroundColor:[NSColor colorWithRgbInt:fg]
662                     backgroundColor:[NSColor colorWithArgbInt:bg]
663                        specialColor:[NSColor colorWithRgbInt:sp]];
664             free(characters);
665             [string release];
666         } else if (InsertLinesDrawType == type) {
667             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
668             int row = *((int*)bytes);  bytes += sizeof(int);
669             int count = *((int*)bytes);  bytes += sizeof(int);
670             int bot = *((int*)bytes);  bytes += sizeof(int);
671             int left = *((int*)bytes);  bytes += sizeof(int);
672             int right = *((int*)bytes);  bytes += sizeof(int);
674 #if MM_DEBUG_DRAWING
675             NSLog(@"   Insert %d line(s) at row %d", count, row);
676 #endif
677             [self insertLinesAtRow:row lineCount:count
678                              scrollBottom:bot left:left right:right
679                                     color:[NSColor colorWithArgbInt:color]];
680         } else if (DrawCursorDrawType == type) {
681             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
682             int row = *((int*)bytes);  bytes += sizeof(int);
683             int col = *((int*)bytes);  bytes += sizeof(int);
684             int shape = *((int*)bytes);  bytes += sizeof(int);
685             int percent = *((int*)bytes);  bytes += sizeof(int);
687 #if MM_DEBUG_DRAWING
688             NSLog(@"   Draw cursor at (%d,%d)", row, col);
689 #endif
690             [helper setInsertionPointColor:[NSColor colorWithRgbInt:color]];
691             [self drawInsertionPointAtRow:row column:col shape:shape
692                                  fraction:percent];
693         } else if (DrawInvertedRectDrawType == type) {
694             int row = *((int*)bytes);  bytes += sizeof(int);
695             int col = *((int*)bytes);  bytes += sizeof(int);
696             int nr = *((int*)bytes);  bytes += sizeof(int);
697             int nc = *((int*)bytes);  bytes += sizeof(int);
698             /*int invert = *((int*)bytes);*/  bytes += sizeof(int);
700 #if MM_DEBUG_DRAWING
701             NSLog(@"   Draw inverted rect: row=%d col=%d nrows=%d ncols=%d",
702                     row, col, nr, nc);
703 #endif
704             [self drawInvertedRectAtRow:row column:col numRows:nr
705                              numColumns:nc];
706         } else if (SetCursorPosDrawType == type) {
707             // TODO: This is used for Voice Over support in MMTextView,
708             // MMAtsuiTextView currently does not support Voice Over.
709             /*cursorRow = *((int*)bytes);*/  bytes += sizeof(int);
710             /*cursorCol = *((int*)bytes);*/  bytes += sizeof(int);
711         } else {
712             NSLog(@"WARNING: Unknown draw type (type=%d)", type);
713         }
714     }
716     [self endDrawing];
718     [self setNeedsDisplay:YES];
720     // NOTE: During resizing, Cocoa only sends draw messages before Vim's rows
721     // and columns are changed (due to ipc delays). Force a redraw here.
722     if ([self inLiveResize])
723         [self display];
725 #if MM_DEBUG_DRAWING
726     NSLog(@"<==== END   %s", _cmd);
727 #endif
730 - (NSSize)constrainRows:(int *)rows columns:(int *)cols toSize:(NSSize)size
732     // TODO:
733     // - Rounding errors may cause size change when there should be none
734     // - Desired rows/columns shold not be 'too small'
736     // Constrain the desired size to the given size.  Values for the minimum
737     // rows and columns is taken from Vim.
738     NSSize desiredSize = [self desiredSize];
739     int desiredRows = maxRows;
740     int desiredCols = maxColumns;
742     if (size.height != desiredSize.height) {
743         float fh = cellSize.height;
744         float ih = 2 * insetSize.height;
745         if (fh < 1.0f) fh = 1.0f;
747         desiredRows = floor((size.height - ih)/fh);
748         desiredSize.height = fh*desiredRows + ih;
749     }
751     if (size.width != desiredSize.width) {
752         float fw = cellSize.width;
753         float iw = 2 * insetSize.width;
754         if (fw < 1.0f) fw = 1.0f;
756         desiredCols = floor((size.width - iw)/fw);
757         desiredSize.width = fw*desiredCols + iw;
758     }
760     if (rows) *rows = desiredRows;
761     if (cols) *cols = desiredCols;
763     return desiredSize;
766 - (NSSize)desiredSize
768     // Compute the size the text view should be for the entire text area and
769     // inset area to be visible with the present number of rows and columns.
770     return NSMakeSize(maxColumns * cellSize.width + 2 * insetSize.width,
771                       maxRows * cellSize.height + 2 * insetSize.height);
774 - (NSSize)minSize
776     // Compute the smallest size the text view is allowed to be.
777     return NSMakeSize(MMMinColumns * cellSize.width + 2 * insetSize.width,
778                       MMMinRows * cellSize.height + 2 * insetSize.height);
781 - (void)changeFont:(id)sender
783     NSFont *newFont = [sender convertFont:font];
785     if (newFont) {
786         NSString *name = [newFont displayName];
787         unsigned len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
788         if (len > 0) {
789             NSMutableData *data = [NSMutableData data];
790             float pointSize = [newFont pointSize];
792             [data appendBytes:&pointSize length:sizeof(float)];
794             ++len;  // include NUL byte
795             [data appendBytes:&len length:sizeof(unsigned)];
796             [data appendBytes:[name UTF8String] length:len];
798             [[self vimController] sendMessage:SetFontMsgID data:data];
799         }
800     }
805 // NOTE: The menu items cut/copy/paste/undo/redo/select all/... must be bound
806 // to the same actions as in IB otherwise they will not work with dialogs.  All
807 // we do here is forward these actions to the Vim process.
809 - (IBAction)cut:(id)sender
811     [[self windowController] vimMenuItemAction:sender];
814 - (IBAction)copy:(id)sender
816     [[self windowController] vimMenuItemAction:sender];
819 - (IBAction)paste:(id)sender
821     [[self windowController] vimMenuItemAction:sender];
824 - (IBAction)undo:(id)sender
826     [[self windowController] vimMenuItemAction:sender];
829 - (IBAction)redo:(id)sender
831     [[self windowController] vimMenuItemAction:sender];
834 - (IBAction)selectAll:(id)sender
836     [[self windowController] vimMenuItemAction:sender];
839 - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column
841     // View is not flipped, instead the atsui code draws to a flipped image;
842     // thus we need to 'flip' the coordinate here since the column number
843     // increases in an up-to-down order.
844     point.y = [self frame].size.height - point.y;
846     NSPoint origin = { insetSize.width, insetSize.height };
848     if (!(cellSize.width > 0 && cellSize.height > 0))
849         return NO;
851     if (row) *row = floor((point.y-origin.y-1) / cellSize.height);
852     if (column) *column = floor((point.x-origin.x-1) / cellSize.width);
854     return YES;
857 - (NSPoint)pointForRow:(int)row column:(int)col
859     // Return the lower left coordinate of the cell at (row,column).
860     NSPoint pt;
862     pt.x = insetSize.width + col*cellSize.width;
863     pt.y = [self frame].size.height -
864            (insetSize.height + (1+row)*cellSize.height);
866     return pt;
869 - (NSRect)rectForRow:(int)row column:(int)col numRows:(int)nr
870           numColumns:(int)nc
872     // Return the rect for the block which covers the specified rows and
873     // columns.  The lower-left corner is the origin of this rect.
874     NSRect rect;
876     rect.origin.x = insetSize.width + col*cellSize.width;
877     rect.origin.y = [self frame].size.height -
878                     (insetSize.height + (nr+row)*cellSize.height);
879     rect.size.width = nc*cellSize.width;
880     rect.size.height = nr*cellSize.height;
882     return rect;
885 - (NSArray *)validAttributesForMarkedText
887     return nil;
890 - (NSAttributedString *)attributedSubstringFromRange:(NSRange)range
892     return nil;
895 - (NSUInteger)characterIndexForPoint:(NSPoint)point
897     return NSNotFound;
900 // The return type of this message changed with OS X 10.5 so we need this
901 // kludge in order to avoid compiler warnings on OS X 10.4.
902 #if (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4)
903 - (NSInteger)conversationIdentifier
905     return (NSInteger)self;
907 #else
908 - (long)conversationIdentifier
910     return (long)self;
912 #endif
914 - (NSRange)selectedRange
916     return [helper imRange];
919 - (NSRect)firstRectForCharacterRange:(NSRange)range
921     return [helper firstRectForCharacterRange:range];
924 @end // MMAtsuiTextView
929 @implementation MMAtsuiTextView (Private)
931 - (void)initAtsuStyles
933     int i;
934     for (i = 0; i < MMMaxCellsPerChar; i++)
935         ATSUCreateStyle(&atsuStyles[i]);
938 - (void)disposeAtsuStyles
940     int i;
942     for (i = 0; i < MMMaxCellsPerChar; i++)
943         if (atsuStyles[i] != NULL)
944         {
945             if (ATSUDisposeStyle(atsuStyles[i]) != noErr)
946                 atsuStyles[i] = NULL;
947         }
950 - (void)updateAtsuStyles
952     ATSUFontID        fontID;
953     Fixed             fontSize;
954     Fixed             fontWidth;
955     int               i;
956     CGAffineTransform transform = CGAffineTransformMakeScale(1, -1);
957     ATSStyleRenderingOptions options;
959     fontID    = [font _atsFontID];
960     fontSize  = Long2Fix([font pointSize]);
961     options   = kATSStyleApplyAntiAliasing;
963     ATSUAttributeTag attribTags[] =
964     {
965         kATSUFontTag, kATSUSizeTag, kATSUImposeWidthTag,
966         kATSUFontMatrixTag, kATSUStyleRenderingOptionsTag,
967         kATSUMaxATSUITagValue + 1
968     };
970     ByteCount attribSizes[] =
971     {
972         sizeof(ATSUFontID), sizeof(Fixed), sizeof(fontWidth),
973         sizeof(CGAffineTransform), sizeof(ATSStyleRenderingOptions),
974         sizeof(font)
975     };
977     ATSUAttributeValuePtr attribValues[] =
978     {
979         &fontID, &fontSize, &fontWidth, &transform, &options, &font
980     };
982     ATSUFontFeatureType featureTypes[] = {
983         kLigaturesType, kLigaturesType
984     };
986     ATSUFontFeatureSelector featureSelectors[] = {
987         kCommonLigaturesOffSelector, kRareLigaturesOffSelector
988     };
990     for (i = 0; i < MMMaxCellsPerChar; i++)
991     {
992         fontWidth = Long2Fix(cellSize.width * (i + 1));
994         if (ATSUSetAttributes(atsuStyles[i],
995                               (sizeof attribTags) / sizeof(ATSUAttributeTag),
996                               attribTags, attribSizes, attribValues) != noErr)
997         {
998             ATSUDisposeStyle(atsuStyles[i]);
999             atsuStyles[i] = NULL;
1000         }
1002         // Turn off ligatures by default
1003         ATSUSetFontFeatures(atsuStyles[i],
1004                             sizeof(featureTypes) / sizeof(featureTypes[0]),
1005                             featureTypes, featureSelectors);
1006     }
1009 - (MMWindowController *)windowController
1011     id windowController = [[self window] windowController];
1012     if ([windowController isKindOfClass:[MMWindowController class]])
1013         return (MMWindowController*)windowController;
1014     return nil;
1017 - (MMVimController *)vimController
1019     return [[self windowController] vimController];
1022 @end // MMAtsuiTextView (Private)
1027 @implementation MMAtsuiTextView (Drawing)
1029 - (NSPoint)originForRow:(int)row column:(int)col
1031     return NSMakePoint(col * cellSize.width, row * cellSize.height);
1034 - (NSRect)rectFromRow:(int)row1 column:(int)col1
1035                 toRow:(int)row2 column:(int)col2
1037     NSPoint origin = [self originForRow:row1 column:col1];
1038     return NSMakeRect(origin.x, origin.y,
1039                       (col2 + 1 - col1) * cellSize.width,
1040                       (row2 + 1 - row1) * cellSize.height);
1043 - (NSSize)textAreaSize
1045     // Calculate the (desired) size of the text area, i.e. the text view area
1046     // minus the inset area.
1047     return NSMakeSize(maxColumns * cellSize.width, maxRows * cellSize.height);
1050 - (void)resizeContentImage
1052     //NSLog(@"resizeContentImage");
1053     [contentImage release];
1054     contentImage = [[NSImage alloc] initWithSize:[self textAreaSize]];
1055     [contentImage setFlipped:YES];
1056     imageSize = [self textAreaSize];
1059 - (void)beginDrawing
1061     [contentImage lockFocus];
1064 - (void)endDrawing
1066     [contentImage unlockFocus];
1069 #define atsu_style_set_bool(s, t, b) \
1070     ATSUSetAttributes(s, 1, &t, &(sizeof(Boolean)), &&b);
1071 #define FILL_Y(y)    (y * cellSize.height)
1073 - (void)drawString:(UniChar *)string length:(UniCharCount)length
1074              atRow:(int)row column:(int)col cells:(int)cells
1075          withFlags:(int)flags foregroundColor:(NSColor *)fg
1076    backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp
1078     // 'string' consists of 'length' utf-16 code pairs and should cover 'cells'
1079     // display cells (a normal character takes up one display cell, a wide
1080     // character takes up two)
1081     ATSUStyle       style = (flags & DRAW_WIDE) ? atsuStyles[1] : atsuStyles[0];
1082     ATSUTextLayout  layout;
1084     // Font selection and rendering options for ATSUI
1085     ATSUAttributeTag      attribTags[3] = { kATSUQDBoldfaceTag,
1086                                             kATSUFontMatrixTag,
1087                                             kATSUStyleRenderingOptionsTag };
1089     ByteCount             attribSizes[] = { sizeof(Boolean),
1090                                             sizeof(CGAffineTransform),
1091                                             sizeof(UInt32) };
1092     Boolean               useBold;
1093     CGAffineTransform     theTransform = CGAffineTransformMakeScale(1.0, -1.0);
1094     UInt32                useAntialias;
1096     ATSUAttributeValuePtr attribValues[3] = { &useBold, &theTransform,
1097                                               &useAntialias };
1099     useBold      = (flags & DRAW_BOLD) ? true : false;
1101     if (flags & DRAW_ITALIC)
1102         theTransform.c = Fix2X(kATSItalicQDSkew);
1104     useAntialias = antialias ? kATSStyleApplyAntiAliasing
1105                              : kATSStyleNoAntiAliasing;
1107     ATSUSetAttributes(style, sizeof(attribValues) / sizeof(attribValues[0]),
1108                       attribTags, attribSizes, attribValues);
1110     // NSLog(@"drawString: %d", length);
1112     ATSUCreateTextLayout(&layout);
1113     ATSUSetTextPointerLocation(layout, string,
1114                                kATSUFromTextBeginning, kATSUToTextEnd,
1115                                length);
1116     ATSUSetRunStyle(layout, style, kATSUFromTextBeginning, kATSUToTextEnd);
1118     NSRect rect = NSMakeRect(col * cellSize.width, row * cellSize.height,
1119                              length * cellSize.width, cellSize.height);
1120     if (flags & DRAW_WIDE)
1121         rect.size.width = rect.size.width * 2;
1122     CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
1124     ATSUAttributeTag tags[] = { kATSUCGContextTag };
1125     ByteCount sizes[] = { sizeof(CGContextRef) };
1126     ATSUAttributeValuePtr values[] = { &context };
1127     ATSUSetLayoutControls(layout, 1, tags, sizes, values);
1129     if (! (flags & DRAW_TRANSP))
1130     {
1131         [bg set];
1132         NSRectFill(rect);
1133     }
1135     [fg set];
1137     ATSUSetTransientFontMatching(layout, TRUE);
1138     ATSUDrawText(layout,
1139                  kATSUFromTextBeginning,
1140                  kATSUToTextEnd,
1141                  X2Fix(rect.origin.x),
1142                  X2Fix(rect.origin.y + ascender));
1143     ATSUDisposeTextLayout(layout);
1145     if (flags & DRAW_UNDERL)
1146     {
1147         [sp set];
1148         NSRectFill(NSMakeRect(rect.origin.x,
1149                               (row + 1) * cellSize.height + kUnderlineOffset,
1150                               rect.size.width, kUnderlineHeight));
1151     }
1153     if (flags & DRAW_UNDERC)
1154     {
1155         [sp set];
1157         float line_end_x = rect.origin.x + rect.size.width;
1158         int i = 0;
1159         NSRect line_rect = NSMakeRect(
1160                 rect.origin.x,
1161                 (row + 1) * cellSize.height + kUndercurlOffset,
1162                 kUndercurlDotWidth, kUndercurlHeight);
1164         while (line_rect.origin.x < line_end_x)
1165         {
1166             if (i % 2)
1167                 NSRectFill(line_rect);
1169             line_rect.origin.x += kUndercurlDotDistance;
1170             i++;
1171         }
1172     }
1175 - (void)scrollRect:(NSRect)rect lineCount:(int)count
1177     NSPoint destPoint = rect.origin;
1178     destPoint.y += count * cellSize.height;
1180     NSCopyBits(0, rect, destPoint);
1183 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
1184               scrollBottom:(int)bottom left:(int)left right:(int)right
1185                      color:(NSColor *)color
1187     NSRect rect = [self rectFromRow:row + count
1188                              column:left
1189                               toRow:bottom
1190                              column:right];
1191     [color set];
1192     // move rect up for count lines
1193     [self scrollRect:rect lineCount:-count];
1194     [self clearBlockFromRow:bottom - count + 1
1195                      column:left
1196                       toRow:bottom
1197                      column:right
1198                       color:color];
1201 - (void)insertLinesAtRow:(int)row lineCount:(int)count
1202             scrollBottom:(int)bottom left:(int)left right:(int)right
1203                    color:(NSColor *)color
1205     NSRect rect = [self rectFromRow:row
1206                              column:left
1207                               toRow:bottom - count
1208                              column:right];
1209     [color set];
1210     // move rect down for count lines
1211     [self scrollRect:rect lineCount:count];
1212     [self clearBlockFromRow:row
1213                      column:left
1214                       toRow:row + count - 1
1215                      column:right
1216                       color:color];
1219 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
1220                    column:(int)col2 color:(NSColor *)color
1222     [color set];
1223     NSRectFill([self rectFromRow:row1 column:col1 toRow:row2 column:col2]);
1226 - (void)clearAll
1228     [defaultBackgroundColor set];
1229     NSRectFill(NSMakeRect(0, 0, imageSize.width, imageSize.height));
1232 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
1233                        fraction:(int)percent
1235     NSPoint origin = [self originForRow:row column:col];
1236     NSRect rect = NSMakeRect(origin.x, origin.y,
1237                              cellSize.width, cellSize.height);
1239     // NSLog(@"shape = %d, fraction: %d", shape, percent);
1241     if (MMInsertionPointHorizontal == shape) {
1242         int frac = (cellSize.height * percent + 99)/100;
1243         rect.origin.y += rect.size.height - frac;
1244         rect.size.height = frac;
1245     } else if (MMInsertionPointVertical == shape) {
1246         int frac = (cellSize.width * percent + 99)/100;
1247         rect.size.width = frac;
1248     } else if (MMInsertionPointVerticalRight == shape) {
1249         int frac = (cellSize.width * percent + 99)/100;
1250         rect.origin.x += rect.size.width - frac;
1251         rect.size.width = frac;
1252     }
1254     [[helper insertionPointColor] set];
1255     if (MMInsertionPointHollow == shape) {
1256         NSFrameRect(rect);
1257     } else {
1258         NSRectFill(rect);
1259     }
1262 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nrows
1263                    numColumns:(int)ncols
1265     // TODO: THIS CODE HAS NOT BEEN TESTED!
1266     CGContextRef cgctx = [[NSGraphicsContext currentContext] graphicsPort];
1267     CGContextSaveGState(cgctx);
1268     CGContextSetBlendMode(cgctx, kCGBlendModeDifference);
1269     CGContextSetRGBFillColor(cgctx, 1.0, 1.0, 1.0, 1.0);
1271     CGRect rect = { col * cellSize.width, row * cellSize.height,
1272                     ncols * cellSize.width, nrows * cellSize.height };
1273     CGContextFillRect(cgctx, rect);
1275     CGContextRestoreGState(cgctx);
1278 @end // MMAtsuiTextView (Drawing)