Fix placement of auxiliary IM window for Core Text
[MacVim.git] / src / MacVim / MMAtsuiTextView.m
blob3137ea2cbec675350e4b1b61432de14a9e5fe4e6
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"
34 #if MM_ENABLE_ATSUI
36 // TODO: What does DRAW_TRANSP flag do?  If the background isn't drawn when
37 // this flag is set, then sometimes the character after the cursor becomes
38 // blank.  Everything seems to work fine by just ignoring this flag.
39 #define DRAW_TRANSP               0x01    /* draw with transparant bg */
40 #define DRAW_BOLD                 0x02    /* draw bold text */
41 #define DRAW_UNDERL               0x04    /* draw underline text */
42 #define DRAW_UNDERC               0x08    /* draw undercurl text */
43 #define DRAW_ITALIC               0x10    /* draw italic text */
44 #define DRAW_CURSOR               0x20
45 #define DRAW_WIDE                 0x40    /* draw wide text */
47 #define kUnderlineOffset            (-2)
48 #define kUnderlineHeight            1
49 #define kUndercurlHeight            2
50 #define kUndercurlOffset            (-2)
51 #define kUndercurlDotWidth          2
52 #define kUndercurlDotDistance       2
55 @interface NSFont (AppKitPrivate)
56 - (ATSUFontID) _atsFontID;
57 @end
60 @interface MMAtsuiTextView (Private)
61 - (void)initAtsuStyles;
62 - (void)disposeAtsuStyles;
63 - (void)updateAtsuStyles;
64 - (MMWindowController *)windowController;
65 - (MMVimController *)vimController;
66 @end
69 @interface MMAtsuiTextView (Drawing)
70 - (NSPoint)originForRow:(int)row column:(int)column;
71 - (NSRect)rectFromRow:(int)row1 column:(int)col1
72                 toRow:(int)row2 column:(int)col2;
73 - (NSSize)textAreaSize;
74 - (void)resizeContentImage;
75 - (void)beginDrawing;
76 - (void)endDrawing;
77 - (void)drawString:(UniChar *)string length:(UniCharCount)length
78              atRow:(int)row column:(int)col cells:(int)cells
79          withFlags:(int)flags foregroundColor:(NSColor *)fg
80    backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp;
81 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
82               scrollBottom:(int)bottom left:(int)left right:(int)right
83                      color:(NSColor *)color;
84 - (void)insertLinesAtRow:(int)row lineCount:(int)count
85             scrollBottom:(int)bottom left:(int)left right:(int)right
86                    color:(NSColor *)color;
87 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
88                    column:(int)col2 color:(NSColor *)color;
89 - (void)clearAll;
90 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
91                        fraction:(int)percent;
92 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nrows
93                    numColumns:(int)ncols;
94 @end
98     static float
99 defaultLineHeightForFont(NSFont *font)
101     // HACK: -[NSFont defaultLineHeightForFont] is deprecated but since the
102     // ATSUI renderer does not use NSLayoutManager we create one temporarily.
103     NSLayoutManager *lm = [[NSLayoutManager alloc] init];
104     float height = [lm defaultLineHeightForFont:font];
105     [lm release];
107     return height;
110 @implementation MMAtsuiTextView
112 - (id)initWithFrame:(NSRect)frame
114     if (!(self = [super initWithFrame:frame]))
115         return nil;
117     // NOTE!  It does not matter which font is set here, Vim will set its
118     // own font on startup anyway.  Just set some bogus values.
119     font = [[NSFont userFixedPitchFontOfSize:0] retain];
120     ascender = 0;
121     cellSize.width = cellSize.height = 1;
122     contentImage = nil;
123     imageSize = NSZeroSize;
124     insetSize = NSZeroSize;
126     // NOTE: If the default changes to 'NO' then the intialization of
127     // p_antialias in option.c must change as well.
128     antialias = YES;
130     helper = [[MMTextViewHelper alloc] init];
131     [helper setTextView:self];
133     [self initAtsuStyles];
135     [self registerForDraggedTypes:[NSArray arrayWithObjects:
136             NSFilenamesPboardType, NSStringPboardType, nil]];
138     return self;
141 - (void)dealloc
143     ASLogDebug(@"");
145     [self disposeAtsuStyles];
146     [font release];  font = nil;
147     [fontWide release];  fontWide = 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     // Compute rect whose vertical dimensions cover the rows in the given
212     // range.
213     // NOTE: The rect should be in _flipped_ coordinates and the first row must
214     // include the top inset as well.  (This method is only used to place the
215     // scrollbars inside MMVimView.)
217     NSRect rect = { {0, 0}, {0, 0} };
218     unsigned start = range.location > maxRows ? maxRows : range.location;
219     unsigned length = range.length;
221     if (start + length > maxRows)
222         length = maxRows - start;
224     if (start > 0) {
225         rect.origin.y = cellSize.height * start + insetSize.height;
226         rect.size.height = cellSize.height * length;
227     } else {
228         // Include top inset
229         rect.origin.y = 0;
230         rect.size.height = cellSize.height * length + insetSize.height;
231     }
233     return rect;
236 - (NSRect)rectForColumnsInRange:(NSRange)range
238     // Compute rect whose horizontal dimensions cover the columns in the given
239     // range.
240     // NOTE: The first column must include the left inset.  (This method is
241     // only used to place the scrollbars inside MMVimView.)
243     NSRect rect = { {0, 0}, {0, 0} };
244     unsigned start = range.location > maxColumns ? maxColumns : range.location;
245     unsigned length = range.length;
247     if (start+length > maxColumns)
248         length = maxColumns - start;
250     if (start > 0) {
251         rect.origin.x = cellSize.width * start + insetSize.width;
252         rect.size.width = cellSize.width * length;
253     } else {
254         // Include left inset
255         rect.origin.x = 0;
256         rect.size.width = cellSize.width * length + insetSize.width;
257     }
259     return rect;
263 - (void)setFont:(NSFont *)newFont
265     if (newFont && font != newFont) {
266         [font release];
267         font = [newFont retain];
268         ascender = roundf([font ascender]);
270         float em = [@"m" sizeWithAttributes:
271                 [NSDictionary dictionaryWithObject:newFont
272                                             forKey:NSFontAttributeName]].width;
273         float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
274                 floatForKey:MMCellWidthMultiplierKey];
276         // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
277         // only render at integer sizes.  Hence, we restrict the cell width to
278         // an integer here, otherwise the window width and the actual text
279         // width will not match.
280         cellSize.width = ceilf(em * cellWidthMultiplier);
281         cellSize.height = linespace + defaultLineHeightForFont(newFont);
283         [self updateAtsuStyles];
284     }
287 - (void)setWideFont:(NSFont *)newFont
289     if (!newFont) {
290         if (font) [self setWideFont:font];
291     } else if (newFont != fontWide) {
292         [fontWide release];
294         float pointSize = [newFont pointSize];
295         NSFontDescriptor *desc = [newFont fontDescriptor];
296         NSDictionary *dictWide = [NSDictionary
297             dictionaryWithObject:[NSNumber numberWithFloat:2*cellSize.width]
298                           forKey:NSFontFixedAdvanceAttribute];
299         desc = [desc fontDescriptorByAddingAttributes:dictWide];
300         fontWide = [NSFont fontWithDescriptor:desc size:pointSize];
301         [fontWide retain];
302     }
305 - (NSFont *)font
307     return font;
310 - (NSFont *)fontWide
312     return fontWide;
315 - (NSSize)cellSize
317     return cellSize;
320 - (void)setLinespace:(float)newLinespace
322     linespace = newLinespace;
324     // NOTE: The linespace is added to the cell height in order for a multiline
325     // selection not to have white (background color) gaps between lines.  Also
326     // this simplifies the code a lot because there is no need to check the
327     // linespace when calculating the size of the text view etc.  When the
328     // linespace is non-zero the baseline will be adjusted as well; check
329     // MMTypesetter.
330     cellSize.height = linespace + defaultLineHeightForFont(font);
335 - (void)setShouldDrawInsertionPoint:(BOOL)on
339 - (void)setPreEditRow:(int)row column:(int)col
341     [helper setPreEditRow:row column:col];
344 - (void)setMouseShape:(int)shape
346     [helper setMouseShape:shape];
349 - (void)setAntialias:(BOOL)state
351     antialias = state;
354 - (void)setImControl:(BOOL)enable
356     [helper setImControl:enable];
359 - (void)activateIm:(BOOL)enable
361     [helper activateIm:enable];
364 - (BOOL)_wantsKeyDownForEvent:(id)event
366     // HACK! This is an undocumented method which is called from within
367     // -[NSWindow sendEvent] (and perhaps in other places as well) when the
368     // user presses e.g. Ctrl-Tab or Ctrl-Esc .  Returning YES here effectively
369     // disables the Cocoa "key view loop" (which is undesirable).  It may have
370     // other side-effects, but we really _do_ want to process all key down
371     // events so it seems safe to always return YES.
372     return YES;
375 - (void)keyDown:(NSEvent *)event
377     [helper keyDown:event];
380 - (void)insertText:(id)string
382     [helper insertText:string];
385 - (void)doCommandBySelector:(SEL)selector
387     [helper doCommandBySelector:selector];
390 - (BOOL)performKeyEquivalent:(NSEvent *)event
392     return [helper performKeyEquivalent:event];
395 - (BOOL)hasMarkedText
397     return [helper hasMarkedText];
400 - (NSRange)markedRange
402     return [helper markedRange];
405 - (NSDictionary *)markedTextAttributes
407     return [helper markedTextAttributes];
410 - (void)setMarkedTextAttributes:(NSDictionary *)attr
412     [helper setMarkedTextAttributes:attr];
415 - (void)setMarkedText:(id)text selectedRange:(NSRange)range
417     [helper setMarkedText:text selectedRange:range];
420 - (void)unmarkText
422     [helper unmarkText];
425 - (void)scrollWheel:(NSEvent *)event
427     [helper scrollWheel:event];
430 - (void)mouseDown:(NSEvent *)event
432     [helper mouseDown:event];
435 - (void)rightMouseDown:(NSEvent *)event
437     [helper mouseDown:event];
440 - (void)otherMouseDown:(NSEvent *)event
442     [helper mouseDown:event];
445 - (void)mouseUp:(NSEvent *)event
447     [helper mouseUp:event];
450 - (void)rightMouseUp:(NSEvent *)event
452     [helper mouseUp:event];
455 - (void)otherMouseUp:(NSEvent *)event
457     [helper mouseUp:event];
460 - (void)mouseDragged:(NSEvent *)event
462     [helper mouseDragged:event];
465 - (void)rightMouseDragged:(NSEvent *)event
467     [helper mouseDragged:event];
470 - (void)otherMouseDragged:(NSEvent *)event
472     [helper mouseDragged:event];
475 - (void)mouseMoved:(NSEvent *)event
477     [helper mouseMoved:event];
480 - (void)mouseEntered:(NSEvent *)event
482     [helper mouseEntered:event];
485 - (void)mouseExited:(NSEvent *)event
487     [helper mouseExited:event];
490 - (void)setFrame:(NSRect)frame
492     [super setFrame:frame];
493     [helper setFrame:frame];
496 - (void)viewDidMoveToWindow
498     [helper viewDidMoveToWindow];
501 - (void)viewWillMoveToWindow:(NSWindow *)newWindow
503     [helper viewWillMoveToWindow:newWindow];
506 - (NSMenu*)menuForEvent:(NSEvent *)event
508     // HACK! Return nil to disable default popup menus (Vim provides its own).
509     // Called when user Ctrl-clicks in the view (this is already handled in
510     // rightMouseDown:).
511     return nil;
514 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
516     return [helper performDragOperation:sender];
519 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
521     return [helper draggingEntered:sender];
524 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
526     return [helper draggingUpdated:sender];
531 - (BOOL)mouseDownCanMoveWindow
533     return NO;
536 - (BOOL)isOpaque
538     return YES;
541 - (BOOL)acceptsFirstResponder
543     return YES;
546 - (BOOL)isFlipped
548     return NO;
551 - (void)drawRect:(NSRect)rect
553     [defaultBackgroundColor set];
554     NSRectFill(rect);
556     NSPoint pt = { insetSize.width, insetSize.height };
557     [contentImage compositeToPoint:pt operation:NSCompositeCopy];
559 #ifdef INCLUDE_OLD_IM_CODE
560     if ([self hasMarkedText] && ![helper useInlineIm]) {
561         int len = [[helper markedText] length];
562         int rows = 0;
563         int cols = maxColumns - [helper preEditColumn];
564         NSFont *theFont = [[self markedTextAttributes]
565             valueForKey:NSFontAttributeName];
566         if (theFont == [self fontWide])
567             cols = cols / 2;
568         int done = 0;
569         int lend = cols > len ? len : cols;
570         NSAttributedString *aString = [[helper markedText]
571                 attributedSubstringFromRange:NSMakeRange(done, lend)];
572         NSPoint pt = [self pointForRow:[helper preEditRow]
573                                 column:[helper preEditColumn]];
574         [aString drawAtPoint:pt];
575         done = lend;
576         if (done != len) {
577             int r;
578             rows = (len - done) / (maxColumns / 2) + 1;
579             for (r = 1; r <= rows; r++) {
580             lend = len - done > maxColumns / 2
581                 ? maxColumns / 2 : len - done;
582                 aString = [[helper markedText] attributedSubstringFromRange:
583                         NSMakeRange(done, lend)];
584                 NSPoint pt = [self pointForRow:[helper preEditRow]+r
585                                         column:0];
586                 [aString drawAtPoint:pt];
587                 done += lend;
588             }
589         }
591         rows = maxRows - 1 - [helper preEditRow];
592         cols = [helper preEditColumn];
593         if (theFont == fontWide) {
594             cols += ([helper imRange].location+[helper imRange].length) * 2;
595             if (cols >= maxColumns - 1) {
596                 rows -= cols / maxColumns;
597                 cols = cols % 2 ? cols % maxColumns + 1 :
598                                   cols % maxColumns;
599             }
600         } else {
601             cols += ([helper imRange].location+[helper imRange].length);
602             if (cols >= maxColumns) {
603                 rows -= cols / maxColumns;
604                 cols = cols % 2 ? cols % maxColumns + 1 :
605                                   cols % maxColumns;
606             }
607         }
609         // TODO: Could IM be in "right-left" mode?  If so the insertion point
610         // will be on the wrong side.
611         [self drawInsertionPointAtRow:rows
612                                column:cols
613                                 shape:MMInsertionPointVertical
614                              fraction:25];
615     }
616 #endif // INCLUDE_OLD_IM_CODE
619 - (BOOL) wantsDefaultClipping
621     return NO;
625 #define MM_DEBUG_DRAWING 0
627 - (void)performBatchDrawWithData:(NSData *)data
629     const void *bytes = [data bytes];
630     const void *end = bytes + [data length];
632     if (! NSEqualSizes(imageSize, [self textAreaSize]))
633         [self resizeContentImage];
635 #if MM_DEBUG_DRAWING
636     ASLogDebug(@"====> BEGIN %s", _cmd);
637 #endif
638     [self beginDrawing];
640     // TODO: Sanity check input
642     while (bytes < end) {
643         int type = *((int*)bytes);  bytes += sizeof(int);
645         if (ClearAllDrawType == type) {
646 #if MM_DEBUG_DRAWING
647             ASLogDebug(@"   Clear all");
648 #endif
649             [self clearAll];
650         } else if (ClearBlockDrawType == type) {
651             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
652             int row1 = *((int*)bytes);  bytes += sizeof(int);
653             int col1 = *((int*)bytes);  bytes += sizeof(int);
654             int row2 = *((int*)bytes);  bytes += sizeof(int);
655             int col2 = *((int*)bytes);  bytes += sizeof(int);
657 #if MM_DEBUG_DRAWING
658             ASLogDebug(@"   Clear block (%d,%d) -> (%d,%d)", row1, col1,
659                        row2,col2);
660 #endif
661             [self clearBlockFromRow:row1 column:col1
662                     toRow:row2 column:col2
663                     color:[NSColor colorWithArgbInt:color]];
664         } else if (DeleteLinesDrawType == type) {
665             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
666             int row = *((int*)bytes);  bytes += sizeof(int);
667             int count = *((int*)bytes);  bytes += sizeof(int);
668             int bot = *((int*)bytes);  bytes += sizeof(int);
669             int left = *((int*)bytes);  bytes += sizeof(int);
670             int right = *((int*)bytes);  bytes += sizeof(int);
672 #if MM_DEBUG_DRAWING
673             ASLogDebug(@"   Delete %d line(s) from %d", count, row);
674 #endif
675             [self deleteLinesFromRow:row lineCount:count
676                     scrollBottom:bot left:left right:right
677                            color:[NSColor colorWithArgbInt:color]];
678         } else if (DrawStringDrawType == type) {
679             int bg = *((int*)bytes);  bytes += sizeof(int);
680             int fg = *((int*)bytes);  bytes += sizeof(int);
681             int sp = *((int*)bytes);  bytes += sizeof(int);
682             int row = *((int*)bytes);  bytes += sizeof(int);
683             int col = *((int*)bytes);  bytes += sizeof(int);
684             int cells = *((int*)bytes);  bytes += sizeof(int);
685             int flags = *((int*)bytes);  bytes += sizeof(int);
686             int len = *((int*)bytes);  bytes += sizeof(int);
687             // UniChar *string = (UniChar*)bytes;  bytes += len;
688             NSString *string = [[NSString alloc]
689                     initWithBytesNoCopy:(void*)bytes
690                                  length:len
691                                encoding:NSUTF8StringEncoding
692                            freeWhenDone:NO];
693             bytes += len;
694 #if MM_DEBUG_DRAWING
695             ASLogDebug(@"   Draw string at (%d,%d) length=%d flags=%d fg=0x%x "
696                        "bg=0x%x sp=0x%x", row, col, len, flags, fg, bg, sp);
697 #endif
698             unichar *characters = malloc(sizeof(unichar) * [string length]);
699             [string getCharacters:characters];
701             [self drawString:characters
702                              length:[string length]
703                               atRow:row
704                              column:col
705                               cells:cells withFlags:flags
706                     foregroundColor:[NSColor colorWithRgbInt:fg]
707                     backgroundColor:[NSColor colorWithArgbInt:bg]
708                        specialColor:[NSColor colorWithRgbInt:sp]];
709             free(characters);
710             [string release];
711         } else if (InsertLinesDrawType == type) {
712             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
713             int row = *((int*)bytes);  bytes += sizeof(int);
714             int count = *((int*)bytes);  bytes += sizeof(int);
715             int bot = *((int*)bytes);  bytes += sizeof(int);
716             int left = *((int*)bytes);  bytes += sizeof(int);
717             int right = *((int*)bytes);  bytes += sizeof(int);
719 #if MM_DEBUG_DRAWING
720             ASLogDebug(@"   Insert %d line(s) at row %d", count, row);
721 #endif
722             [self insertLinesAtRow:row lineCount:count
723                              scrollBottom:bot left:left right:right
724                                     color:[NSColor colorWithArgbInt:color]];
725         } else if (DrawCursorDrawType == type) {
726             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
727             int row = *((int*)bytes);  bytes += sizeof(int);
728             int col = *((int*)bytes);  bytes += sizeof(int);
729             int shape = *((int*)bytes);  bytes += sizeof(int);
730             int percent = *((int*)bytes);  bytes += sizeof(int);
732 #if MM_DEBUG_DRAWING
733             ASLogDebug(@"   Draw cursor at (%d,%d)", row, col);
734 #endif
735             [helper setInsertionPointColor:[NSColor colorWithRgbInt:color]];
736             [self drawInsertionPointAtRow:row column:col shape:shape
737                                  fraction:percent];
738         } else if (DrawInvertedRectDrawType == type) {
739             int row = *((int*)bytes);  bytes += sizeof(int);
740             int col = *((int*)bytes);  bytes += sizeof(int);
741             int nr = *((int*)bytes);  bytes += sizeof(int);
742             int nc = *((int*)bytes);  bytes += sizeof(int);
743             /*int invert = *((int*)bytes);*/  bytes += sizeof(int);
745 #if MM_DEBUG_DRAWING
746             ASLogDebug(@"   Draw inverted rect: row=%d col=%d nrows=%d "
747                        "ncols=%d", row, col, nr, nc);
748 #endif
749             [self drawInvertedRectAtRow:row column:col numRows:nr
750                              numColumns:nc];
751         } else if (SetCursorPosDrawType == type) {
752             // TODO: This is used for Voice Over support in MMTextView,
753             // MMAtsuiTextView currently does not support Voice Over.
754             /*cursorRow = *((int*)bytes);*/  bytes += sizeof(int);
755             /*cursorCol = *((int*)bytes);*/  bytes += sizeof(int);
756         } else {
757             ASLogWarn(@"Unknown draw type (type=%d)", type);
758         }
759     }
761     [self endDrawing];
763     [self setNeedsDisplay:YES];
765     // NOTE: During resizing, Cocoa only sends draw messages before Vim's rows
766     // and columns are changed (due to ipc delays). Force a redraw here.
767     if ([self inLiveResize])
768         [self display];
770 #if MM_DEBUG_DRAWING
771     ASLogDebug(@"<==== END   %s", _cmd);
772 #endif
775 - (NSSize)constrainRows:(int *)rows columns:(int *)cols toSize:(NSSize)size
777     // TODO:
778     // - Rounding errors may cause size change when there should be none
779     // - Desired rows/columns shold not be 'too small'
781     // Constrain the desired size to the given size.  Values for the minimum
782     // rows and columns are taken from Vim.
783     NSSize desiredSize = [self desiredSize];
784     int desiredRows = maxRows;
785     int desiredCols = maxColumns;
787     if (size.height != desiredSize.height) {
788         float fh = cellSize.height;
789         float ih = 2 * insetSize.height;
790         if (fh < 1.0f) fh = 1.0f;
792         desiredRows = floor((size.height - ih)/fh);
793         desiredSize.height = fh*desiredRows + ih;
794     }
796     if (size.width != desiredSize.width) {
797         float fw = cellSize.width;
798         float iw = 2 * insetSize.width;
799         if (fw < 1.0f) fw = 1.0f;
801         desiredCols = floor((size.width - iw)/fw);
802         desiredSize.width = fw*desiredCols + iw;
803     }
805     if (rows) *rows = desiredRows;
806     if (cols) *cols = desiredCols;
808     return desiredSize;
811 - (NSSize)desiredSize
813     // Compute the size the text view should be for the entire text area and
814     // inset area to be visible with the present number of rows and columns.
815     return NSMakeSize(maxColumns * cellSize.width + 2 * insetSize.width,
816                       maxRows * cellSize.height + 2 * insetSize.height);
819 - (NSSize)minSize
821     // Compute the smallest size the text view is allowed to be.
822     return NSMakeSize(MMMinColumns * cellSize.width + 2 * insetSize.width,
823                       MMMinRows * cellSize.height + 2 * insetSize.height);
826 - (void)changeFont:(id)sender
828     [helper changeFont:sender];
833 // NOTE: The menu items cut/copy/paste/undo/redo/select all/... must be bound
834 // to the same actions as in IB otherwise they will not work with dialogs.  All
835 // we do here is forward these actions to the Vim process.
837 - (IBAction)cut:(id)sender
839     [[self windowController] vimMenuItemAction:sender];
842 - (IBAction)copy:(id)sender
844     [[self windowController] vimMenuItemAction:sender];
847 - (IBAction)paste:(id)sender
849     [[self windowController] vimMenuItemAction:sender];
852 - (IBAction)undo:(id)sender
854     [[self windowController] vimMenuItemAction:sender];
857 - (IBAction)redo:(id)sender
859     [[self windowController] vimMenuItemAction:sender];
862 - (IBAction)selectAll:(id)sender
864     [[self windowController] vimMenuItemAction:sender];
867 - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column
869     // View is not flipped, instead the atsui code draws to a flipped image;
870     // thus we need to 'flip' the coordinate here since the column number
871     // increases in an up-to-down order.
872     point.y = [self frame].size.height - point.y;
874     NSPoint origin = { insetSize.width, insetSize.height };
876     if (!(cellSize.width > 0 && cellSize.height > 0))
877         return NO;
879     if (row) *row = floor((point.y-origin.y-1) / cellSize.height);
880     if (column) *column = floor((point.x-origin.x-1) / cellSize.width);
882     return YES;
885 - (NSPoint)pointForRow:(int)row column:(int)col
887     // Return the lower left coordinate of the cell at (row,column).
888     NSPoint pt;
890     pt.x = insetSize.width + col*cellSize.width;
891     pt.y = [self frame].size.height -
892            (insetSize.height + (1+row)*cellSize.height);
894     return pt;
897 - (NSRect)rectForRow:(int)row column:(int)col numRows:(int)nr
898           numColumns:(int)nc
900     // Return the rect for the block which covers the specified rows and
901     // columns.  The lower-left corner is the origin of this rect.
902     NSRect rect;
904     rect.origin.x = insetSize.width + col*cellSize.width;
905     rect.origin.y = [self frame].size.height -
906                     (insetSize.height + (nr+row)*cellSize.height);
907     rect.size.width = nc*cellSize.width;
908     rect.size.height = nr*cellSize.height;
910     return rect;
913 - (NSArray *)validAttributesForMarkedText
915     return nil;
918 - (NSAttributedString *)attributedSubstringFromRange:(NSRange)range
920     return nil;
923 - (NSUInteger)characterIndexForPoint:(NSPoint)point
925     return NSNotFound;
928 // The return type of this message changed with OS X 10.5 so we need this
929 // kludge in order to avoid compiler warnings on OS X 10.4.
930 #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
931 - (NSInteger)conversationIdentifier
933     return (NSInteger)self;
935 #else
936 - (long)conversationIdentifier
938     return (long)self;
940 #endif
942 - (NSRange)selectedRange
944     return [helper imRange];
947 - (NSRect)firstRectForCharacterRange:(NSRange)range
949     return [helper firstRectForCharacterRange:range];
952 @end // MMAtsuiTextView
957 @implementation MMAtsuiTextView (Private)
959 - (void)initAtsuStyles
961     int i;
962     for (i = 0; i < MMMaxCellsPerChar; i++)
963         ATSUCreateStyle(&atsuStyles[i]);
966 - (void)disposeAtsuStyles
968     int i;
970     for (i = 0; i < MMMaxCellsPerChar; i++)
971         if (atsuStyles[i] != NULL)
972         {
973             if (ATSUDisposeStyle(atsuStyles[i]) != noErr)
974                 atsuStyles[i] = NULL;
975         }
978 - (void)updateAtsuStyles
980     ATSUFontID        fontID;
981     Fixed             fontSize;
982     Fixed             fontWidth;
983     int               i;
984     CGAffineTransform transform = CGAffineTransformMakeScale(1, -1);
985     ATSStyleRenderingOptions options;
987     fontID    = [font _atsFontID];
988     fontSize  = Long2Fix([font pointSize]);
989     options   = kATSStyleApplyAntiAliasing;
991     ATSUAttributeTag attribTags[] =
992     {
993         kATSUFontTag, kATSUSizeTag, kATSUImposeWidthTag,
994         kATSUFontMatrixTag, kATSUStyleRenderingOptionsTag,
995         kATSUMaxATSUITagValue + 1
996     };
998     ByteCount attribSizes[] =
999     {
1000         sizeof(ATSUFontID), sizeof(Fixed), sizeof(fontWidth),
1001         sizeof(CGAffineTransform), sizeof(ATSStyleRenderingOptions),
1002         sizeof(font)
1003     };
1005     ATSUAttributeValuePtr attribValues[] =
1006     {
1007         &fontID, &fontSize, &fontWidth, &transform, &options, &font
1008     };
1010     ATSUFontFeatureType featureTypes[] = {
1011         kLigaturesType, kLigaturesType
1012     };
1014     ATSUFontFeatureSelector featureSelectors[] = {
1015         kCommonLigaturesOffSelector, kRareLigaturesOffSelector
1016     };
1018     for (i = 0; i < MMMaxCellsPerChar; i++)
1019     {
1020         fontWidth = Long2Fix(cellSize.width * (i + 1));
1022         if (ATSUSetAttributes(atsuStyles[i],
1023                               (sizeof attribTags) / sizeof(ATSUAttributeTag),
1024                               attribTags, attribSizes, attribValues) != noErr)
1025         {
1026             ATSUDisposeStyle(atsuStyles[i]);
1027             atsuStyles[i] = NULL;
1028         }
1030         // Turn off ligatures by default
1031         ATSUSetFontFeatures(atsuStyles[i],
1032                             sizeof(featureTypes) / sizeof(featureTypes[0]),
1033                             featureTypes, featureSelectors);
1034     }
1037 - (MMWindowController *)windowController
1039     id windowController = [[self window] windowController];
1040     if ([windowController isKindOfClass:[MMWindowController class]])
1041         return (MMWindowController*)windowController;
1042     return nil;
1045 - (MMVimController *)vimController
1047     return [[self windowController] vimController];
1050 @end // MMAtsuiTextView (Private)
1055 @implementation MMAtsuiTextView (Drawing)
1057 - (NSPoint)originForRow:(int)row column:(int)col
1059     return NSMakePoint(col * cellSize.width, row * cellSize.height);
1062 - (NSRect)rectFromRow:(int)row1 column:(int)col1
1063                 toRow:(int)row2 column:(int)col2
1065     NSPoint origin = [self originForRow:row1 column:col1];
1066     return NSMakeRect(origin.x, origin.y,
1067                       (col2 + 1 - col1) * cellSize.width,
1068                       (row2 + 1 - row1) * cellSize.height);
1071 - (NSSize)textAreaSize
1073     // Calculate the (desired) size of the text area, i.e. the text view area
1074     // minus the inset area.
1075     return NSMakeSize(maxColumns * cellSize.width, maxRows * cellSize.height);
1078 - (void)resizeContentImage
1080     [contentImage release];
1081     contentImage = [[NSImage alloc] initWithSize:[self textAreaSize]];
1082     [contentImage setFlipped:YES];
1083     imageSize = [self textAreaSize];
1086 - (void)beginDrawing
1088     [contentImage lockFocus];
1091 - (void)endDrawing
1093     [contentImage unlockFocus];
1096 #define atsu_style_set_bool(s, t, b) \
1097     ATSUSetAttributes(s, 1, &t, &(sizeof(Boolean)), &&b);
1098 #define FILL_Y(y)    (y * cellSize.height)
1100 - (void)drawString:(UniChar *)string length:(UniCharCount)length
1101              atRow:(int)row column:(int)col cells:(int)cells
1102          withFlags:(int)flags foregroundColor:(NSColor *)fg
1103    backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp
1105     // 'string' consists of 'length' utf-16 code pairs and should cover 'cells'
1106     // display cells (a normal character takes up one display cell, a wide
1107     // character takes up two)
1108     ATSUStyle       style = (flags & DRAW_WIDE) ? atsuStyles[1] : atsuStyles[0];
1109     ATSUTextLayout  layout;
1111     // Font selection and rendering options for ATSUI
1112     ATSUAttributeTag      attribTags[3] = { kATSUQDBoldfaceTag,
1113                                             kATSUFontMatrixTag,
1114                                             kATSUStyleRenderingOptionsTag };
1116     ByteCount             attribSizes[] = { sizeof(Boolean),
1117                                             sizeof(CGAffineTransform),
1118                                             sizeof(UInt32) };
1119     Boolean               useBold;
1120     CGAffineTransform     theTransform = CGAffineTransformMakeScale(1.0, -1.0);
1121     UInt32                useAntialias;
1123     ATSUAttributeValuePtr attribValues[3] = { &useBold, &theTransform,
1124                                               &useAntialias };
1126     useBold      = (flags & DRAW_BOLD) ? true : false;
1128     if (flags & DRAW_ITALIC)
1129         theTransform.c = Fix2X(kATSItalicQDSkew);
1131     useAntialias = antialias ? kATSStyleApplyAntiAliasing
1132                              : kATSStyleNoAntiAliasing;
1134     ATSUSetAttributes(style, sizeof(attribValues) / sizeof(attribValues[0]),
1135                       attribTags, attribSizes, attribValues);
1137     ATSUCreateTextLayout(&layout);
1138     ATSUSetTextPointerLocation(layout, string,
1139                                kATSUFromTextBeginning, kATSUToTextEnd,
1140                                length);
1141     ATSUSetRunStyle(layout, style, kATSUFromTextBeginning, kATSUToTextEnd);
1143     NSRect rect = NSMakeRect(col * cellSize.width, row * cellSize.height,
1144                              length * cellSize.width, cellSize.height);
1145     if (flags & DRAW_WIDE)
1146         rect.size.width = rect.size.width * 2;
1147     CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
1149     // Clip drawing to avoid text bleeding into adjacent display cells when
1150     // antialiasing is enabled.
1151     CGContextSaveGState(context);
1152     CGContextClipToRect(context, *(CGRect*)&rect);
1154     ATSUAttributeTag tags[] = { kATSUCGContextTag };
1155     ByteCount sizes[] = { sizeof(CGContextRef) };
1156     ATSUAttributeValuePtr values[] = { &context };
1157     ATSUSetLayoutControls(layout, 1, tags, sizes, values);
1159     if (! (flags & DRAW_TRANSP))
1160     {
1161         [bg set];
1162         NSRectFill(rect);
1163     }
1165     [fg set];
1167     ATSUSetTransientFontMatching(layout, TRUE);
1168     ATSUDrawText(layout,
1169                  kATSUFromTextBeginning,
1170                  kATSUToTextEnd,
1171                  X2Fix(rect.origin.x),
1172                  X2Fix(rect.origin.y + ascender));
1173     ATSUDisposeTextLayout(layout);
1175     if (flags & DRAW_UNDERL)
1176     {
1177         [sp set];
1178         NSRectFill(NSMakeRect(rect.origin.x,
1179                               (row + 1) * cellSize.height + kUnderlineOffset,
1180                               rect.size.width, kUnderlineHeight));
1181     }
1183     if (flags & DRAW_UNDERC)
1184     {
1185         [sp set];
1187         float line_end_x = rect.origin.x + rect.size.width;
1188         int i = 0;
1189         NSRect line_rect = NSMakeRect(
1190                 rect.origin.x,
1191                 (row + 1) * cellSize.height + kUndercurlOffset,
1192                 kUndercurlDotWidth, kUndercurlHeight);
1194         while (line_rect.origin.x < line_end_x)
1195         {
1196             if (i % 2)
1197                 NSRectFill(line_rect);
1199             line_rect.origin.x += kUndercurlDotDistance;
1200             i++;
1201         }
1202     }
1204     CGContextRestoreGState(context);
1207 - (void)scrollRect:(NSRect)rect lineCount:(int)count
1209     NSPoint destPoint = rect.origin;
1210     destPoint.y += count * cellSize.height;
1212     NSCopyBits(0, rect, destPoint);
1215 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
1216               scrollBottom:(int)bottom left:(int)left right:(int)right
1217                      color:(NSColor *)color
1219     NSRect rect = [self rectFromRow:row + count
1220                              column:left
1221                               toRow:bottom
1222                              column:right];
1223     [color set];
1224     // move rect up for count lines
1225     [self scrollRect:rect lineCount:-count];
1226     [self clearBlockFromRow:bottom - count + 1
1227                      column:left
1228                       toRow:bottom
1229                      column:right
1230                       color:color];
1233 - (void)insertLinesAtRow:(int)row lineCount:(int)count
1234             scrollBottom:(int)bottom left:(int)left right:(int)right
1235                    color:(NSColor *)color
1237     NSRect rect = [self rectFromRow:row
1238                              column:left
1239                               toRow:bottom - count
1240                              column:right];
1241     [color set];
1242     // move rect down for count lines
1243     [self scrollRect:rect lineCount:count];
1244     [self clearBlockFromRow:row
1245                      column:left
1246                       toRow:row + count - 1
1247                      column:right
1248                       color:color];
1251 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
1252                    column:(int)col2 color:(NSColor *)color
1254     [color set];
1255     NSRectFill([self rectFromRow:row1 column:col1 toRow:row2 column:col2]);
1258 - (void)clearAll
1260     [defaultBackgroundColor set];
1261     NSRectFill(NSMakeRect(0, 0, imageSize.width, imageSize.height));
1264 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
1265                        fraction:(int)percent
1267     NSPoint origin = [self originForRow:row column:col];
1268     NSRect rect = NSMakeRect(origin.x, origin.y,
1269                              cellSize.width, cellSize.height);
1271     if (MMInsertionPointHorizontal == shape) {
1272         int frac = (cellSize.height * percent + 99)/100;
1273         rect.origin.y += rect.size.height - frac;
1274         rect.size.height = frac;
1275     } else if (MMInsertionPointVertical == shape) {
1276         int frac = (cellSize.width * percent + 99)/100;
1277         rect.size.width = frac;
1278     } else if (MMInsertionPointVerticalRight == shape) {
1279         int frac = (cellSize.width * percent + 99)/100;
1280         rect.origin.x += rect.size.width - frac;
1281         rect.size.width = frac;
1282     }
1284     [[helper insertionPointColor] set];
1285     if (MMInsertionPointHollow == shape) {
1286         NSFrameRect(rect);
1287     } else {
1288         NSRectFill(rect);
1289     }
1292 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nrows
1293                    numColumns:(int)ncols
1295     // TODO: THIS CODE HAS NOT BEEN TESTED!
1296     CGContextRef cgctx = [[NSGraphicsContext currentContext] graphicsPort];
1297     CGContextSaveGState(cgctx);
1298     CGContextSetBlendMode(cgctx, kCGBlendModeDifference);
1299     CGContextSetRGBFillColor(cgctx, 1.0, 1.0, 1.0, 1.0);
1301     CGRect rect = { col * cellSize.width, row * cellSize.height,
1302                     ncols * cellSize.width, nrows * cellSize.height };
1303     CGContextFillRect(cgctx, rect);
1305     CGContextRestoreGState(cgctx);
1308 @end // MMAtsuiTextView (Drawing)
1310 #endif // MM_ENABLE_ATSUI