Backend initiates window zooming
[MacVim.git] / src / MacVim / MMAtsuiTextView.m
blobfca5011f0af1a9f62da81dfe066dc1a0c154b413
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     [defaultBackgroundColor release];  defaultBackgroundColor = nil;
148     [defaultForegroundColor release];  defaultForegroundColor = nil;
150     [helper setTextView:nil];
151     [helper release];  helper = nil;
153     [super dealloc];
156 - (int)maxRows
158     return maxRows;
161 - (int)maxColumns
163     return maxColumns;
166 - (void)getMaxRows:(int*)rows columns:(int*)cols
168     if (rows) *rows = maxRows;
169     if (cols) *cols = maxColumns;
172 - (void)setMaxRows:(int)rows columns:(int)cols
174     // NOTE: Just remember the new values, the actual resizing is done lazily.
175     maxRows = rows;
176     maxColumns = cols;
179 - (void)setDefaultColorsBackground:(NSColor *)bgColor
180                         foreground:(NSColor *)fgColor
182     if (defaultBackgroundColor != bgColor) {
183         [defaultBackgroundColor release];
184         defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
185     }
187     if (defaultForegroundColor != fgColor) {
188         [defaultForegroundColor release];
189         defaultForegroundColor = fgColor ? [fgColor retain] : nil;
190     }
193 - (NSColor *)defaultBackgroundColor
195     return defaultBackgroundColor;
198 - (NSColor *)defaultForegroundColor
200     return defaultForegroundColor;
203 - (void)setTextContainerInset:(NSSize)size
205     insetSize = size;
208 - (NSRect)rectForRowsInRange:(NSRange)range
210     NSRect rect = { 0, 0, 0, 0 };
211     unsigned start = range.location > maxRows ? maxRows : range.location;
212     unsigned length = range.length;
214     if (start + length > maxRows)
215         length = maxRows - start;
217     rect.origin.y = cellSize.height * start + insetSize.height;
218     rect.size.height = cellSize.height * length;
220     return rect;
223 - (NSRect)rectForColumnsInRange:(NSRange)range
225     NSRect rect = { 0, 0, 0, 0 };
226     unsigned start = range.location > maxColumns ? maxColumns : range.location;
227     unsigned length = range.length;
229     if (start+length > maxColumns)
230         length = maxColumns - start;
232     rect.origin.x = cellSize.width * start + insetSize.width;
233     rect.size.width = cellSize.width * length;
235     return rect;
239 - (void)setFont:(NSFont *)newFont
241     if (newFont && font != newFont) {
242         [font release];
243         font = [newFont retain];
244         ascender = roundf([font ascender]);
246         float em = [@"m" sizeWithAttributes:
247                 [NSDictionary dictionaryWithObject:newFont
248                                             forKey:NSFontAttributeName]].width;
249         float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
250                 floatForKey:MMCellWidthMultiplierKey];
252         // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
253         // only render at integer sizes.  Hence, we restrict the cell width to
254         // an integer here, otherwise the window width and the actual text
255         // width will not match.
256         cellSize.width = ceilf(em * cellWidthMultiplier);
257         cellSize.height = linespace + defaultLineHeightForFont(newFont);
259         [self updateAtsuStyles];
260     }
263 - (void)setWideFont:(NSFont *)newFont
265     if (!newFont) {
266         if (font) [self setWideFont:font];
267     } else if (newFont != fontWide) {
268         float pointSize = [newFont pointSize];
269         NSFontDescriptor *desc = [newFont fontDescriptor];
270         NSDictionary *dictWide = [NSDictionary
271             dictionaryWithObject:[NSNumber numberWithFloat:2*cellSize.width]
272                           forKey:NSFontFixedAdvanceAttribute];
273         desc = [desc fontDescriptorByAddingAttributes:dictWide];
274         fontWide = [NSFont fontWithDescriptor:desc size:pointSize];
275         [fontWide retain];
276     }
279 - (NSFont *)font
281     return font;
284 - (NSFont *)fontWide
286     return fontWide;
289 - (NSSize)cellSize
291     return cellSize;
294 - (void)setLinespace:(float)newLinespace
296     linespace = newLinespace;
298     // NOTE: The linespace is added to the cell height in order for a multiline
299     // selection not to have white (background color) gaps between lines.  Also
300     // this simplifies the code a lot because there is no need to check the
301     // linespace when calculating the size of the text view etc.  When the
302     // linespace is non-zero the baseline will be adjusted as well; check
303     // MMTypesetter.
304     cellSize.height = linespace + defaultLineHeightForFont(font);
309 - (void)setShouldDrawInsertionPoint:(BOOL)on
313 - (void)setPreEditRow:(int)row column:(int)col
315     [helper setPreEditRow:row column:col];
318 - (void)setMouseShape:(int)shape
320     [helper setMouseShape:shape];
323 - (void)setAntialias:(BOOL)state
325     antialias = state;
328 - (void)setImControl:(BOOL)enable
330     [helper setImControl:enable];
333 - (void)activateIm:(BOOL)enable
335     [helper activateIm:enable];
338 - (BOOL)_wantsKeyDownForEvent:(id)event
340     // HACK! This is an undocumented method which is called from within
341     // -[NSWindow sendEvent] (and perhaps in other places as well) when the
342     // user presses e.g. Ctrl-Tab or Ctrl-Esc .  Returning YES here effectively
343     // disables the Cocoa "key view loop" (which is undesirable).  It may have
344     // other side-effects, but we really _do_ want to process all key down
345     // events so it seems safe to always return YES.
346     return YES;
349 - (void)keyDown:(NSEvent *)event
351     [helper keyDown:event];
354 - (void)insertText:(id)string
356     [helper insertText:string];
359 - (void)doCommandBySelector:(SEL)selector
361     [helper doCommandBySelector:selector];
364 - (BOOL)performKeyEquivalent:(NSEvent *)event
366     return [helper performKeyEquivalent:event];
369 - (BOOL)hasMarkedText
371     return [helper hasMarkedText];
374 - (NSRange)markedRange
376     return [helper markedRange];
379 - (NSDictionary *)markedTextAttributes
381     return [helper markedTextAttributes];
384 - (void)setMarkedTextAttributes:(NSDictionary *)attr
386     [helper setMarkedTextAttributes:attr];
389 - (void)setMarkedText:(id)text selectedRange:(NSRange)range
391     [helper setMarkedText:text selectedRange:range];
394 - (void)unmarkText
396     [helper unmarkText];
399 - (void)scrollWheel:(NSEvent *)event
401     [helper scrollWheel:event];
404 - (void)mouseDown:(NSEvent *)event
406     [helper mouseDown:event];
409 - (void)rightMouseDown:(NSEvent *)event
411     [helper mouseDown:event];
414 - (void)otherMouseDown:(NSEvent *)event
416     [helper mouseDown:event];
419 - (void)mouseUp:(NSEvent *)event
421     [helper mouseUp:event];
424 - (void)rightMouseUp:(NSEvent *)event
426     [helper mouseUp:event];
429 - (void)otherMouseUp:(NSEvent *)event
431     [helper mouseUp:event];
434 - (void)mouseDragged:(NSEvent *)event
436     [helper mouseDragged:event];
439 - (void)rightMouseDragged:(NSEvent *)event
441     [helper mouseDragged:event];
444 - (void)otherMouseDragged:(NSEvent *)event
446     [helper mouseDragged:event];
449 - (void)mouseMoved:(NSEvent *)event
451     [helper mouseMoved:event];
454 - (void)mouseEntered:(NSEvent *)event
456     [helper mouseEntered:event];
459 - (void)mouseExited:(NSEvent *)event
461     [helper mouseExited:event];
464 - (void)setFrame:(NSRect)frame
466     [super setFrame:frame];
467     [helper setFrame:frame];
470 - (void)viewDidMoveToWindow
472     [helper viewDidMoveToWindow];
475 - (void)viewWillMoveToWindow:(NSWindow *)newWindow
477     [helper viewWillMoveToWindow:newWindow];
480 - (NSMenu*)menuForEvent:(NSEvent *)event
482     // HACK! Return nil to disable default popup menus (Vim provides its own).
483     // Called when user Ctrl-clicks in the view (this is already handled in
484     // rightMouseDown:).
485     return nil;
488 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
490     return [helper performDragOperation:sender];
493 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
495     return [helper draggingEntered:sender];
498 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
500     return [helper draggingUpdated:sender];
505 - (BOOL)mouseDownCanMoveWindow
507     return NO;
510 - (BOOL)isOpaque
512     return YES;
515 - (BOOL)acceptsFirstResponder
517     return YES;
520 - (BOOL)isFlipped
522     return NO;
525 - (void)drawRect:(NSRect)rect
527     [defaultBackgroundColor set];
528     NSRectFill(rect);
530     NSPoint pt = { insetSize.width, insetSize.height };
531     [contentImage compositeToPoint:pt operation:NSCompositeCopy];
533 #ifdef INCLUDE_OLD_IM_CODE
534     if ([self hasMarkedText] && ![helper useInlineIm]) {
535         int len = [[helper markedText] length];
536         int rows = 0;
537         int cols = maxColumns - [helper preEditColumn];
538         NSFont *theFont = [[self markedTextAttributes]
539             valueForKey:NSFontAttributeName];
540         if (theFont == [self fontWide])
541             cols = cols / 2;
542         int done = 0;
543         int lend = cols > len ? len : cols;
544         NSAttributedString *aString = [[helper markedText]
545                 attributedSubstringFromRange:NSMakeRange(done, lend)];
546         NSPoint pt = [self pointForRow:[helper preEditRow]
547                                 column:[helper preEditColumn]];
548         [aString drawAtPoint:pt];
549         done = lend;
550         if (done != len) {
551             int r;
552             rows = (len - done) / (maxColumns / 2) + 1;
553             for (r = 1; r <= rows; r++) {
554             lend = len - done > maxColumns / 2
555                 ? maxColumns / 2 : len - done;
556                 aString = [[helper markedText] attributedSubstringFromRange:
557                         NSMakeRange(done, lend)];
558                 NSPoint pt = [self pointForRow:[helper preEditRow]+r
559                                         column:0];
560                 [aString drawAtPoint:pt];
561                 done += lend;
562             }
563         }
565         rows = maxRows - 1 - [helper preEditRow];
566         cols = [helper preEditColumn];
567         if (theFont == fontWide) {
568             cols += ([helper imRange].location+[helper imRange].length) * 2;
569             if (cols >= maxColumns - 1) {
570                 rows -= cols / maxColumns;
571                 cols = cols % 2 ? cols % maxColumns + 1 :
572                                   cols % maxColumns;
573             }
574         } else {
575             cols += ([helper imRange].location+[helper imRange].length);
576             if (cols >= maxColumns) {
577                 rows -= cols / maxColumns;
578                 cols = cols % 2 ? cols % maxColumns + 1 :
579                                   cols % maxColumns;
580             }
581         }
583         // TODO: Could IM be in "right-left" mode?  If so the insertion point
584         // will be on the wrong side.
585         [self drawInsertionPointAtRow:rows
586                                column:cols
587                                 shape:MMInsertionPointVertical
588                              fraction:25];
589     }
590 #endif // INCLUDE_OLD_IM_CODE
593 - (BOOL) wantsDefaultClipping
595     return NO;
599 #define MM_DEBUG_DRAWING 0
601 - (void)performBatchDrawWithData:(NSData *)data
603     const void *bytes = [data bytes];
604     const void *end = bytes + [data length];
606     if (! NSEqualSizes(imageSize, [self textAreaSize]))
607         [self resizeContentImage];
609 #if MM_DEBUG_DRAWING
610     ASLogDebug(@"====> BEGIN %s", _cmd);
611 #endif
612     [self beginDrawing];
614     // TODO: Sanity check input
616     while (bytes < end) {
617         int type = *((int*)bytes);  bytes += sizeof(int);
619         if (ClearAllDrawType == type) {
620 #if MM_DEBUG_DRAWING
621             ASLogDebug(@"   Clear all");
622 #endif
623             [self clearAll];
624         } else if (ClearBlockDrawType == type) {
625             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
626             int row1 = *((int*)bytes);  bytes += sizeof(int);
627             int col1 = *((int*)bytes);  bytes += sizeof(int);
628             int row2 = *((int*)bytes);  bytes += sizeof(int);
629             int col2 = *((int*)bytes);  bytes += sizeof(int);
631 #if MM_DEBUG_DRAWING
632             ASLogDebug(@"   Clear block (%d,%d) -> (%d,%d)", row1, col1,
633                        row2,col2);
634 #endif
635             [self clearBlockFromRow:row1 column:col1
636                     toRow:row2 column:col2
637                     color:[NSColor colorWithArgbInt:color]];
638         } else if (DeleteLinesDrawType == type) {
639             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
640             int row = *((int*)bytes);  bytes += sizeof(int);
641             int count = *((int*)bytes);  bytes += sizeof(int);
642             int bot = *((int*)bytes);  bytes += sizeof(int);
643             int left = *((int*)bytes);  bytes += sizeof(int);
644             int right = *((int*)bytes);  bytes += sizeof(int);
646 #if MM_DEBUG_DRAWING
647             ASLogDebug(@"   Delete %d line(s) from %d", count, row);
648 #endif
649             [self deleteLinesFromRow:row lineCount:count
650                     scrollBottom:bot left:left right:right
651                            color:[NSColor colorWithArgbInt:color]];
652         } else if (DrawStringDrawType == type) {
653             int bg = *((int*)bytes);  bytes += sizeof(int);
654             int fg = *((int*)bytes);  bytes += sizeof(int);
655             int sp = *((int*)bytes);  bytes += sizeof(int);
656             int row = *((int*)bytes);  bytes += sizeof(int);
657             int col = *((int*)bytes);  bytes += sizeof(int);
658             int cells = *((int*)bytes);  bytes += sizeof(int);
659             int flags = *((int*)bytes);  bytes += sizeof(int);
660             int len = *((int*)bytes);  bytes += sizeof(int);
661             // UniChar *string = (UniChar*)bytes;  bytes += len;
662             NSString *string = [[NSString alloc]
663                     initWithBytesNoCopy:(void*)bytes
664                                  length:len
665                                encoding:NSUTF8StringEncoding
666                            freeWhenDone:NO];
667             bytes += len;
668 #if MM_DEBUG_DRAWING
669             ASLogDebug(@"   Draw string at (%d,%d) length=%d flags=%d fg=0x%x "
670                        "bg=0x%x sp=0x%x", row, col, len, flags, fg, bg, sp);
671 #endif
672             unichar *characters = malloc(sizeof(unichar) * [string length]);
673             [string getCharacters:characters];
675             [self drawString:characters
676                              length:[string length]
677                               atRow:row
678                              column:col
679                               cells:cells withFlags:flags
680                     foregroundColor:[NSColor colorWithRgbInt:fg]
681                     backgroundColor:[NSColor colorWithArgbInt:bg]
682                        specialColor:[NSColor colorWithRgbInt:sp]];
683             free(characters);
684             [string release];
685         } else if (InsertLinesDrawType == type) {
686             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
687             int row = *((int*)bytes);  bytes += sizeof(int);
688             int count = *((int*)bytes);  bytes += sizeof(int);
689             int bot = *((int*)bytes);  bytes += sizeof(int);
690             int left = *((int*)bytes);  bytes += sizeof(int);
691             int right = *((int*)bytes);  bytes += sizeof(int);
693 #if MM_DEBUG_DRAWING
694             ASLogDebug(@"   Insert %d line(s) at row %d", count, row);
695 #endif
696             [self insertLinesAtRow:row lineCount:count
697                              scrollBottom:bot left:left right:right
698                                     color:[NSColor colorWithArgbInt:color]];
699         } else if (DrawCursorDrawType == type) {
700             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
701             int row = *((int*)bytes);  bytes += sizeof(int);
702             int col = *((int*)bytes);  bytes += sizeof(int);
703             int shape = *((int*)bytes);  bytes += sizeof(int);
704             int percent = *((int*)bytes);  bytes += sizeof(int);
706 #if MM_DEBUG_DRAWING
707             ASLogDebug(@"   Draw cursor at (%d,%d)", row, col);
708 #endif
709             [helper setInsertionPointColor:[NSColor colorWithRgbInt:color]];
710             [self drawInsertionPointAtRow:row column:col shape:shape
711                                  fraction:percent];
712         } else if (DrawInvertedRectDrawType == type) {
713             int row = *((int*)bytes);  bytes += sizeof(int);
714             int col = *((int*)bytes);  bytes += sizeof(int);
715             int nr = *((int*)bytes);  bytes += sizeof(int);
716             int nc = *((int*)bytes);  bytes += sizeof(int);
717             /*int invert = *((int*)bytes);*/  bytes += sizeof(int);
719 #if MM_DEBUG_DRAWING
720             ASLogDebug(@"   Draw inverted rect: row=%d col=%d nrows=%d "
721                        "ncols=%d", row, col, nr, nc);
722 #endif
723             [self drawInvertedRectAtRow:row column:col numRows:nr
724                              numColumns:nc];
725         } else if (SetCursorPosDrawType == type) {
726             // TODO: This is used for Voice Over support in MMTextView,
727             // MMAtsuiTextView currently does not support Voice Over.
728             /*cursorRow = *((int*)bytes);*/  bytes += sizeof(int);
729             /*cursorCol = *((int*)bytes);*/  bytes += sizeof(int);
730         } else {
731             ASLogWarn(@"Unknown draw type (type=%d)", type);
732         }
733     }
735     [self endDrawing];
737     [self setNeedsDisplay:YES];
739     // NOTE: During resizing, Cocoa only sends draw messages before Vim's rows
740     // and columns are changed (due to ipc delays). Force a redraw here.
741     if ([self inLiveResize])
742         [self display];
744 #if MM_DEBUG_DRAWING
745     ASLogDebug(@"<==== END   %s", _cmd);
746 #endif
749 - (NSSize)constrainRows:(int *)rows columns:(int *)cols toSize:(NSSize)size
751     // TODO:
752     // - Rounding errors may cause size change when there should be none
753     // - Desired rows/columns shold not be 'too small'
755     // Constrain the desired size to the given size.  Values for the minimum
756     // rows and columns are taken from Vim.
757     NSSize desiredSize = [self desiredSize];
758     int desiredRows = maxRows;
759     int desiredCols = maxColumns;
761     if (size.height != desiredSize.height) {
762         float fh = cellSize.height;
763         float ih = 2 * insetSize.height;
764         if (fh < 1.0f) fh = 1.0f;
766         desiredRows = floor((size.height - ih)/fh);
767         desiredSize.height = fh*desiredRows + ih;
768     }
770     if (size.width != desiredSize.width) {
771         float fw = cellSize.width;
772         float iw = 2 * insetSize.width;
773         if (fw < 1.0f) fw = 1.0f;
775         desiredCols = floor((size.width - iw)/fw);
776         desiredSize.width = fw*desiredCols + iw;
777     }
779     if (rows) *rows = desiredRows;
780     if (cols) *cols = desiredCols;
782     return desiredSize;
785 - (NSSize)desiredSize
787     // Compute the size the text view should be for the entire text area and
788     // inset area to be visible with the present number of rows and columns.
789     return NSMakeSize(maxColumns * cellSize.width + 2 * insetSize.width,
790                       maxRows * cellSize.height + 2 * insetSize.height);
793 - (NSSize)minSize
795     // Compute the smallest size the text view is allowed to be.
796     return NSMakeSize(MMMinColumns * cellSize.width + 2 * insetSize.width,
797                       MMMinRows * cellSize.height + 2 * insetSize.height);
800 - (void)changeFont:(id)sender
802     [helper changeFont:sender];
807 // NOTE: The menu items cut/copy/paste/undo/redo/select all/... must be bound
808 // to the same actions as in IB otherwise they will not work with dialogs.  All
809 // we do here is forward these actions to the Vim process.
811 - (IBAction)cut:(id)sender
813     [[self windowController] vimMenuItemAction:sender];
816 - (IBAction)copy:(id)sender
818     [[self windowController] vimMenuItemAction:sender];
821 - (IBAction)paste:(id)sender
823     [[self windowController] vimMenuItemAction:sender];
826 - (IBAction)undo:(id)sender
828     [[self windowController] vimMenuItemAction:sender];
831 - (IBAction)redo:(id)sender
833     [[self windowController] vimMenuItemAction:sender];
836 - (IBAction)selectAll:(id)sender
838     [[self windowController] vimMenuItemAction:sender];
841 - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column
843     // View is not flipped, instead the atsui code draws to a flipped image;
844     // thus we need to 'flip' the coordinate here since the column number
845     // increases in an up-to-down order.
846     point.y = [self frame].size.height - point.y;
848     NSPoint origin = { insetSize.width, insetSize.height };
850     if (!(cellSize.width > 0 && cellSize.height > 0))
851         return NO;
853     if (row) *row = floor((point.y-origin.y-1) / cellSize.height);
854     if (column) *column = floor((point.x-origin.x-1) / cellSize.width);
856     return YES;
859 - (NSPoint)pointForRow:(int)row column:(int)col
861     // Return the lower left coordinate of the cell at (row,column).
862     NSPoint pt;
864     pt.x = insetSize.width + col*cellSize.width;
865     pt.y = [self frame].size.height -
866            (insetSize.height + (1+row)*cellSize.height);
868     return pt;
871 - (NSRect)rectForRow:(int)row column:(int)col numRows:(int)nr
872           numColumns:(int)nc
874     // Return the rect for the block which covers the specified rows and
875     // columns.  The lower-left corner is the origin of this rect.
876     NSRect rect;
878     rect.origin.x = insetSize.width + col*cellSize.width;
879     rect.origin.y = [self frame].size.height -
880                     (insetSize.height + (nr+row)*cellSize.height);
881     rect.size.width = nc*cellSize.width;
882     rect.size.height = nr*cellSize.height;
884     return rect;
887 - (NSArray *)validAttributesForMarkedText
889     return nil;
892 - (NSAttributedString *)attributedSubstringFromRange:(NSRange)range
894     return nil;
897 - (NSUInteger)characterIndexForPoint:(NSPoint)point
899     return NSNotFound;
902 // The return type of this message changed with OS X 10.5 so we need this
903 // kludge in order to avoid compiler warnings on OS X 10.4.
904 #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
905 - (NSInteger)conversationIdentifier
907     return (NSInteger)self;
909 #else
910 - (long)conversationIdentifier
912     return (long)self;
914 #endif
916 - (NSRange)selectedRange
918     return [helper imRange];
921 - (NSRect)firstRectForCharacterRange:(NSRange)range
923     return [helper firstRectForCharacterRange:range];
926 @end // MMAtsuiTextView
931 @implementation MMAtsuiTextView (Private)
933 - (void)initAtsuStyles
935     int i;
936     for (i = 0; i < MMMaxCellsPerChar; i++)
937         ATSUCreateStyle(&atsuStyles[i]);
940 - (void)disposeAtsuStyles
942     int i;
944     for (i = 0; i < MMMaxCellsPerChar; i++)
945         if (atsuStyles[i] != NULL)
946         {
947             if (ATSUDisposeStyle(atsuStyles[i]) != noErr)
948                 atsuStyles[i] = NULL;
949         }
952 - (void)updateAtsuStyles
954     ATSUFontID        fontID;
955     Fixed             fontSize;
956     Fixed             fontWidth;
957     int               i;
958     CGAffineTransform transform = CGAffineTransformMakeScale(1, -1);
959     ATSStyleRenderingOptions options;
961     fontID    = [font _atsFontID];
962     fontSize  = Long2Fix([font pointSize]);
963     options   = kATSStyleApplyAntiAliasing;
965     ATSUAttributeTag attribTags[] =
966     {
967         kATSUFontTag, kATSUSizeTag, kATSUImposeWidthTag,
968         kATSUFontMatrixTag, kATSUStyleRenderingOptionsTag,
969         kATSUMaxATSUITagValue + 1
970     };
972     ByteCount attribSizes[] =
973     {
974         sizeof(ATSUFontID), sizeof(Fixed), sizeof(fontWidth),
975         sizeof(CGAffineTransform), sizeof(ATSStyleRenderingOptions),
976         sizeof(font)
977     };
979     ATSUAttributeValuePtr attribValues[] =
980     {
981         &fontID, &fontSize, &fontWidth, &transform, &options, &font
982     };
984     ATSUFontFeatureType featureTypes[] = {
985         kLigaturesType, kLigaturesType
986     };
988     ATSUFontFeatureSelector featureSelectors[] = {
989         kCommonLigaturesOffSelector, kRareLigaturesOffSelector
990     };
992     for (i = 0; i < MMMaxCellsPerChar; i++)
993     {
994         fontWidth = Long2Fix(cellSize.width * (i + 1));
996         if (ATSUSetAttributes(atsuStyles[i],
997                               (sizeof attribTags) / sizeof(ATSUAttributeTag),
998                               attribTags, attribSizes, attribValues) != noErr)
999         {
1000             ATSUDisposeStyle(atsuStyles[i]);
1001             atsuStyles[i] = NULL;
1002         }
1004         // Turn off ligatures by default
1005         ATSUSetFontFeatures(atsuStyles[i],
1006                             sizeof(featureTypes) / sizeof(featureTypes[0]),
1007                             featureTypes, featureSelectors);
1008     }
1011 - (MMWindowController *)windowController
1013     id windowController = [[self window] windowController];
1014     if ([windowController isKindOfClass:[MMWindowController class]])
1015         return (MMWindowController*)windowController;
1016     return nil;
1019 - (MMVimController *)vimController
1021     return [[self windowController] vimController];
1024 @end // MMAtsuiTextView (Private)
1029 @implementation MMAtsuiTextView (Drawing)
1031 - (NSPoint)originForRow:(int)row column:(int)col
1033     return NSMakePoint(col * cellSize.width, row * cellSize.height);
1036 - (NSRect)rectFromRow:(int)row1 column:(int)col1
1037                 toRow:(int)row2 column:(int)col2
1039     NSPoint origin = [self originForRow:row1 column:col1];
1040     return NSMakeRect(origin.x, origin.y,
1041                       (col2 + 1 - col1) * cellSize.width,
1042                       (row2 + 1 - row1) * cellSize.height);
1045 - (NSSize)textAreaSize
1047     // Calculate the (desired) size of the text area, i.e. the text view area
1048     // minus the inset area.
1049     return NSMakeSize(maxColumns * cellSize.width, maxRows * cellSize.height);
1052 - (void)resizeContentImage
1054     [contentImage release];
1055     contentImage = [[NSImage alloc] initWithSize:[self textAreaSize]];
1056     [contentImage setFlipped:YES];
1057     imageSize = [self textAreaSize];
1060 - (void)beginDrawing
1062     [contentImage lockFocus];
1065 - (void)endDrawing
1067     [contentImage unlockFocus];
1070 #define atsu_style_set_bool(s, t, b) \
1071     ATSUSetAttributes(s, 1, &t, &(sizeof(Boolean)), &&b);
1072 #define FILL_Y(y)    (y * cellSize.height)
1074 - (void)drawString:(UniChar *)string length:(UniCharCount)length
1075              atRow:(int)row column:(int)col cells:(int)cells
1076          withFlags:(int)flags foregroundColor:(NSColor *)fg
1077    backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp
1079     // 'string' consists of 'length' utf-16 code pairs and should cover 'cells'
1080     // display cells (a normal character takes up one display cell, a wide
1081     // character takes up two)
1082     ATSUStyle       style = (flags & DRAW_WIDE) ? atsuStyles[1] : atsuStyles[0];
1083     ATSUTextLayout  layout;
1085     // Font selection and rendering options for ATSUI
1086     ATSUAttributeTag      attribTags[3] = { kATSUQDBoldfaceTag,
1087                                             kATSUFontMatrixTag,
1088                                             kATSUStyleRenderingOptionsTag };
1090     ByteCount             attribSizes[] = { sizeof(Boolean),
1091                                             sizeof(CGAffineTransform),
1092                                             sizeof(UInt32) };
1093     Boolean               useBold;
1094     CGAffineTransform     theTransform = CGAffineTransformMakeScale(1.0, -1.0);
1095     UInt32                useAntialias;
1097     ATSUAttributeValuePtr attribValues[3] = { &useBold, &theTransform,
1098                                               &useAntialias };
1100     useBold      = (flags & DRAW_BOLD) ? true : false;
1102     if (flags & DRAW_ITALIC)
1103         theTransform.c = Fix2X(kATSItalicQDSkew);
1105     useAntialias = antialias ? kATSStyleApplyAntiAliasing
1106                              : kATSStyleNoAntiAliasing;
1108     ATSUSetAttributes(style, sizeof(attribValues) / sizeof(attribValues[0]),
1109                       attribTags, attribSizes, attribValues);
1111     ATSUCreateTextLayout(&layout);
1112     ATSUSetTextPointerLocation(layout, string,
1113                                kATSUFromTextBeginning, kATSUToTextEnd,
1114                                length);
1115     ATSUSetRunStyle(layout, style, kATSUFromTextBeginning, kATSUToTextEnd);
1117     NSRect rect = NSMakeRect(col * cellSize.width, row * cellSize.height,
1118                              length * cellSize.width, cellSize.height);
1119     if (flags & DRAW_WIDE)
1120         rect.size.width = rect.size.width * 2;
1121     CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
1123     // Clip drawing to avoid text bleeding into adjacent display cells when
1124     // antialiasing is enabled.
1125     CGContextSaveGState(context);
1126     CGContextClipToRect(context, *(CGRect*)&rect);
1128     ATSUAttributeTag tags[] = { kATSUCGContextTag };
1129     ByteCount sizes[] = { sizeof(CGContextRef) };
1130     ATSUAttributeValuePtr values[] = { &context };
1131     ATSUSetLayoutControls(layout, 1, tags, sizes, values);
1133     if (! (flags & DRAW_TRANSP))
1134     {
1135         [bg set];
1136         NSRectFill(rect);
1137     }
1139     [fg set];
1141     ATSUSetTransientFontMatching(layout, TRUE);
1142     ATSUDrawText(layout,
1143                  kATSUFromTextBeginning,
1144                  kATSUToTextEnd,
1145                  X2Fix(rect.origin.x),
1146                  X2Fix(rect.origin.y + ascender));
1147     ATSUDisposeTextLayout(layout);
1149     if (flags & DRAW_UNDERL)
1150     {
1151         [sp set];
1152         NSRectFill(NSMakeRect(rect.origin.x,
1153                               (row + 1) * cellSize.height + kUnderlineOffset,
1154                               rect.size.width, kUnderlineHeight));
1155     }
1157     if (flags & DRAW_UNDERC)
1158     {
1159         [sp set];
1161         float line_end_x = rect.origin.x + rect.size.width;
1162         int i = 0;
1163         NSRect line_rect = NSMakeRect(
1164                 rect.origin.x,
1165                 (row + 1) * cellSize.height + kUndercurlOffset,
1166                 kUndercurlDotWidth, kUndercurlHeight);
1168         while (line_rect.origin.x < line_end_x)
1169         {
1170             if (i % 2)
1171                 NSRectFill(line_rect);
1173             line_rect.origin.x += kUndercurlDotDistance;
1174             i++;
1175         }
1176     }
1178     CGContextRestoreGState(context);
1181 - (void)scrollRect:(NSRect)rect lineCount:(int)count
1183     NSPoint destPoint = rect.origin;
1184     destPoint.y += count * cellSize.height;
1186     NSCopyBits(0, rect, destPoint);
1189 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
1190               scrollBottom:(int)bottom left:(int)left right:(int)right
1191                      color:(NSColor *)color
1193     NSRect rect = [self rectFromRow:row + count
1194                              column:left
1195                               toRow:bottom
1196                              column:right];
1197     [color set];
1198     // move rect up for count lines
1199     [self scrollRect:rect lineCount:-count];
1200     [self clearBlockFromRow:bottom - count + 1
1201                      column:left
1202                       toRow:bottom
1203                      column:right
1204                       color:color];
1207 - (void)insertLinesAtRow:(int)row lineCount:(int)count
1208             scrollBottom:(int)bottom left:(int)left right:(int)right
1209                    color:(NSColor *)color
1211     NSRect rect = [self rectFromRow:row
1212                              column:left
1213                               toRow:bottom - count
1214                              column:right];
1215     [color set];
1216     // move rect down for count lines
1217     [self scrollRect:rect lineCount:count];
1218     [self clearBlockFromRow:row
1219                      column:left
1220                       toRow:row + count - 1
1221                      column:right
1222                       color:color];
1225 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
1226                    column:(int)col2 color:(NSColor *)color
1228     [color set];
1229     NSRectFill([self rectFromRow:row1 column:col1 toRow:row2 column:col2]);
1232 - (void)clearAll
1234     [defaultBackgroundColor set];
1235     NSRectFill(NSMakeRect(0, 0, imageSize.width, imageSize.height));
1238 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
1239                        fraction:(int)percent
1241     NSPoint origin = [self originForRow:row column:col];
1242     NSRect rect = NSMakeRect(origin.x, origin.y,
1243                              cellSize.width, cellSize.height);
1245     if (MMInsertionPointHorizontal == shape) {
1246         int frac = (cellSize.height * percent + 99)/100;
1247         rect.origin.y += rect.size.height - frac;
1248         rect.size.height = frac;
1249     } else if (MMInsertionPointVertical == shape) {
1250         int frac = (cellSize.width * percent + 99)/100;
1251         rect.size.width = frac;
1252     } else if (MMInsertionPointVerticalRight == shape) {
1253         int frac = (cellSize.width * percent + 99)/100;
1254         rect.origin.x += rect.size.width - frac;
1255         rect.size.width = frac;
1256     }
1258     [[helper insertionPointColor] set];
1259     if (MMInsertionPointHollow == shape) {
1260         NSFrameRect(rect);
1261     } else {
1262         NSRectFill(rect);
1263     }
1266 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nrows
1267                    numColumns:(int)ncols
1269     // TODO: THIS CODE HAS NOT BEEN TESTED!
1270     CGContextRef cgctx = [[NSGraphicsContext currentContext] graphicsPort];
1271     CGContextSaveGState(cgctx);
1272     CGContextSetBlendMode(cgctx, kCGBlendModeDifference);
1273     CGContextSetRGBFillColor(cgctx, 1.0, 1.0, 1.0, 1.0);
1275     CGRect rect = { col * cellSize.width, row * cellSize.height,
1276                     ncols * cellSize.width, nrows * cellSize.height };
1277     CGContextFillRect(cgctx, rect);
1279     CGContextRestoreGState(cgctx);
1282 @end // MMAtsuiTextView (Drawing)
1284 #endif // MM_ENABLE_ATSUI