Turn off ligatures by default
[MacVim.git] / src / MacVim / MMAtsuiTextView.m
blobdb5520976ec209ad53d4168fdf641a845017aed3
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 "MMVimController.h"
31 #import "MMWindowController.h"
32 #import "Miscellaneous.h"
35 // TODO: What does DRAW_TRANSP flag do?  If the background isn't drawn when
36 // this flag is set, then sometimes the character after the cursor becomes
37 // blank.  Everything seems to work fine by just ignoring this flag.
38 #define DRAW_TRANSP               0x01    /* draw with transparant bg */
39 #define DRAW_BOLD                 0x02    /* draw bold text */
40 #define DRAW_UNDERL               0x04    /* draw underline text */
41 #define DRAW_UNDERC               0x08    /* draw undercurl text */
42 #define DRAW_ITALIC               0x10    /* draw italic text */
43 #define DRAW_CURSOR               0x20
46 static char MMKeypadEnter[2] = { 'K', 'A' };
47 static NSString *MMKeypadEnterString = @"KA";
49 enum {
50     // These values are chosen so that the min size is not too small with the
51     // default font (they only affect resizing with the mouse, you can still
52     // use e.g. ":set lines=2" to go below these values).
53     MMMinRows = 4,
54     MMMinColumns = 30
58 @interface NSFont (AppKitPrivate)
59 - (ATSUFontID) _atsFontID;
60 @end
63 @interface MMAtsuiTextView (Private)
64 - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column;
65 - (void)initAtsuStyles;
66 - (void)disposeAtsuStyles;
67 - (void)updateAtsuStyles;
68 - (void)dispatchKeyEvent:(NSEvent *)event;
69 - (void)sendKeyDown:(const char *)chars length:(int)len modifiers:(int)flags;
70 - (void)hideMouseCursor;
71 - (MMWindowController *)windowController;
72 - (MMVimController *)vimController;
73 @end
76 @interface MMAtsuiTextView (Drawing)
77 - (NSPoint)originForRow:(int)row column:(int)column;
78 - (NSRect)rectFromRow:(int)row1 column:(int)col1
79                 toRow:(int)row2 column:(int)col2;
80 - (NSSize)textAreaSize;
81 - (void)resizeContentImage;
82 - (void)beginDrawing;
83 - (void)endDrawing;
84 - (void)drawString:(UniChar *)string length:(UniCharCount)length
85              atRow:(int)row column:(int)col cells:(int)cells
86          withFlags:(int)flags foregroundColor:(NSColor *)fg
87    backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp;
88 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
89               scrollBottom:(int)bottom left:(int)left right:(int)right
90                      color:(NSColor *)color;
91 - (void)insertLinesAtRow:(int)row lineCount:(int)count
92             scrollBottom:(int)bottom left:(int)left right:(int)right
93                    color:(NSColor *)color;
94 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
95                    column:(int)col2 color:(NSColor *)color;
96 - (void)clearAll;
97 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
98                        fraction:(int)percent color:(NSColor *)color;
99 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nrows
100                    numColumns:(int)ncols;
101 @end
104 @implementation MMAtsuiTextView
106 - (id)initWithFrame:(NSRect)frame
108     if ((self = [super initWithFrame:frame])) {
109         // NOTE!  It does not matter which font is set here, Vim will set its
110         // own font on startup anyway.  Just set some bogus values.
111         font = [[NSFont userFixedPitchFontOfSize:0] retain];
112         cellSize.width = cellSize.height = 1;
113         contentImage = nil;
114         imageSize = NSZeroSize;
115         insetSize = NSZeroSize;
117         // NOTE: If the default changes to 'NO' then the intialization of
118         // p_antialias in option.c must change as well.
119         antialias = YES;
121         [self initAtsuStyles];
122     }
124     return self;
127 - (void)dealloc
129     [self disposeAtsuStyles];
130     [font release];  font = nil;
131     [defaultBackgroundColor release];  defaultBackgroundColor = nil;
132     [defaultForegroundColor release];  defaultForegroundColor = nil;
134     [super dealloc];
137 - (void)getMaxRows:(int*)rows columns:(int*)cols
139     if (rows) *rows = maxRows;
140     if (cols) *cols = maxColumns;
143 - (void)setMaxRows:(int)rows columns:(int)cols
145     // NOTE: Just remember the new values, the actual resizing is done lazily.
146     maxRows = rows;
147     maxColumns = cols;
150 - (void)setDefaultColorsBackground:(NSColor *)bgColor
151                         foreground:(NSColor *)fgColor
153     if (defaultBackgroundColor != bgColor) {
154         [defaultBackgroundColor release];
155         defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
156     }
158     // NOTE: The default foreground color isn't actually used for anything, but
159     // other class instances might want to be able to access it so it is stored
160     // here.
161     if (defaultForegroundColor != fgColor) {
162         [defaultForegroundColor release];
163         defaultForegroundColor = fgColor ? [fgColor retain] : nil;
164     }
167 - (void)setTextContainerInset:(NSSize)size
169     insetSize = size;
172 - (NSRect)rectForRowsInRange:(NSRange)range
174     NSRect rect = { 0, 0, 0, 0 };
175     unsigned start = range.location > maxRows ? maxRows : range.location;
176     unsigned length = range.length;
178     if (start + length > maxRows)
179         length = maxRows - start;
181     rect.origin.y = cellSize.height * start + insetSize.height;
182     rect.size.height = cellSize.height * length;
184     return rect;
187 - (NSRect)rectForColumnsInRange:(NSRange)range
189     NSRect rect = { 0, 0, 0, 0 };
190     unsigned start = range.location > maxColumns ? maxColumns : range.location;
191     unsigned length = range.length;
193     if (start+length > maxColumns)
194         length = maxColumns - start;
196     rect.origin.x = cellSize.width * start + insetSize.width;
197     rect.size.width = cellSize.width * length;
199     return rect;
203 - (void)setFont:(NSFont *)newFont
205     if (newFont && font != newFont) {
206         [font release];
207         font = [newFont retain];
209         float em = [newFont widthOfString:@"m"];
210         float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
211                 floatForKey:MMCellWidthMultiplierKey];
213         // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
214         // only render at integer sizes.  Hence, we restrict the cell width to
215         // an integer here, otherwise the window width and the actual text
216         // width will not match.
217         cellSize.width = ceilf(em * cellWidthMultiplier);
218         cellSize.height = linespace + [newFont defaultLineHeightForFont];
220         [self updateAtsuStyles];
221     }
224 - (void)setWideFont:(NSFont *)newFont
228 - (NSFont *)font
230     return font;
233 - (NSSize)cellSize
235     return cellSize;
238 - (void)setLinespace:(float)newLinespace
240     linespace = newLinespace;
242     // NOTE: The linespace is added to the cell height in order for a multiline
243     // selection not to have white (background color) gaps between lines.  Also
244     // this simplifies the code a lot because there is no need to check the
245     // linespace when calculating the size of the text view etc.  When the
246     // linespace is non-zero the baseline will be adjusted as well; check
247     // MMTypesetter.
248     cellSize.height = linespace + [font defaultLineHeightForFont];
254 - (void)setShouldDrawInsertionPoint:(BOOL)on
258 - (void)setPreEditRow:(int)row column:(int)col
262 - (void)hideMarkedTextField
266 - (void)setMouseShape:(int)shape
270 - (void)setAntialias:(BOOL)state
272     antialias = state;
278 - (void)keyDown:(NSEvent *)event
280     //NSLog(@"%s %@", _cmd, event);
281     // HACK! If control modifier is held, don't pass the event along to
282     // interpretKeyEvents: since some keys are bound to multiple commands which
283     // means doCommandBySelector: is called several times.  Do the same for
284     // Alt+Function key presses (Alt+Up and Alt+Down are bound to two
285     // commands).  This hack may break input management, but unless we can
286     // figure out a way to disable key bindings there seems little else to do.
287     //
288     // TODO: Figure out a way to disable Cocoa key bindings entirely, without
289     // affecting input management.
290     int flags = [event modifierFlags];
291     if ((flags & NSControlKeyMask) ||
292             ((flags & NSAlternateKeyMask) && (flags & NSFunctionKeyMask))) {
293         NSString *unmod = [event charactersIgnoringModifiers];
294         if ([unmod length] == 1 && [unmod characterAtIndex:0] <= 0x7f
295                                 && [unmod characterAtIndex:0] >= 0x60) {
296             // HACK! Send Ctrl-letter keys (and C-@, C-[, C-\, C-], C-^, C-_)
297             // as normal text to be added to the Vim input buffer.  This must
298             // be done in order for the backend to be able to separate e.g.
299             // Ctrl-i and Ctrl-tab.
300             [self insertText:[event characters]];
301         } else {
302             [self dispatchKeyEvent:event];
303         }
304     } else {
305         [self interpretKeyEvents:[NSArray arrayWithObject:event]];
306     }
309 - (void)insertText:(id)string
311     //NSLog(@"%s %@", _cmd, string);
312     // NOTE!  This method is called for normal key presses but also for
313     // Option-key presses --- even when Ctrl is held as well as Option.  When
314     // Ctrl is held, the AppKit translates the character to a Ctrl+key stroke,
315     // so 'string' need not be a printable character!  In this case it still
316     // works to pass 'string' on to Vim as a printable character (since
317     // modifiers are already included and should not be added to the input
318     // buffer using CSI, K_MODIFIER).
320     [self hideMarkedTextField];
322     NSEvent *event = [NSApp currentEvent];
324     // HACK!  In order to be able to bind to <S-Space>, <S-M-Tab>, etc. we have
325     // to watch for them here.
326     if ([event type] == NSKeyDown
327             && [[event charactersIgnoringModifiers] length] > 0
328             && [event modifierFlags]
329                 & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask)) {
330         unichar c = [[event charactersIgnoringModifiers] characterAtIndex:0];
332         // <S-M-Tab> translates to 0x19 
333         if (' ' == c || 0x19 == c) {
334             [self dispatchKeyEvent:event];
335             return;
336         }
337     }
339     [self hideMouseCursor];
341     // NOTE: 'string' is either an NSString or an NSAttributedString.  Since we
342     // do not support attributes, simply pass the corresponding NSString in the
343     // latter case.
344     if ([string isKindOfClass:[NSAttributedString class]])
345         string = [string string];
347     //NSLog(@"send InsertTextMsgID: %@", string);
349     [[self vimController] sendMessage:InsertTextMsgID
350                  data:[string dataUsingEncoding:NSUTF8StringEncoding]];
353 - (void)doCommandBySelector:(SEL)selector
355     //NSLog(@"%s %@", _cmd, NSStringFromSelector(selector));
356     // By ignoring the selector we effectively disable the key binding
357     // mechanism of Cocoa.  Hopefully this is what the user will expect
358     // (pressing Ctrl+P would otherwise result in moveUp: instead of previous
359     // match, etc.).
360     //
361     // We usually end up here if the user pressed Ctrl+key (but not
362     // Ctrl+Option+key).
364     NSEvent *event = [NSApp currentEvent];
366     if (selector == @selector(cancelOperation:)
367             || selector == @selector(insertNewline:)) {
368         // HACK! If there was marked text which got abandoned as a result of
369         // hitting escape or enter, then 'insertText:' is called with the
370         // abandoned text but '[event characters]' includes the abandoned text
371         // as well.  Since 'dispatchKeyEvent:' looks at '[event characters]' we
372         // must intercept these keys here or the abandonded text gets inserted
373         // twice.
374         NSString *key = [event charactersIgnoringModifiers];
375         const char *chars = [key UTF8String];
376         int len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
378         if (0x3 == chars[0]) {
379             // HACK! AppKit turns enter (not return) into Ctrl-C, so we need to
380             // handle it separately (else Ctrl-C doesn't work).
381             len = sizeof(MMKeypadEnter)/sizeof(MMKeypadEnter[0]);
382             chars = MMKeypadEnter;
383         }
385         [self sendKeyDown:chars length:len modifiers:[event modifierFlags]];
386     } else {
387         [self dispatchKeyEvent:event];
388     }
391 - (BOOL)performKeyEquivalent:(NSEvent *)event
393     //NSLog(@"%s %@", _cmd, event);
394     // Called for Cmd+key keystrokes, function keys, arrow keys, page
395     // up/down, home, end.
396     //
397     // NOTE: This message cannot be ignored since Cmd+letter keys never are
398     // passed to keyDown:.  It seems as if the main menu consumes Cmd-key
399     // strokes, unless the key is a function key.
401     // NOTE: If the event that triggered this method represents a function key
402     // down then we do nothing, otherwise the input method never gets the key
403     // stroke (some input methods use e.g. arrow keys).  The function key down
404     // event will still reach Vim though (via keyDown:).  The exceptions to
405     // this rule are: PageUp/PageDown (keycode 116/121).
406     int flags = [event modifierFlags];
407     if ([event type] != NSKeyDown || flags & NSFunctionKeyMask
408             && !(116 == [event keyCode] || 121 == [event keyCode]))
409         return NO;
411     // HACK!  KeyCode 50 represent the key which switches between windows
412     // within an application (like Cmd+Tab is used to switch between
413     // applications).  Return NO here, else the window switching does not work.
414     if ([event keyCode] == 50)
415         return NO;
417     // HACK!  Let the main menu try to handle any key down event, before
418     // passing it on to vim, otherwise key equivalents for menus will
419     // effectively be disabled.
420     if ([[NSApp mainMenu] performKeyEquivalent:event])
421         return YES;
423     // HACK!  On Leopard Ctrl-key events end up here instead of keyDown:.
424     if (flags & NSControlKeyMask) {
425         [self keyDown:event];
426         return YES;
427     }
429     // HACK!  Don't handle Cmd-? or the "Help" menu does not work on Leopard.
430     NSString *unmodchars = [event charactersIgnoringModifiers];
431     if ([unmodchars isEqual:@"?"])
432         return NO;
434     //NSLog(@"%s%@", _cmd, event);
436     NSString *chars = [event characters];
437     int len = [unmodchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
438     NSMutableData *data = [NSMutableData data];
440     if (len <= 0)
441         return NO;
443     // If 'chars' and 'unmodchars' differs when shift flag is present, then we
444     // can clear the shift flag as it is already included in 'unmodchars'.
445     // Failing to clear the shift flag means <D-Bar> turns into <S-D-Bar> (on
446     // an English keyboard).
447     if (flags & NSShiftKeyMask && ![chars isEqual:unmodchars])
448         flags &= ~NSShiftKeyMask;
450     if (0x3 == [unmodchars characterAtIndex:0]) {
451         // HACK! AppKit turns enter (not return) into Ctrl-C, so we need to
452         // handle it separately (else Cmd-enter turns into Ctrl-C).
453         unmodchars = MMKeypadEnterString;
454         len = [unmodchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
455     }
457     [data appendBytes:&flags length:sizeof(int)];
458     [data appendBytes:&len length:sizeof(int)];
459     [data appendBytes:[unmodchars UTF8String] length:len];
461     [[self vimController] sendMessage:CmdKeyMsgID data:data];
463     return YES;
469 - (BOOL)acceptsFirstResponder
471     return YES;
474 - (BOOL)isFlipped
476     return NO;
479 - (void)drawRect:(NSRect)rect
481     NSRect srcRect = NSMakeRect(0, 0, imageSize.width, imageSize.height);
482     NSRect dstRect = srcRect;
484     dstRect.origin.x += insetSize.width;
485     dstRect.origin.y += insetSize.height;
487     [contentImage drawInRect: dstRect
488                     fromRect: srcRect
489                    operation: NSCompositeCopy
490                     fraction: 1.0];
493 - (BOOL) wantsDefaultClipping
495     return NO;
499 #define MM_DEBUG_DRAWING 0
501 - (void)performBatchDrawWithData:(NSData *)data
503     const void *bytes = [data bytes];
504     const void *end = bytes + [data length];
506     if (! NSEqualSizes(imageSize, [self textAreaSize]))
507         [self resizeContentImage];
509 #if MM_DEBUG_DRAWING
510     NSLog(@"====> BEGIN %s", _cmd);
511 #endif
512     [self beginDrawing];
514     // TODO: Sanity check input
516     while (bytes < end) {
517         int type = *((int*)bytes);  bytes += sizeof(int);
519         if (ClearAllDrawType == type) {
520 #if MM_DEBUG_DRAWING
521             NSLog(@"   Clear all");
522 #endif
523             [self clearAll];
524         } else if (ClearBlockDrawType == type) {
525             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
526             int row1 = *((int*)bytes);  bytes += sizeof(int);
527             int col1 = *((int*)bytes);  bytes += sizeof(int);
528             int row2 = *((int*)bytes);  bytes += sizeof(int);
529             int col2 = *((int*)bytes);  bytes += sizeof(int);
531 #if MM_DEBUG_DRAWING
532             NSLog(@"   Clear block (%d,%d) -> (%d,%d)", row1, col1,
533                     row2,col2);
534 #endif
535             [self clearBlockFromRow:row1 column:col1
536                     toRow:row2 column:col2
537                     color:[NSColor colorWithArgbInt:color]];
538         } else if (DeleteLinesDrawType == type) {
539             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
540             int row = *((int*)bytes);  bytes += sizeof(int);
541             int count = *((int*)bytes);  bytes += sizeof(int);
542             int bot = *((int*)bytes);  bytes += sizeof(int);
543             int left = *((int*)bytes);  bytes += sizeof(int);
544             int right = *((int*)bytes);  bytes += sizeof(int);
546 #if MM_DEBUG_DRAWING
547             NSLog(@"   Delete %d line(s) from %d", count, row);
548 #endif
549             [self deleteLinesFromRow:row lineCount:count
550                     scrollBottom:bot left:left right:right
551                            color:[NSColor colorWithArgbInt:color]];
552         } else if (DrawStringDrawType == type) {
553             int bg = *((int*)bytes);  bytes += sizeof(int);
554             int fg = *((int*)bytes);  bytes += sizeof(int);
555             int sp = *((int*)bytes);  bytes += sizeof(int);
556             int row = *((int*)bytes);  bytes += sizeof(int);
557             int col = *((int*)bytes);  bytes += sizeof(int);
558             int cells = *((int*)bytes);  bytes += sizeof(int);
559             int flags = *((int*)bytes);  bytes += sizeof(int);
560             int len = *((int*)bytes);  bytes += sizeof(int);
561             // UniChar *string = (UniChar*)bytes;  bytes += len;
562             NSString *string = [[NSString alloc] initWithBytesNoCopy:(void*)bytes
563                                                               length:len
564                                                             encoding:NSUTF8StringEncoding
565                                                         freeWhenDone:NO];
566             bytes += len;
567 #if MM_DEBUG_DRAWING
568             NSLog(@"   Draw string at (%d,%d) length=%d flags=%d fg=0x%x "
569                     "bg=0x%x sp=0x%x", row, col, len, flags, fg, bg, sp);
570 #endif
571             unichar *characters = malloc(sizeof(unichar) * [string length]);
572             [string getCharacters:characters];
574             [self drawString:characters length:[string length] atRow:row column:col
575                        cells:cells withFlags:flags
576                     foregroundColor:[NSColor colorWithRgbInt:fg]
577                     backgroundColor:[NSColor colorWithArgbInt:bg]
578                        specialColor:[NSColor colorWithRgbInt:sp]];
579             free(characters);
580             [string release];
581         } else if (InsertLinesDrawType == type) {
582             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
583             int row = *((int*)bytes);  bytes += sizeof(int);
584             int count = *((int*)bytes);  bytes += sizeof(int);
585             int bot = *((int*)bytes);  bytes += sizeof(int);
586             int left = *((int*)bytes);  bytes += sizeof(int);
587             int right = *((int*)bytes);  bytes += sizeof(int);
589 #if MM_DEBUG_DRAWING
590             NSLog(@"   Insert %d line(s) at row %d", count, row);
591 #endif
592             [self insertLinesAtRow:row lineCount:count
593                              scrollBottom:bot left:left right:right
594                                     color:[NSColor colorWithArgbInt:color]];
595         } else if (DrawCursorDrawType == type) {
596             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
597             int row = *((int*)bytes);  bytes += sizeof(int);
598             int col = *((int*)bytes);  bytes += sizeof(int);
599             int shape = *((int*)bytes);  bytes += sizeof(int);
600             int percent = *((int*)bytes);  bytes += sizeof(int);
602 #if MM_DEBUG_DRAWING
603             NSLog(@"   Draw cursor at (%d,%d)", row, col);
604 #endif
605             [self drawInsertionPointAtRow:row column:col shape:shape
606                                      fraction:percent
607                                         color:[NSColor colorWithRgbInt:color]];
608         } else if (DrawInvertedRectDrawType == type) {
609             int row = *((int*)bytes);  bytes += sizeof(int);
610             int col = *((int*)bytes);  bytes += sizeof(int);
611             int nr = *((int*)bytes);  bytes += sizeof(int);
612             int nc = *((int*)bytes);  bytes += sizeof(int);
613             /*int invert = *((int*)bytes);*/  bytes += sizeof(int);
615 #if MM_DEBUG_DRAWING
616             NSLog(@"   Draw inverted rect: row=%d col=%d nrows=%d ncols=%d",
617                     row, col, nr, nc);
618 #endif
619             [self drawInvertedRectAtRow:row column:col numRows:nr
620                              numColumns:nc];
621         } else if (SetCursorPosDrawType == type) {
622             // TODO: This is used for Voice Over support in MMTextView,
623             // MMAtsuiTextView currently does not support Voice Over.
624             /*cursorRow = *((int*)bytes);*/  bytes += sizeof(int);
625             /*cursorCol = *((int*)bytes);*/  bytes += sizeof(int);
626         } else {
627             NSLog(@"WARNING: Unknown draw type (type=%d)", type);
628         }
629     }
631     [self endDrawing];
633     // NOTE: During resizing, Cocoa only sends draw messages before Vim's rows
634     // and columns are changed (due to ipc delays). Force a redraw here.
635     [self setNeedsDisplay:YES];
636     // [self displayIfNeeded];
638 #if MM_DEBUG_DRAWING
639     NSLog(@"<==== END   %s", _cmd);
640 #endif
643 - (NSSize)constrainRows:(int *)rows columns:(int *)cols toSize:(NSSize)size
645     // TODO:
646     // - Rounding errors may cause size change when there should be none
647     // - Desired rows/columns shold not be 'too small'
649     // Constrain the desired size to the given size.  Values for the minimum
650     // rows and columns is taken from Vim.
651     NSSize desiredSize = [self desiredSize];
652     int desiredRows = maxRows;
653     int desiredCols = maxColumns;
655     if (size.height != desiredSize.height) {
656         float fh = cellSize.height;
657         float ih = 2 * insetSize.height;
658         if (fh < 1.0f) fh = 1.0f;
660         desiredRows = floor((size.height - ih)/fh);
661         desiredSize.height = fh*desiredRows + ih;
662     }
664     if (size.width != desiredSize.width) {
665         float fw = cellSize.width;
666         float iw = 2 * insetSize.width;
667         if (fw < 1.0f) fw = 1.0f;
669         desiredCols = floor((size.width - iw)/fw);
670         desiredSize.width = fw*desiredCols + iw;
671     }
673     if (rows) *rows = desiredRows;
674     if (cols) *cols = desiredCols;
676     return desiredSize;
679 - (NSSize)desiredSize
681     // Compute the size the text view should be for the entire text area and
682     // inset area to be visible with the present number of rows and columns.
683     return NSMakeSize(maxColumns * cellSize.width + 2 * insetSize.width,
684                       maxRows * cellSize.height + 2 * insetSize.height);
687 - (NSSize)minSize
689     // Compute the smallest size the text view is allowed to be.
690     return NSMakeSize(MMMinColumns * cellSize.width + 2 * insetSize.width,
691                       MMMinRows * cellSize.height + 2 * insetSize.height);
694 - (void)changeFont:(id)sender
696     NSFont *newFont = [sender convertFont:font];
698     if (newFont) {
699         NSString *name = [newFont displayName];
700         unsigned len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
701         if (len > 0) {
702             NSMutableData *data = [NSMutableData data];
703             float pointSize = [newFont pointSize];
705             [data appendBytes:&pointSize length:sizeof(float)];
707             ++len;  // include NUL byte
708             [data appendBytes:&len length:sizeof(unsigned)];
709             [data appendBytes:[name UTF8String] length:len];
711             [[self vimController] sendMessage:SetFontMsgID data:data];
712         }
713     }
716 - (void)scrollWheel:(NSEvent *)event
718     if ([event deltaY] == 0)
719         return;
721     int row, col;
722     NSPoint pt = [self convertPoint:[event locationInWindow] fromView:nil];
724     // View is not flipped, instead the atsui code draws to a flipped image;
725     // thus we need to 'flip' the coordinate here since the column number
726     // increases in an up-to-down order.
727     pt.y = [self frame].size.height - pt.y;
729     if (![self convertPoint:pt toRow:&row column:&col])
730         return;
732     int flags = [event modifierFlags];
733     float dy = [event deltaY];
734     NSMutableData *data = [NSMutableData data];
736     [data appendBytes:&row length:sizeof(int)];
737     [data appendBytes:&col length:sizeof(int)];
738     [data appendBytes:&flags length:sizeof(int)];
739     [data appendBytes:&dy length:sizeof(float)];
741     [[self vimController] sendMessage:ScrollWheelMsgID data:data];
746 // NOTE: The menu items cut/copy/paste/undo/redo/select all/... must be bound
747 // to the same actions as in IB otherwise they will not work with dialogs.  All
748 // we do here is forward these actions to the Vim process.
750 - (IBAction)cut:(id)sender
752     [[self windowController] vimMenuItemAction:sender];
755 - (IBAction)copy:(id)sender
757     [[self windowController] vimMenuItemAction:sender];
760 - (IBAction)paste:(id)sender
762     [[self windowController] vimMenuItemAction:sender];
765 - (IBAction)undo:(id)sender
767     [[self windowController] vimMenuItemAction:sender];
770 - (IBAction)redo:(id)sender
772     [[self windowController] vimMenuItemAction:sender];
775 - (IBAction)selectAll:(id)sender
777     [[self windowController] vimMenuItemAction:sender];
780 @end // MMAtsuiTextView
785 @implementation MMAtsuiTextView (Private)
787 - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column
789     NSPoint origin = { insetSize.width, insetSize.height };
791     if (!(cellSize.width > 0 && cellSize.height > 0))
792         return NO;
794     if (row) *row = floor((point.y-origin.y-1) / cellSize.height);
795     if (column) *column = floor((point.x-origin.x-1) / cellSize.width);
797     //NSLog(@"convertPoint:%@ toRow:%d column:%d", NSStringFromPoint(point),
798     //        *row, *column);
800     return YES;
803 - (void)initAtsuStyles
805     int i;
806     for (i = 0; i < MMMaxCellsPerChar; i++)
807         ATSUCreateStyle(&atsuStyles[i]);
810 - (void)disposeAtsuStyles
812     int i;
814     for (i = 0; i < MMMaxCellsPerChar; i++)
815         if (atsuStyles[i] != NULL)
816         {
817             if (ATSUDisposeStyle(atsuStyles[i]) != noErr)
818                 atsuStyles[i] = NULL;
819         }
822 - (void)updateAtsuStyles
824     ATSUFontID        fontID;
825     Fixed             fontSize;
826     Fixed             fontWidth;
827     int               i;
828     CGAffineTransform transform = CGAffineTransformMakeScale(1, -1);
829     ATSStyleRenderingOptions options;
831     fontID    = [font _atsFontID];
832     fontSize  = Long2Fix([font pointSize]);
833     options   = kATSStyleApplyAntiAliasing;
835     ATSUAttributeTag attribTags[] =
836     {
837         kATSUFontTag, kATSUSizeTag, kATSUImposeWidthTag,
838         kATSUFontMatrixTag, kATSUStyleRenderingOptionsTag,
839         kATSUMaxATSUITagValue + 1
840     };
842     ByteCount attribSizes[] =
843     {
844         sizeof(ATSUFontID), sizeof(Fixed), sizeof(fontWidth),
845         sizeof(CGAffineTransform), sizeof(ATSStyleRenderingOptions),
846         sizeof(font)
847     };
849     ATSUAttributeValuePtr attribValues[] =
850     {
851         &fontID, &fontSize, &fontWidth, &transform, &options, &font
852     };
854     ATSUFontFeatureType featureTypes[] = {
855         kLigaturesType, kLigaturesType
856     };
858     ATSUFontFeatureSelector featureSelectors[] = {
859         kCommonLigaturesOffSelector, kRareLigaturesOffSelector
860     };
862     for (i = 0; i < MMMaxCellsPerChar; i++)
863     {
864         fontWidth = Long2Fix(cellSize.width * (i + 1));
866         if (ATSUSetAttributes(atsuStyles[i],
867                               (sizeof attribTags) / sizeof(ATSUAttributeTag),
868                               attribTags, attribSizes, attribValues) != noErr)
869         {
870             ATSUDisposeStyle(atsuStyles[i]);
871             atsuStyles[i] = NULL;
872         }
874         // Turn off ligatures by default
875         ATSUSetFontFeatures(atsuStyles[i],
876                             sizeof(featureTypes) / sizeof(featureTypes[0]),
877                             featureTypes, featureSelectors);
878     }
881 - (void)dispatchKeyEvent:(NSEvent *)event
883     // Only handle the command if it came from a keyDown event
884     if ([event type] != NSKeyDown)
885         return;
887     NSString *chars = [event characters];
888     NSString *unmodchars = [event charactersIgnoringModifiers];
889     unichar c = [chars characterAtIndex:0];
890     unichar imc = [unmodchars characterAtIndex:0];
891     int len = 0;
892     const char *bytes = 0;
893     int mods = [event modifierFlags];
895     //NSLog(@"%s chars[0]=0x%x unmodchars[0]=0x%x (chars=%@ unmodchars=%@)",
896     //        _cmd, c, imc, chars, unmodchars);
898     if (' ' == imc && 0xa0 != c) {
899         // HACK!  The AppKit turns <C-Space> into <C-@> which is not standard
900         // Vim behaviour, so bypass this problem.  (0xa0 is <M-Space>, which
901         // should be passed on as is.)
902         len = [unmodchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
903         bytes = [unmodchars UTF8String];
904     } else if (imc == c && '2' == c) {
905         // HACK!  Translate Ctrl+2 to <C-@>.
906         static char ctrl_at = 0;
907         len = 1;  bytes = &ctrl_at;
908     } else if (imc == c && '6' == c) {
909         // HACK!  Translate Ctrl+6 to <C-^>.
910         static char ctrl_hat = 0x1e;
911         len = 1;  bytes = &ctrl_hat;
912     } else if (c == 0x19 && imc == 0x19) {
913         // HACK! AppKit turns back tab into Ctrl-Y, so we need to handle it
914         // separately (else Ctrl-Y doesn't work).
915         static char tab = 0x9;
916         len = 1;  bytes = &tab;  mods |= NSShiftKeyMask;
917     } else {
918         len = [chars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
919         bytes = [chars UTF8String];
920     }
922     [self sendKeyDown:bytes length:len modifiers:mods];
925 - (void)sendKeyDown:(const char *)chars length:(int)len modifiers:(int)flags
927     if (chars && len > 0) {
928         NSMutableData *data = [NSMutableData data];
930         [data appendBytes:&flags length:sizeof(int)];
931         [data appendBytes:&len length:sizeof(int)];
932         [data appendBytes:chars length:len];
934         [self hideMouseCursor];
936         //NSLog(@"%s len=%d chars=0x%x", _cmd, len, chars[0]);
937         [[self vimController] sendMessage:KeyDownMsgID data:data];
938     }
941 - (void)hideMouseCursor
943     // Check 'mousehide' option
944     id mh = [[[self vimController] vimState] objectForKey:@"p_mh"];
945     if (mh && ![mh boolValue])
946         [NSCursor setHiddenUntilMouseMoves:NO];
947     else
948         [NSCursor setHiddenUntilMouseMoves:YES];
951 - (MMWindowController *)windowController
953     id windowController = [[self window] windowController];
954     if ([windowController isKindOfClass:[MMWindowController class]])
955         return (MMWindowController*)windowController;
956     return nil;
959 - (MMVimController *)vimController
961     return [[self windowController] vimController];
964 @end // MMAtsuiTextView (Private)
969 @implementation MMAtsuiTextView (Drawing)
971 - (NSPoint)originForRow:(int)row column:(int)col
973     return NSMakePoint(col * cellSize.width, row * cellSize.height);
976 - (NSRect)rectFromRow:(int)row1 column:(int)col1
977                 toRow:(int)row2 column:(int)col2
979     NSPoint origin = [self originForRow: row1 column: col1];
980     return NSMakeRect(origin.x, origin.y,
981                       (col2 + 1 - col1) * cellSize.width,
982                       (row2 + 1 - row1) * cellSize.height);
985 - (NSSize)textAreaSize
987     // Calculate the (desired) size of the text area, i.e. the text view area
988     // minus the inset area.
989     return NSMakeSize(maxColumns * cellSize.width, maxRows * cellSize.height);
992 - (void)resizeContentImage
994     //NSLog(@"resizeContentImage");
995     [contentImage release];
996     contentImage = [[NSImage alloc] initWithSize:[self textAreaSize]];
997     [contentImage setFlipped: YES];
998     imageSize = [self textAreaSize];
1001 - (void)beginDrawing
1003     [contentImage lockFocus];
1006 - (void)endDrawing
1008     [contentImage unlockFocus];
1011 #define atsu_style_set_bool(s, t, b) \
1012     ATSUSetAttributes(s, 1, &t, &(sizeof(Boolean)), &&b);
1014 - (void)drawString:(UniChar *)string length:(UniCharCount)length
1015              atRow:(int)row column:(int)col cells:(int)cells
1016          withFlags:(int)flags foregroundColor:(NSColor *)fg
1017    backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp
1019     // 'string' consists of 'length' utf-16 code pairs and should cover 'cells'
1020     // display cells (a normal character takes up one display cell, a wide
1021     // character takes up two)
1022     ATSUStyle          style = (flags & DRAW_WIDE) ? atsuStyles[1] : atsuStyles[0];
1023     ATSUTextLayout     layout;
1025     // Font selection and rendering options for ATSUI
1026     ATSUAttributeTag      attribTags[3] = { kATSUQDBoldfaceTag,
1027                                             kATSUQDItalicTag,
1028                                             kATSUStyleRenderingOptionsTag };
1029     ByteCount             attribSizes[] = { sizeof(Boolean),
1030                                             sizeof(Boolean),
1031                                             sizeof(UInt32) };
1032     Boolean               useBold, useItalic;
1033     UInt32                useAntialias;
1034     ATSUAttributeValuePtr attribValues[3] = { &useBold, &useItalic,
1035                                               &useAntialias };
1037     useBold      = (flags & DRAW_BOLD) ? true : false;
1038     useItalic    = (flags & DRAW_ITALIC) ? true : false;
1039     useAntialias = antialias ? kATSStyleApplyAntiAliasing
1040                              : kATSStyleNoAntiAliasing;
1042     ATSUSetAttributes(style, sizeof(attribValues) / sizeof(attribValues[0]),
1043                       attribTags, attribSizes, attribValues);
1045     // NSLog(@"drawString: %d", length);
1047     ATSUCreateTextLayout(&layout);
1048     ATSUSetTextPointerLocation(layout, string,
1049                                kATSUFromTextBeginning, kATSUToTextEnd,
1050                                length);
1051     ATSUSetRunStyle(layout, style, kATSUFromTextBeginning, kATSUToTextEnd);
1053     NSRect rect = NSMakeRect(col * cellSize.width, row * cellSize.height,
1054                              length * cellSize.width, cellSize.height);
1055     if (flags & DRAW_WIDE)
1056         rect.size.width = rect.size.width * 2;
1057     CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
1059     ATSUAttributeTag tags[] = { kATSUCGContextTag };
1060     ByteCount sizes[] = { sizeof(CGContextRef) };
1061     ATSUAttributeValuePtr values[] = { &context };
1062     ATSUSetLayoutControls(layout, 1, tags, sizes, values);
1064     if (! (flags & DRAW_TRANSP))
1065     {
1066         [bg set];
1067         NSRectFill(rect);
1068     }
1070     [fg set];
1072     ATSUSetTransientFontMatching(layout, TRUE);
1073     ATSUDrawText(layout,
1074                  kATSUFromTextBeginning,
1075                  kATSUToTextEnd,
1076                  X2Fix(rect.origin.x),
1077                  X2Fix(rect.origin.y + [font ascender]));
1078     ATSUDisposeTextLayout(layout);
1081 - (void)scrollRect:(NSRect)rect lineCount:(int)count
1083     NSPoint destPoint = rect.origin;
1084     destPoint.y += count * cellSize.height;
1086     NSCopyBits(0, rect, destPoint);
1089 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
1090               scrollBottom:(int)bottom left:(int)left right:(int)right
1091                      color:(NSColor *)color
1093     NSRect rect = [self rectFromRow:row + count
1094                              column:left
1095                               toRow:bottom
1096                              column:right];
1097     [color set];
1098     // move rect up for count lines
1099     [self scrollRect:rect lineCount:-count];
1100     [self clearBlockFromRow:bottom - count + 1
1101                      column:left
1102                       toRow:bottom
1103                      column:right
1104                       color:color];
1107 - (void)insertLinesAtRow:(int)row lineCount:(int)count
1108             scrollBottom:(int)bottom left:(int)left right:(int)right
1109                    color:(NSColor *)color
1111     NSRect rect = [self rectFromRow:row
1112                              column:left
1113                               toRow:bottom - count
1114                              column:right];
1115     [color set];
1116     // move rect down for count lines
1117     [self scrollRect:rect lineCount:count];
1118     [self clearBlockFromRow:row
1119                      column:left
1120                       toRow:row + count - 1
1121                      column:right
1122                       color:color];
1125 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
1126                    column:(int)col2 color:(NSColor *)color
1128     [color set];
1129     NSRectFill([self rectFromRow:row1 column:col1 toRow:row2 column:col2]);
1132 - (void)clearAll
1134     [defaultBackgroundColor set];
1135     NSRectFill(NSMakeRect(0, 0, imageSize.width, imageSize.height));
1138 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
1139                        fraction:(int)percent color:(NSColor *)color
1141     NSPoint origin = [self originForRow:row column:col];
1142     NSRect rect = NSMakeRect(origin.x, origin.y,
1143                              cellSize.width, cellSize.height);
1145     // NSLog(@"shape = %d, fraction: %d", shape, percent);
1147     if (MMInsertionPointHorizontal == shape) {
1148         int frac = (cellSize.height * percent + 99)/100;
1149         rect.origin.y += rect.size.height - frac;
1150         rect.size.height = frac;
1151     } else if (MMInsertionPointVertical == shape) {
1152         int frac = (cellSize.width * percent + 99)/100;
1153         rect.size.width = frac;
1154     } else if (MMInsertionPointVerticalRight == shape) {
1155         int frac = (cellSize.width * percent + 99)/100;
1156         rect.origin.x += rect.size.width - frac;
1157         rect.size.width = frac;
1158     }
1160     [color set];
1161     if (MMInsertionPointHollow == shape) {
1162         NSFrameRect(rect);
1163     } else {
1164         NSRectFill(rect);
1165     }
1168 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nrows
1169                    numColumns:(int)ncols
1171     // TODO: THIS CODE HAS NOT BEEN TESTED!
1172     CGContextRef cgctx = [[NSGraphicsContext currentContext] graphicsPort];
1173     CGContextSaveGState(cgctx);
1174     CGContextSetBlendMode(cgctx, kCGBlendModeDifference);
1175     CGContextSetRGBFillColor(cgctx, 1.0, 1.0, 1.0, 1.0);
1177     CGRect rect = { col * cellSize.width, row * cellSize.height,
1178                     ncols * cellSize.width, nrows * cellSize.height };
1179     CGContextFillRect(cgctx, rect);
1181     CGContextRestoreGState(cgctx);
1184 @end // MMAtsuiTextView (Drawing)