Window and view refactoring
[MacVim.git] / src / MacVim / MMAtsuiTextView.m
blob9ea9fb5b932eb99052fe6befa3fc26271d9ccebb
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 "MMAtsuiTextView.h"
29 #import "MMVimController.h"
30 #import "MacVim.h"
33 // TODO: What does DRAW_TRANSP flag do?  If the background isn't drawn when
34 // this flag is set, then sometimes the character after the cursor becomes
35 // blank.  Everything seems to work fine by just ignoring this flag.
36 #define DRAW_TRANSP               0x01    /* draw with transparant bg */
37 #define DRAW_BOLD                 0x02    /* draw bold text */
38 #define DRAW_UNDERL               0x04    /* draw underline text */
39 #define DRAW_UNDERC               0x08    /* draw undercurl text */
40 #define DRAW_ITALIC               0x10    /* draw italic text */
41 #define DRAW_CURSOR               0x20
44 static char MMKeypadEnter[2] = { 'K', 'A' };
45 static NSString *MMKeypadEnterString = @"KA";
47 enum {
48     // These values are chosen so that the min size is not too small with the
49     // default font (they only affect resizing with the mouse, you can still
50     // use e.g. ":set lines=2" to go below these values).
51     MMMinRows = 4,
52     MMMinColumns = 30
56 @interface NSFont (AppKitPrivate)
57 - (ATSUFontID) _atsFontID;
58 @end
61 @interface MMAtsuiTextView (Private)
62 - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column;
63 - (void)initAtsuStyles;
64 - (void)disposeAtsuStyles;
65 - (void)updateAtsuStyles;
66 - (void)dispatchKeyEvent:(NSEvent *)event;
67 - (void)sendKeyDown:(const char *)chars length:(int)len modifiers:(int)flags;
68 - (MMVimController *)vimController;
69 @end
72 @interface MMAtsuiTextView (Drawing)
73 - (NSRect)rectFromRow:(int)row1 column:(int)col1
74                 toRow:(int)row2 column:(int)col2;
75 - (NSSize)textAreaSize;
76 - (void)resizeContentImage;
77 - (void)beginDrawing;
78 - (void)endDrawing;
79 - (void)drawString:(UniChar *)string length:(UniCharCount)length
80              atRow:(int)row column:(int)col cells:(int)cells
81          withFlags:(int)flags foregroundColor:(NSColor *)fg
82    backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp;
83 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
84               scrollBottom:(int)bottom left:(int)left right:(int)right
85                      color:(NSColor *)color;
86 - (void)insertLinesAtRow:(int)row lineCount:(int)count
87             scrollBottom:(int)bottom left:(int)left right:(int)right
88                    color:(NSColor *)color;
89 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
90                    column:(int)col2 color:(NSColor *)color;
91 - (void)clearAll;
92 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
93                        fraction:(int)percent color:(NSColor *)color;
94 @end
97 @implementation MMAtsuiTextView
99 - (id)initWithFrame:(NSRect)frame
101     if ((self = [super initWithFrame:frame])) {
102         // NOTE!  It does not matter which font is set here, Vim will set its
103         // own font on startup anyway.  Just set some bogus values.
104         font = [[NSFont userFixedPitchFontOfSize:0] retain];
105         cellSize.width = cellSize.height = 1;
106         contentImage = nil;
107         imageSize = NSZeroSize;
109         [self initAtsuStyles];
110     }
112     return self;
115 - (void)dealloc
117     [self disposeAtsuStyles];
118     [font release];  font = nil;
119     [defaultBackgroundColor release];  defaultBackgroundColor = nil;
120     [defaultForegroundColor release];  defaultForegroundColor = nil;
122     [super dealloc];
125 - (void)getMaxRows:(int*)rows columns:(int*)cols
127     if (rows) *rows = maxRows;
128     if (cols) *cols = maxColumns;
131 - (void)setMaxRows:(int)rows columns:(int)cols
133     // NOTE: Just remember the new values, the actual resizing is done lazily.
134     maxRows = rows;
135     maxColumns = cols;
138 - (void)setDefaultColorsBackground:(NSColor *)bgColor
139                         foreground:(NSColor *)fgColor
141     if (defaultBackgroundColor != bgColor) {
142         [defaultBackgroundColor release];
143         defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
144     }
146     // NOTE: The default foreground color isn't actually used for anything, but
147     // other class instances might want to be able to access it so it is stored
148     // here.
149     if (defaultForegroundColor != fgColor) {
150         [defaultForegroundColor release];
151         defaultForegroundColor = fgColor ? [fgColor retain] : nil;
152     }
155 - (NSRect)rectForRowsInRange:(NSRange)range
157     // TODO: Add text inset to origin
158     NSRect rect = { 0, 0, 0, 0 };
159     unsigned start = range.location > maxRows ? maxRows : range.location;
160     unsigned length = range.length;
162     if (start+length > maxRows)
163         length = maxRows - start;
165     rect.origin.y = cellSize.height * start;
166     rect.size.height = cellSize.height * length;
168     return rect;
171 - (NSRect)rectForColumnsInRange:(NSRange)range
173     // TODO: Add text inset to origin
174     NSRect rect = { 0, 0, 0, 0 };
175     unsigned start = range.location > maxColumns ? maxColumns : range.location;
176     unsigned length = range.length;
178     if (start+length > maxColumns)
179         length = maxColumns - start;
181     rect.origin.x = cellSize.width * start;
182     rect.size.width = cellSize.width * length;
184     return rect;
188 - (void)setFont:(NSFont *)newFont
190     if (newFont && font != newFont) {
191         [font release];
192         font = [newFont retain];
194         float em = [newFont widthOfString:@"m"];
195         float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
196                 floatForKey:MMCellWidthMultiplierKey];
198         // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
199         // only render at integer sizes.  Hence, we restrict the cell width to
200         // an integer here, otherwise the window width and the actual text
201         // width will not match.
202         cellSize.width = ceilf(em * cellWidthMultiplier);
203         cellSize.height = linespace + [newFont defaultLineHeightForFont];
205         [self updateAtsuStyles];
206     }
209 - (void)setWideFont:(NSFont *)newFont
213 - (NSFont *)font
215     return font;
218 - (NSSize)cellSize
220     return cellSize;
223 - (void)setLinespace:(float)newLinespace
225     linespace = newLinespace;
227     // NOTE: The linespace is added to the cell height in order for a multiline
228     // selection not to have white (background color) gaps between lines.  Also
229     // this simplifies the code a lot because there is no need to check the
230     // linespace when calculating the size of the text view etc.  When the
231     // linespace is non-zero the baseline will be adjusted as well; check
232     // MMTypesetter.
233     cellSize.height = linespace + [font defaultLineHeightForFont];
239 - (NSEvent *)lastMouseDownEvent
241     return nil;
244 - (void)setShouldDrawInsertionPoint:(BOOL)on
248 - (void)setPreEditRow:(int)row column:(int)col
252 - (void)hideMarkedTextField
259 - (void)keyDown:(NSEvent *)event
261     //NSLog(@"%s %@", _cmd, event);
262     // HACK! If control modifier is held, don't pass the event along to
263     // interpretKeyEvents: since some keys are bound to multiple commands which
264     // means doCommandBySelector: is called several times.
265     //
266     // TODO: Figure out a way to disable Cocoa key bindings entirely, without
267     // affecting input management.
268     if ([event modifierFlags] & NSControlKeyMask) {
269         NSString *unmod = [event charactersIgnoringModifiers];
270         if ([unmod length] == 1 && [unmod characterAtIndex:0] <= 0x7f
271                                 && [unmod characterAtIndex:0] >= 0x60) {
272             // HACK! Send Ctrl-letter keys (and C-@, C-[, C-\, C-], C-^, C-_)
273             // as normal text to be added to the Vim input buffer.  This must
274             // be done in order for the backend to be able to separate e.g.
275             // Ctrl-i and Ctrl-tab.
276             [self insertText:[event characters]];
277         } else {
278             [self dispatchKeyEvent:event];
279         }
280     } else {
281         [self interpretKeyEvents:[NSArray arrayWithObject:event]];
282     }
285 - (void)insertText:(id)string
287     //NSLog(@"%s %@", _cmd, string);
288     // NOTE!  This method is called for normal key presses but also for
289     // Option-key presses --- even when Ctrl is held as well as Option.  When
290     // Ctrl is held, the AppKit translates the character to a Ctrl+key stroke,
291     // so 'string' need not be a printable character!  In this case it still
292     // works to pass 'string' on to Vim as a printable character (since
293     // modifiers are already included and should not be added to the input
294     // buffer using CSI, K_MODIFIER).
296     [self hideMarkedTextField];
298     NSEvent *event = [NSApp currentEvent];
300     // HACK!  In order to be able to bind to <S-Space>, <S-M-Tab>, etc. we have
301     // to watch for them here.
302     if ([event type] == NSKeyDown
303             && [[event charactersIgnoringModifiers] length] > 0
304             && [event modifierFlags]
305                 & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask)) {
306         unichar c = [[event charactersIgnoringModifiers] characterAtIndex:0];
308         // <S-M-Tab> translates to 0x19 
309         if (' ' == c || 0x19 == c) {
310             [self dispatchKeyEvent:event];
311             return;
312         }
313     }
315     // TODO: Support 'mousehide' (check p_mh)
316     [NSCursor setHiddenUntilMouseMoves:YES];
318     // NOTE: 'string' is either an NSString or an NSAttributedString.  Since we
319     // do not support attributes, simply pass the corresponding NSString in the
320     // latter case.
321     if ([string isKindOfClass:[NSAttributedString class]])
322         string = [string string];
324     //NSLog(@"send InsertTextMsgID: %@", string);
326     [[self vimController] sendMessage:InsertTextMsgID
327                  data:[string dataUsingEncoding:NSUTF8StringEncoding]];
330 - (void)doCommandBySelector:(SEL)selector
332     //NSLog(@"%s %@", _cmd, NSStringFromSelector(selector));
333     // By ignoring the selector we effectively disable the key binding
334     // mechanism of Cocoa.  Hopefully this is what the user will expect
335     // (pressing Ctrl+P would otherwise result in moveUp: instead of previous
336     // match, etc.).
337     //
338     // We usually end up here if the user pressed Ctrl+key (but not
339     // Ctrl+Option+key).
341     NSEvent *event = [NSApp currentEvent];
343     if (selector == @selector(cancelOperation:)
344             || selector == @selector(insertNewline:)) {
345         // HACK! If there was marked text which got abandoned as a result of
346         // hitting escape or enter, then 'insertText:' is called with the
347         // abandoned text but '[event characters]' includes the abandoned text
348         // as well.  Since 'dispatchKeyEvent:' looks at '[event characters]' we
349         // must intercept these keys here or the abandonded text gets inserted
350         // twice.
351         NSString *key = [event charactersIgnoringModifiers];
352         const char *chars = [key UTF8String];
353         int len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
355         if (0x3 == chars[0]) {
356             // HACK! AppKit turns enter (not return) into Ctrl-C, so we need to
357             // handle it separately (else Ctrl-C doesn't work).
358             len = sizeof(MMKeypadEnter)/sizeof(MMKeypadEnter[0]);
359             chars = MMKeypadEnter;
360         }
362         [self sendKeyDown:chars length:len modifiers:[event modifierFlags]];
363     } else {
364         [self dispatchKeyEvent:event];
365     }
368 - (BOOL)performKeyEquivalent:(NSEvent *)event
370     //NSLog(@"%s %@", _cmd, event);
371     // Called for Cmd+key keystrokes, function keys, arrow keys, page
372     // up/down, home, end.
373     //
374     // NOTE: This message cannot be ignored since Cmd+letter keys never are
375     // passed to keyDown:.  It seems as if the main menu consumes Cmd-key
376     // strokes, unless the key is a function key.
378     // NOTE: If the event that triggered this method represents a function key
379     // down then we do nothing, otherwise the input method never gets the key
380     // stroke (some input methods use e.g. arrow keys).  The function key down
381     // event will still reach Vim though (via keyDown:).  The exceptions to
382     // this rule are: PageUp/PageDown (keycode 116/121).
383     int flags = [event modifierFlags];
384     if ([event type] != NSKeyDown || flags & NSFunctionKeyMask
385             && !(116 == [event keyCode] || 121 == [event keyCode]))
386         return NO;
388     // HACK!  Let the main menu try to handle any key down event, before
389     // passing it on to vim, otherwise key equivalents for menus will
390     // effectively be disabled.
391     if ([[NSApp mainMenu] performKeyEquivalent:event])
392         return YES;
394     // HACK!  KeyCode 50 represent the key which switches between windows
395     // within an application (like Cmd+Tab is used to switch between
396     // applications).  Return NO here, else the window switching does not work.
397     //
398     // Will this hack work for all languages / keyboard layouts?
399     if ([event keyCode] == 50)
400         return NO;
402     // HACK!  On Leopard Ctrl-key events end up here instead of keyDown:.
403     if (flags & NSControlKeyMask) {
404         [self keyDown:event];
405         return YES;
406     }
408     //NSLog(@"%s%@", _cmd, event);
410     NSString *chars = [event characters];
411     NSString *unmodchars = [event charactersIgnoringModifiers];
412     int len = [unmodchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
413     NSMutableData *data = [NSMutableData data];
415     if (len <= 0)
416         return NO;
418     // If 'chars' and 'unmodchars' differs when shift flag is present, then we
419     // can clear the shift flag as it is already included in 'unmodchars'.
420     // Failing to clear the shift flag means <D-Bar> turns into <S-D-Bar> (on
421     // an English keyboard).
422     if (flags & NSShiftKeyMask && ![chars isEqual:unmodchars])
423         flags &= ~NSShiftKeyMask;
425     if (0x3 == [unmodchars characterAtIndex:0]) {
426         // HACK! AppKit turns enter (not return) into Ctrl-C, so we need to
427         // handle it separately (else Cmd-enter turns into Ctrl-C).
428         unmodchars = MMKeypadEnterString;
429         len = [unmodchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
430     }
432     [data appendBytes:&flags length:sizeof(int)];
433     [data appendBytes:&len length:sizeof(int)];
434     [data appendBytes:[unmodchars UTF8String] length:len];
436     [[self vimController] sendMessage:CmdKeyMsgID data:data];
438     return YES;
444 - (BOOL)acceptsFirstResponder
446     return YES;
449 - (BOOL)isFlipped
451     return NO;
454 - (void)drawRect:(NSRect)rect
456     [contentImage drawInRect: rect
457                     fromRect: rect
458                    operation: NSCompositeCopy
459                     fraction: 1.0];
462 - (BOOL) wantsDefaultClipping
464     return NO;
468 #define MM_DEBUG_DRAWING 0
470 - (void)performBatchDrawWithData:(NSData *)data
472     const void *bytes = [data bytes];
473     const void *end = bytes + [data length];
475     if (! NSEqualSizes(imageSize, [self textAreaSize]))
476         [self resizeContentImage];
478 #if MM_DEBUG_DRAWING
479     NSLog(@"====> BEGIN %s", _cmd);
480 #endif
481     [self beginDrawing];
483     // TODO: Sanity check input
485     while (bytes < end) {
486         int type = *((int*)bytes);  bytes += sizeof(int);
488         if (ClearAllDrawType == type) {
489 #if MM_DEBUG_DRAWING
490             NSLog(@"   Clear all");
491 #endif
492             [self clearAll];
493         } else if (ClearBlockDrawType == type) {
494             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
495             int row1 = *((int*)bytes);  bytes += sizeof(int);
496             int col1 = *((int*)bytes);  bytes += sizeof(int);
497             int row2 = *((int*)bytes);  bytes += sizeof(int);
498             int col2 = *((int*)bytes);  bytes += sizeof(int);
500 #if MM_DEBUG_DRAWING
501             NSLog(@"   Clear block (%d,%d) -> (%d,%d)", row1, col1,
502                     row2,col2);
503 #endif
504             [self clearBlockFromRow:row1 column:col1
505                     toRow:row2 column:col2
506                     color:[NSColor colorWithArgbInt:color]];
507         } else if (DeleteLinesDrawType == type) {
508             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
509             int row = *((int*)bytes);  bytes += sizeof(int);
510             int count = *((int*)bytes);  bytes += sizeof(int);
511             int bot = *((int*)bytes);  bytes += sizeof(int);
512             int left = *((int*)bytes);  bytes += sizeof(int);
513             int right = *((int*)bytes);  bytes += sizeof(int);
515 #if MM_DEBUG_DRAWING
516             NSLog(@"   Delete %d line(s) from %d", count, row);
517 #endif
518             [self deleteLinesFromRow:row lineCount:count
519                     scrollBottom:bot left:left right:right
520                            color:[NSColor colorWithArgbInt:color]];
521         } else if (DrawStringDrawType == type) {
522             int bg = *((int*)bytes);  bytes += sizeof(int);
523             int fg = *((int*)bytes);  bytes += sizeof(int);
524             int sp = *((int*)bytes);  bytes += sizeof(int);
525             int row = *((int*)bytes);  bytes += sizeof(int);
526             int col = *((int*)bytes);  bytes += sizeof(int);
527             int cells = *((int*)bytes);  bytes += sizeof(int);
528             int flags = *((int*)bytes);  bytes += sizeof(int);
529             int len = *((int*)bytes);  bytes += sizeof(int);
530             // UniChar *string = (UniChar*)bytes;  bytes += len;
531             NSString *string = [[NSString alloc] initWithBytesNoCopy:(void*)bytes
532                                                               length:len
533                                                             encoding:NSUTF8StringEncoding
534                                                         freeWhenDone:NO];
535             bytes += len;
536 #if MM_DEBUG_DRAWING
537             NSLog(@"   Draw string at (%d,%d) length=%d flags=%d fg=0x%x "
538                     "bg=0x%x sp=0x%x", row, col, len, flags, fg, bg, sp);
539 #endif
540             unichar *characters = malloc(sizeof(unichar) * [string length]);
541             [string getCharacters:characters];
543             [self drawString:characters length:[string length] atRow:row column:col
544                        cells:cells withFlags:flags
545                     foregroundColor:[NSColor colorWithRgbInt:fg]
546                     backgroundColor:[NSColor colorWithArgbInt:bg]
547                        specialColor:[NSColor colorWithRgbInt:sp]];
548             free(characters);
549             [string release];
550         } else if (InsertLinesDrawType == type) {
551             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
552             int row = *((int*)bytes);  bytes += sizeof(int);
553             int count = *((int*)bytes);  bytes += sizeof(int);
554             int bot = *((int*)bytes);  bytes += sizeof(int);
555             int left = *((int*)bytes);  bytes += sizeof(int);
556             int right = *((int*)bytes);  bytes += sizeof(int);
558 #if MM_DEBUG_DRAWING
559             NSLog(@"   Insert %d line(s) at row %d", count, row);
560 #endif
561             [self insertLinesAtRow:row lineCount:count
562                              scrollBottom:bot left:left right:right
563                                     color:[NSColor colorWithArgbInt:color]];
564         } else if (DrawCursorDrawType == type) {
565             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
566             int row = *((int*)bytes);  bytes += sizeof(int);
567             int col = *((int*)bytes);  bytes += sizeof(int);
568             int shape = *((int*)bytes);  bytes += sizeof(int);
569             int percent = *((int*)bytes);  bytes += sizeof(int);
571 #if MM_DEBUG_DRAWING
572             NSLog(@"   Draw cursor at (%d,%d)", row, col);
573 #endif
574             [self drawInsertionPointAtRow:row column:col shape:shape
575                                      fraction:percent
576                                         color:[NSColor colorWithRgbInt:color]];
577         } else {
578             NSLog(@"WARNING: Unknown draw type (type=%d)", type);
579         }
580     }
582     [self endDrawing];
584     // NOTE: During resizing, Cocoa only sends draw messages before Vim's rows
585     // and columns are changed (due to ipc delays). Force a redraw here.
586     [self setNeedsDisplay:YES];
587     // [self displayIfNeeded];
589 #if MM_DEBUG_DRAWING
590     NSLog(@"<==== END   %s", _cmd);
591 #endif
594 - (NSSize)constrainRows:(int *)rows columns:(int *)cols toSize:(NSSize)size
596     // TODO:
597     // - Take text area inset into consideration
598     // - Rounding errors may cause size change when there should be none
599     // - Desired rows/columns shold not be 'too small'
601     // Constrain the desired size to the given size.  Values for the minimum
602     // rows and columns is taken from Vim.
603     NSSize desiredSize = [self desiredSize];
604     int desiredRows = maxRows;
605     int desiredCols = maxColumns;
607     if (size.height != desiredSize.height) {
608         float fh = cellSize.height;
609         if (fh < 1.0f) fh = 1.0f;
611         desiredRows = floor(size.height/fh);
612         desiredSize.height = fh*desiredRows;
613     }
615     if (size.width != desiredSize.width) {
616         float fw = cellSize.width;
617         if (fw < 1.0f) fw = 1.0f;
619         desiredCols = floor(size.width/fw);
620         desiredSize.width = fw*desiredCols;
621     }
623     if (rows) *rows = desiredRows;
624     if (cols) *cols = desiredCols;
626     return desiredSize;
629 - (NSSize)desiredSize
631     // Compute the size the text view should be for the entire text area and
632     // inset area to be visible with the present number of rows and columns.
633     //
634     // TODO: Add inset area to size.
635     return NSMakeSize(maxColumns*cellSize.width, maxRows*cellSize.height);
638 - (NSSize)minSize
640     // Compute the smallest size the text view is allowed to be.
641     //
642     // TODO: Add inset area to size.
643     return NSMakeSize(MMMinColumns*cellSize.width, MMMinRows*cellSize.height);
646 - (void)changeFont:(id)sender
648     NSFont *newFont = [sender convertFont:font];
650     if (newFont) {
651         NSString *name = [newFont displayName];
652         unsigned len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
653         if (len > 0) {
654             NSMutableData *data = [NSMutableData data];
655             float pointSize = [newFont pointSize];
657             [data appendBytes:&pointSize length:sizeof(float)];
659             ++len;  // include NUL byte
660             [data appendBytes:&len length:sizeof(unsigned)];
661             [data appendBytes:[name UTF8String] length:len];
663             [[self vimController] sendMessage:SetFontMsgID data:data];
664         }
665     }
668 - (void)scrollWheel:(NSEvent *)event
670     if ([event deltaY] == 0)
671         return;
673     int row, col;
674     NSPoint pt = [self convertPoint:[event locationInWindow] fromView:nil];
676     // View is not flipped, instead the atsui code draws to a flipped image;
677     // thus we need to 'flip' the coordinate here since the column number
678     // increases in an up-to-down order.
679     pt.y = [self frame].size.height - pt.y;
681     if (![self convertPoint:pt toRow:&row column:&col])
682         return;
684     int flags = [event modifierFlags];
685     float dy = [event deltaY];
686     NSMutableData *data = [NSMutableData data];
688     [data appendBytes:&row length:sizeof(int)];
689     [data appendBytes:&col length:sizeof(int)];
690     [data appendBytes:&flags length:sizeof(int)];
691     [data appendBytes:&dy length:sizeof(float)];
693     [[self vimController] sendMessage:ScrollWheelMsgID data:data];
696 @end // MMAtsuiTextView
701 @implementation MMAtsuiTextView (Private)
703 - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column
705     // TODO: text inset
706     NSPoint origin = { 0,0 };
708     if (!(cellSize.width > 0 && cellSize.height > 0))
709         return NO;
711     if (row) *row = floor((point.y-origin.y-1) / cellSize.height);
712     if (column) *column = floor((point.x-origin.x-1) / cellSize.width);
714     //NSLog(@"convertPoint:%@ toRow:%d column:%d", NSStringFromPoint(point),
715     //        *row, *column);
717     return YES;
720 - (void)initAtsuStyles
722     int i;
723     for (i = 0; i < MMMaxCellsPerChar; i++)
724         ATSUCreateStyle(&atsuStyles[i]);
727 - (void)disposeAtsuStyles
729     int i;
731     for (i = 0; i < MMMaxCellsPerChar; i++)
732         if (atsuStyles[i] != NULL)
733         {
734             if (ATSUDisposeStyle(atsuStyles[i]) != noErr)
735                 atsuStyles[i] = NULL;
736         }
739 - (void)updateAtsuStyles
741     ATSUFontID        fontID;
742     Fixed             fontSize;
743     Fixed             fontWidth;
744     int               i;
745     CGAffineTransform transform = CGAffineTransformMakeScale(1, -1);
746     ATSStyleRenderingOptions options;
748     fontID    = [font _atsFontID];
749     fontSize  = Long2Fix([font pointSize]);
750     options   = kATSStyleApplyAntiAliasing;
752     ATSUAttributeTag attribTags[] =
753     {
754         kATSUFontTag, kATSUSizeTag, kATSUImposeWidthTag,
755         kATSUFontMatrixTag, kATSUStyleRenderingOptionsTag,
756         kATSUMaxATSUITagValue + 1
757     };
759     ByteCount attribSizes[] =
760     {
761         sizeof(ATSUFontID), sizeof(Fixed), sizeof(fontWidth),
762         sizeof(CGAffineTransform), sizeof(ATSStyleRenderingOptions),
763         sizeof(font)
764     };
766     ATSUAttributeValuePtr attribValues[] =
767     {
768         &fontID, &fontSize, &fontWidth, &transform, &options, &font
769     };
771     for (i = 0; i < MMMaxCellsPerChar; i++)
772     {
773         fontWidth = Long2Fix(cellSize.width * (i + 1));
775         if (ATSUSetAttributes(atsuStyles[i],
776                               (sizeof attribTags) / sizeof(ATSUAttributeTag),
777                               attribTags, attribSizes, attribValues) != noErr)
778         {
779             ATSUDisposeStyle(atsuStyles[i]);
780             atsuStyles[i] = NULL;
781         }
782     }
785 - (void)dispatchKeyEvent:(NSEvent *)event
787     // Only handle the command if it came from a keyDown event
788     if ([event type] != NSKeyDown)
789         return;
791     NSString *chars = [event characters];
792     NSString *unmodchars = [event charactersIgnoringModifiers];
793     unichar c = [chars characterAtIndex:0];
794     unichar imc = [unmodchars characterAtIndex:0];
795     int len = 0;
796     const char *bytes = 0;
797     int mods = [event modifierFlags];
799     //NSLog(@"%s chars[0]=0x%x unmodchars[0]=0x%x (chars=%@ unmodchars=%@)",
800     //        _cmd, c, imc, chars, unmodchars);
802     if (' ' == imc && 0xa0 != c) {
803         // HACK!  The AppKit turns <C-Space> into <C-@> which is not standard
804         // Vim behaviour, so bypass this problem.  (0xa0 is <M-Space>, which
805         // should be passed on as is.)
806         len = [unmodchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
807         bytes = [unmodchars UTF8String];
808     } else if (imc == c && '2' == c) {
809         // HACK!  Translate Ctrl+2 to <C-@>.
810         static char ctrl_at = 0;
811         len = 1;  bytes = &ctrl_at;
812     } else if (imc == c && '6' == c) {
813         // HACK!  Translate Ctrl+6 to <C-^>.
814         static char ctrl_hat = 0x1e;
815         len = 1;  bytes = &ctrl_hat;
816     } else if (c == 0x19 && imc == 0x19) {
817         // HACK! AppKit turns back tab into Ctrl-Y, so we need to handle it
818         // separately (else Ctrl-Y doesn't work).
819         static char tab = 0x9;
820         len = 1;  bytes = &tab;  mods |= NSShiftKeyMask;
821     } else {
822         len = [chars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
823         bytes = [chars UTF8String];
824     }
826     [self sendKeyDown:bytes length:len modifiers:mods];
829 - (void)sendKeyDown:(const char *)chars length:(int)len modifiers:(int)flags
831     if (chars && len > 0) {
832         NSMutableData *data = [NSMutableData data];
834         [data appendBytes:&flags length:sizeof(int)];
835         [data appendBytes:&len length:sizeof(int)];
836         [data appendBytes:chars length:len];
838         // TODO: Support 'mousehide' (check p_mh)
839         [NSCursor setHiddenUntilMouseMoves:YES];
841         //NSLog(@"%s len=%d chars=0x%x", _cmd, len, chars[0]);
842         [[self vimController] sendMessage:KeyDownMsgID data:data];
843     }
846 - (MMVimController *)vimController
848     id windowController = [[self window] windowController];
850     // TODO: Make sure 'windowController' is a MMWindowController before type
851     // casting.
852     return [(MMWindowController*)windowController vimController];
855 @end // MMAtsuiTextView (Private)
860 @implementation MMAtsuiTextView (Drawing)
862 - (NSRect)rectFromRow:(int)row1 column:(int)col1
863                 toRow:(int)row2 column:(int)col2
865     return NSMakeRect(col1 * cellSize.width, row1 * cellSize.height,
866                       (col2 + 1 - col1) * cellSize.width,
867                       (row2 + 1 - row1) * cellSize.height);
870 - (NSSize)textAreaSize
872     // Calculate the (desired) size of the text area, i.e. the text view area
873     // minus the inset area.
874     return NSMakeSize(maxColumns*cellSize.width, maxRows*cellSize.height);
877 - (void)resizeContentImage
879     //NSLog(@"resizeContentImage");
880     [contentImage release];
881     contentImage = [[NSImage alloc] initWithSize:[self textAreaSize]];
882     [contentImage setFlipped: YES];
883     imageSize = [self textAreaSize];
886 - (void)beginDrawing
888     [contentImage lockFocus];
891 - (void)endDrawing
893     [contentImage unlockFocus];
896 - (void)drawString:(UniChar *)string length:(UniCharCount)length
897              atRow:(int)row column:(int)col cells:(int)cells
898          withFlags:(int)flags foregroundColor:(NSColor *)fg
899    backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp
901     // 'string' consists of 'length' utf-16 code pairs and should cover 'cells'
902     // display cells (a normal character takes up one display cell, a wide
903     // character takes up two)
904     ATSUStyle          style = atsuStyles[0];
905     ATSUTextLayout     layout;
907     // NSLog(@"drawString: %d", length);
909     ATSUCreateTextLayout(&layout);
910     ATSUSetTextPointerLocation(layout, string,
911                                kATSUFromTextBeginning, kATSUToTextEnd,
912                                length);
913     ATSUSetRunStyle(layout, style, kATSUFromTextBeginning, kATSUToTextEnd);
915     NSRect rect = NSMakeRect(col * cellSize.width, row * cellSize.height,
916                              length * cellSize.width, cellSize.height);
917     CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
919     ATSUAttributeTag tags[] = { kATSUCGContextTag };
920     ByteCount sizes[] = { sizeof(CGContextRef) };
921     ATSUAttributeValuePtr values[] = { &context };
922     ATSUSetLayoutControls(layout, 1, tags, sizes, values);
924     if (! (flags & DRAW_TRANSP))
925     {
926         [bg set];
927         NSRectFill(rect);
928     }
930     [fg set];
932     ATSUSetTransientFontMatching(layout, TRUE);
933     ATSUDrawText(layout,
934                  kATSUFromTextBeginning,
935                  kATSUToTextEnd,
936                  X2Fix(rect.origin.x),
937                  X2Fix(rect.origin.y + [font ascender]));
938     ATSUDisposeTextLayout(layout);
941 - (void)scrollRect:(NSRect)rect lineCount:(int)count
943     NSPoint destPoint = rect.origin;
944     destPoint.y += count * cellSize.height;
946     NSCopyBits(0, rect, destPoint);
949 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
950               scrollBottom:(int)bottom left:(int)left right:(int)right
951                      color:(NSColor *)color
953     NSRect rect = [self rectFromRow:row + count
954                              column:left
955                               toRow:bottom
956                              column:right];
957     [color set];
958     // move rect up for count lines
959     [self scrollRect:rect lineCount:-count];
960     [self clearBlockFromRow:bottom - count + 1
961                      column:left
962                       toRow:bottom
963                      column:right
964                       color:color];
967 - (void)insertLinesAtRow:(int)row lineCount:(int)count
968             scrollBottom:(int)bottom left:(int)left right:(int)right
969                    color:(NSColor *)color
971     NSRect rect = [self rectFromRow:row
972                              column:left
973                               toRow:bottom - count
974                              column:right];
975     [color set];
976     // move rect down for count lines
977     [self scrollRect:rect lineCount:count];
978     [self clearBlockFromRow:row
979                      column:left
980                       toRow:row + count - 1
981                      column:right
982                       color:color];
985 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
986                    column:(int)col2 color:(NSColor *)color
988     [color set];
989     NSRectFill([self rectFromRow:row1 column:col1 toRow:row2 column:col2]);
992 - (void)clearAll
994     [defaultBackgroundColor set];
995     NSRectFill(NSMakeRect(0, 0, imageSize.width, imageSize.height));
998 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
999                        fraction:(int)percent color:(NSColor *)color
1003 @end // MMAtsuiTextView (Drawing)