Remove duplicate code in text renderers
[MacVim.git] / src / MacVim / MMTextView.m
blob6308bba3967e660faf80e1252fbab07cfa05d58e
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  * MMTextView
12  *
13  * Dispatches keyboard and mouse input to the backend.  Handles drag-n-drop of
14  * files onto window.
15  *
16  * Support for input managers is somewhat hacked together.  Marked text is
17  * drawn "pseudo-inline"; it will simply draw on top of existing text and it
18  * does not respect Vim-window boundaries.
19  */
21 #import "MMAppController.h"
22 #import "MMTextStorage.h"
23 #import "MMTextView.h"
24 #import "MMTextViewHelper.h"
25 #import "MMTypesetter.h"
26 #import "MMVimController.h"
27 #import "MMWindowController.h"
28 #import "Miscellaneous.h"
32 // This is taken from gui.h
33 #define DRAW_CURSOR 0x20
37 @interface MMTextView (Private)
38 - (BOOL)convertRow:(int)row column:(int)column toPoint:(NSPoint *)point;
39 - (BOOL)convertRow:(int)row column:(int)column numRows:(int)nr
40         numColumns:(int)nc toRect:(NSRect *)rect;
41 - (MMWindowController *)windowController;
42 - (MMVimController *)vimController;
43 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
44                        fraction:(int)percent color:(NSColor *)color;
45 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nrows
46                    numColumns:(int)ncols invert:(int)invert;
47 @end
51 @implementation MMTextView
53 - (id)initWithFrame:(NSRect)frame
55     // Set up a Cocoa text system.  Note that the textStorage is released in
56     // -[MMVimView dealloc].
57     MMTextStorage *textStorage = [[MMTextStorage alloc] init];
58     NSLayoutManager *lm = [[NSLayoutManager alloc] init];
59     NSTextContainer *tc = [[NSTextContainer alloc] initWithContainerSize:
60                     NSMakeSize(1.0e7,1.0e7)];
62     NSString *typesetterString = [[NSUserDefaults standardUserDefaults]
63             stringForKey:MMTypesetterKey];
64     if ([typesetterString isEqual:@"MMTypesetter"]) {
65         NSTypesetter *typesetter = [[MMTypesetter alloc] init];
66         [lm setTypesetter:typesetter];
67         [typesetter release];
68     } else if ([typesetterString isEqual:@"MMTypesetter2"]) {
69         NSTypesetter *typesetter = [[MMTypesetter2 alloc] init];
70         [lm setTypesetter:typesetter];
71         [typesetter release];
72     } else {
73         // Only MMTypesetter supports different cell width multipliers.
74         [[NSUserDefaults standardUserDefaults]
75                 setFloat:1.0 forKey:MMCellWidthMultiplierKey];
76     }
78     // The characters in the text storage are in display order, so disable
79     // bidirectional text processing (this call is 10.4 only).
80     [[lm typesetter] setBidiProcessingEnabled:NO];
82     [tc setWidthTracksTextView:NO];
83     [tc setHeightTracksTextView:NO];
84     [tc setLineFragmentPadding:0];
86     [textStorage addLayoutManager:lm];
87     [lm addTextContainer:tc];
89     // The text storage retains the layout manager which in turn retains
90     // the text container.
91     [tc autorelease];
92     [lm autorelease];
94     // NOTE: This will make the text storage the principal owner of the text
95     // system.  Releasing the text storage will in turn release the layout
96     // manager, the text container, and finally the text view (self).  This
97     // complicates deallocation somewhat, see -[MMVimView dealloc].
98     if (![super initWithFrame:frame textContainer:tc]) {
99         [textStorage release];
100         return nil;
101     }
103     helper = [[MMTextViewHelper alloc] init];
104     [helper setTextView:self];
106     imRange = NSMakeRange(0, 0);
107     markedRange = NSMakeRange(0, 0);
108     // NOTE: If the default changes to 'NO' then the intialization of
109     // p_antialias in option.c must change as well.
110     antialias = YES;
112     return self;
115 - (void)dealloc
117     LOG_DEALLOC
119      if (markedText) {
120          imRange = NSMakeRange(0, 0);
121          [markedText release];
122          markedText = nil;
123     }
125     if (invertRects) {
126         free(invertRects);
127         invertRects = NULL;
128         numInvertRects = 0;
129     }
131     [helper setTextView:nil];
132     [helper dealloc];  helper = nil;
134     [super dealloc];
137 - (BOOL)shouldDrawInsertionPoint
139     // NOTE: The insertion point is drawn manually in drawRect:.  It would be
140     // nice to be able to use the insertion point related methods of
141     // NSTextView, but it seems impossible to get them to work properly (search
142     // the cocoabuilder archives).
143     return NO;
146 - (void)setShouldDrawInsertionPoint:(BOOL)on
148     shouldDrawInsertionPoint = on;
151 - (void)setPreEditRow:(int)row column:(int)col
153     preEditRow = row;
154     preEditColumn = col;
157 #define MM_DEBUG_DRAWING 0
159 - (void)performBatchDrawWithData:(NSData *)data
161     MMTextStorage *textStorage = (MMTextStorage *)[self textStorage];
162     if (!textStorage)
163         return;
165     const void *bytes = [data bytes];
166     const void *end = bytes + [data length];
167     int cursorRow = -1, cursorCol = 0;
169 #if MM_DEBUG_DRAWING
170     NSLog(@"====> BEGIN %s", _cmd);
171 #endif
172     [textStorage beginEditing];
174     // TODO: Sanity check input
176     while (bytes < end) {
177         int type = *((int*)bytes);  bytes += sizeof(int);
179         if (ClearAllDrawType == type) {
180 #if MM_DEBUG_DRAWING
181             NSLog(@"   Clear all");
182 #endif
183             [textStorage clearAll];
184         } else if (ClearBlockDrawType == type) {
185             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
186             int row1 = *((int*)bytes);  bytes += sizeof(int);
187             int col1 = *((int*)bytes);  bytes += sizeof(int);
188             int row2 = *((int*)bytes);  bytes += sizeof(int);
189             int col2 = *((int*)bytes);  bytes += sizeof(int);
191 #if MM_DEBUG_DRAWING
192             NSLog(@"   Clear block (%d,%d) -> (%d,%d)", row1, col1,
193                     row2,col2);
194 #endif
195             [textStorage clearBlockFromRow:row1 column:col1
196                     toRow:row2 column:col2
197                     color:[NSColor colorWithArgbInt:color]];
198         } else if (DeleteLinesDrawType == type) {
199             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
200             int row = *((int*)bytes);  bytes += sizeof(int);
201             int count = *((int*)bytes);  bytes += sizeof(int);
202             int bot = *((int*)bytes);  bytes += sizeof(int);
203             int left = *((int*)bytes);  bytes += sizeof(int);
204             int right = *((int*)bytes);  bytes += sizeof(int);
206 #if MM_DEBUG_DRAWING
207             NSLog(@"   Delete %d line(s) from %d", count, row);
208 #endif
209             [textStorage deleteLinesFromRow:row lineCount:count
210                     scrollBottom:bot left:left right:right
211                            color:[NSColor colorWithArgbInt:color]];
212         } else if (DrawStringDrawType == type) {
213             int bg = *((int*)bytes);  bytes += sizeof(int);
214             int fg = *((int*)bytes);  bytes += sizeof(int);
215             int sp = *((int*)bytes);  bytes += sizeof(int);
216             int row = *((int*)bytes);  bytes += sizeof(int);
217             int col = *((int*)bytes);  bytes += sizeof(int);
218             int cells = *((int*)bytes);  bytes += sizeof(int);
219             int flags = *((int*)bytes);  bytes += sizeof(int);
220             int len = *((int*)bytes);  bytes += sizeof(int);
221             NSString *string = [[NSString alloc]
222                     initWithBytes:(void*)bytes length:len
223                          encoding:NSUTF8StringEncoding];
224             bytes += len;
226 #if MM_DEBUG_DRAWING
227             NSLog(@"   Draw string at (%d,%d) length=%d flags=%d fg=0x%x "
228                     "bg=0x%x sp=0x%x (%@)", row, col, len, flags, fg, bg, sp,
229                     len > 0 ? [string substringToIndex:1] : @"");
230 #endif
231             // NOTE: If this is a call to draw the (block) cursor, then cancel
232             // any previous request to draw the insertion point, or it might
233             // get drawn as well.
234             if (flags & DRAW_CURSOR) {
235                 [self setShouldDrawInsertionPoint:NO];
236                 //NSColor *color = [NSColor colorWithRgbInt:bg];
237                 //[self drawInsertionPointAtRow:row column:col
238                 //                            shape:MMInsertionPointBlock
239                 //                            color:color];
240             }
242             [textStorage drawString:string
243                               atRow:row column:col cells:cells
244                           withFlags:flags
245                     foregroundColor:[NSColor colorWithRgbInt:fg]
246                     backgroundColor:[NSColor colorWithArgbInt:bg]
247                        specialColor:[NSColor colorWithRgbInt:sp]];
249             [string release];
250         } else if (InsertLinesDrawType == type) {
251             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
252             int row = *((int*)bytes);  bytes += sizeof(int);
253             int count = *((int*)bytes);  bytes += sizeof(int);
254             int bot = *((int*)bytes);  bytes += sizeof(int);
255             int left = *((int*)bytes);  bytes += sizeof(int);
256             int right = *((int*)bytes);  bytes += sizeof(int);
258 #if MM_DEBUG_DRAWING
259             NSLog(@"   Insert %d line(s) at row %d", count, row);
260 #endif
261             [textStorage insertLinesAtRow:row lineCount:count
262                              scrollBottom:bot left:left right:right
263                                     color:[NSColor colorWithArgbInt:color]];
264         } else if (DrawCursorDrawType == type) {
265             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
266             int row = *((int*)bytes);  bytes += sizeof(int);
267             int col = *((int*)bytes);  bytes += sizeof(int);
268             int shape = *((int*)bytes);  bytes += sizeof(int);
269             int percent = *((int*)bytes);  bytes += sizeof(int);
271 #if MM_DEBUG_DRAWING
272             NSLog(@"   Draw cursor at (%d,%d)", row, col);
273 #endif
274             [self drawInsertionPointAtRow:row column:col shape:shape
275                                      fraction:percent
276                                         color:[NSColor colorWithRgbInt:color]];
277         } else if (DrawInvertedRectDrawType == type) {
278             int row = *((int*)bytes);  bytes += sizeof(int);
279             int col = *((int*)bytes);  bytes += sizeof(int);
280             int nr = *((int*)bytes);  bytes += sizeof(int);
281             int nc = *((int*)bytes);  bytes += sizeof(int);
282             int invert = *((int*)bytes);  bytes += sizeof(int);
284 #if MM_DEBUG_DRAWING
285             NSLog(@"   Draw inverted rect: row=%d col=%d nrows=%d ncols=%d",
286                     row, col, nr, nc);
287 #endif
288             [self drawInvertedRectAtRow:row column:col numRows:nr numColumns:nc
289                                  invert:invert];
290         } else if (SetCursorPosDrawType == type) {
291             cursorRow = *((int*)bytes);  bytes += sizeof(int);
292             cursorCol = *((int*)bytes);  bytes += sizeof(int);
293         } else {
294             NSLog(@"WARNING: Unknown draw type (type=%d)", type);
295         }
296     }
298     [textStorage endEditing];
300     if (cursorRow >= 0) {
301         unsigned off = [textStorage characterIndexForRow:cursorRow
302                                                   column:cursorCol];
303         unsigned maxoff = [[textStorage string] length];
304         if (off > maxoff) off = maxoff;
306         [self setSelectedRange:NSMakeRange(off, 0)];
307     }
309     // NOTE: During resizing, Cocoa only sends draw messages before Vim's rows
310     // and columns are changed (due to ipc delays). Force a redraw here.
311     [self displayIfNeeded];
313 #if MM_DEBUG_DRAWING
314     NSLog(@"<==== END   %s", _cmd);
315 #endif
318 - (void)setMouseShape:(int)shape
320     [helper setMouseShape:shape];
323 - (void)setAntialias:(BOOL)state
325     antialias = state;
328 - (NSFont *)font
330     return [(MMTextStorage*)[self textStorage] font];
333 - (void)setFont:(NSFont *)newFont
335     [(MMTextStorage*)[self textStorage] setFont:newFont];
338 - (void)setWideFont:(NSFont *)newFont
340     [(MMTextStorage*)[self textStorage] setWideFont:newFont];
343 - (NSSize)cellSize
345     return [(MMTextStorage*)[self textStorage] cellSize];
348 - (void)setLinespace:(float)newLinespace
350     return [(MMTextStorage*)[self textStorage] setLinespace:newLinespace];
353 - (int)maxRows
355     MMTextStorage *ts = (MMTextStorage *)[self textStorage];
356     return [ts maxRows];
359 - (void)getMaxRows:(int*)rows columns:(int*)cols
361     return [(MMTextStorage*)[self textStorage] getMaxRows:rows columns:cols];
364 - (void)setMaxRows:(int)rows columns:(int)cols
366     return [(MMTextStorage*)[self textStorage] setMaxRows:rows columns:cols];
369 - (NSRect)rectForRowsInRange:(NSRange)range
371     return [(MMTextStorage*)[self textStorage] rectForRowsInRange:range];
374 - (NSRect)rectForColumnsInRange:(NSRange)range
376     return [(MMTextStorage*)[self textStorage] rectForColumnsInRange:range];
379 - (void)setDefaultColorsBackground:(NSColor *)bgColor
380                         foreground:(NSColor *)fgColor
382     [self setBackgroundColor:bgColor];
383     return [(MMTextStorage*)[self textStorage]
384             setDefaultColorsBackground:bgColor foreground:fgColor];
387 - (NSSize)constrainRows:(int *)rows columns:(int *)cols toSize:(NSSize)size
389     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
390     int right = [ud integerForKey:MMTextInsetRightKey];
391     int bot = [ud integerForKey:MMTextInsetBottomKey];
393     size.width -= [self textContainerOrigin].x + right;
394     size.height -= [self textContainerOrigin].y + bot;
396     NSSize newSize = [(MMTextStorage*)[self textStorage] fitToSize:size
397                                                               rows:rows
398                                                            columns:cols];
400     newSize.width += [self textContainerOrigin].x + right;
401     newSize.height += [self textContainerOrigin].y + bot;
403     return newSize;
406 - (NSSize)desiredSize
408     NSSize size = [(MMTextStorage*)[self textStorage] size];
410     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
411     int right = [ud integerForKey:MMTextInsetRightKey];
412     int bot = [ud integerForKey:MMTextInsetBottomKey];
414     size.width += [self textContainerOrigin].x + right;
415     size.height += [self textContainerOrigin].y + bot;
417     return size;
420 - (NSSize)minSize
422     NSSize cellSize = [(MMTextStorage*)[self textStorage] cellSize];
423     NSSize size = { MMMinColumns*cellSize.width, MMMinRows*cellSize.height };
425     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
426     int right = [ud integerForKey:MMTextInsetRightKey];
427     int bot = [ud integerForKey:MMTextInsetBottomKey];
429     size.width += [self textContainerOrigin].x + right;
430     size.height += [self textContainerOrigin].y + bot;
432     return size;
435 - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column
437     MMTextStorage *ts = (MMTextStorage*)[self textStorage];
438     NSSize cellSize = [ts cellSize];
439     if (!(cellSize.width > 0 && cellSize.height > 0))
440         return NO;
441     NSPoint origin = [self textContainerOrigin];
443     if (row) *row = floor((point.y-origin.y-1) / cellSize.height);
444     if (column) *column = floor((point.x-origin.x-1) / cellSize.width);
446     //NSLog(@"convertPoint:%@ toRow:%d column:%d", NSStringFromPoint(point),
447     //        *row, *column);
449     return YES;
452 - (BOOL)isOpaque
454     return NO;
457 - (void)drawRect:(NSRect)rect
459     NSGraphicsContext *context = [NSGraphicsContext currentContext];
460     [context setShouldAntialias:antialias];
462     [super drawRect:rect];
464     if (invertRects) {
465         CGContextRef cgctx = (CGContextRef)[context graphicsPort];
466         CGContextSaveGState(cgctx);
467         CGContextSetBlendMode(cgctx, kCGBlendModeDifference);
468         CGContextSetRGBFillColor(cgctx, 1.0, 1.0, 1.0, 1.0);
470         int i;
471         CGRect *rect = (CGRect*)invertRects;
472         for (i = 0; i < numInvertRects; ++i)
473             CGContextFillRect(cgctx, rect[i]);
475         CGContextRestoreGState(cgctx);
477         free(invertRects);
478         invertRects = NULL;
479         numInvertRects = 0;
480     }
482     if ([self hasMarkedText]) {
483         shouldDrawInsertionPoint = YES;
484         MMTextStorage *ts = (MMTextStorage*)[self textStorage];
485         NSSize inset = [self textContainerInset];
487         // HACK! Get the baseline of the zeroth glyph and use that as the
488         // baseline for the marked text.  (Is there a better way to figure out
489         // what baseline NSTextView uses?)
490         NSLayoutManager *lm = [self layoutManager];
491         NSTypesetter *tsr = [lm typesetter];
492         float baseline = [tsr baselineOffsetInLayoutManager:lm glyphIndex:0];
494         // Also adjust for 'linespace' option (TODO: Why not .5*linespace?)
495         baseline -= floor([ts linespace]);
497         inset.height -= baseline;
499         int len = [markedText length];
500         // The following implementation should be re-written with
501         // more efficient way...
503         // Calculate how many wide-font characters can be inserted at
504         // a first line, and draw those characters.
505         int cols = ([ts actualColumns] - insertionPointColumn);
506         NSFont *theFont = [[self markedTextAttributes]
507                 valueForKey:NSFontAttributeName];
508         if (theFont == [ts fontWide])
509             cols = cols / 2;
510         int done = 0;
511         int lend = cols > len ? len : cols;
512         NSAttributedString *aString = [markedText attributedSubstringFromRange:
513                 NSMakeRange(done, lend)];
514         [aString drawAtPoint:NSMakePoint(
515                 preEditColumn*[ts cellSize].width + inset.width,
516                 preEditRow*[ts cellSize].height + inset.height)];
518         done = lend;
519         // Check whether there're charecters that aren't drawn at
520         // the first line. If everything is already done, the follow
521         // check fails.
522         if (done != len) {
523             int r;
524             // Calculate How many rows are needed to draw all the left
525             // characters.
526             int rows = (len - done) / ([ts actualColumns] / 2) + 1;
527             for (r = 1; r <= rows; r++) {
528                 lend = len - done > [ts actualColumns] / 2
529                         ? [ts actualColumns] / 2 : len - done;
530                 aString = [markedText attributedSubstringFromRange:
531                         NSMakeRange(done, lend)];
532                 [aString drawAtPoint:NSMakePoint(
533                         inset.width,
534                         (preEditRow + r)*[ts cellSize].height
535                             + inset.height)];
536                 done += lend;
537             }
538         }
539     }
541     if (shouldDrawInsertionPoint) {
542         MMTextStorage *ts = (MMTextStorage*)[self textStorage];
544         NSRect ipRect = [ts boundingRectForCharacterAtRow:preEditRow
545                                                    column:preEditColumn];
546         ipRect.origin.x += [self textContainerOrigin].x;
547         ipRect.origin.y += [self textContainerOrigin].y;
549         // Draw insertion point inside marked text.
550         if ([self hasMarkedText]) {
551             NSFont *theFont = [[self markedTextAttributes]
552                     valueForKey:NSFontAttributeName];
553             if (theFont == [ts font])
554                 ipRect.origin.x += [ts cellSize].width *
555                                    (imRange.location + imRange.length);
556             else
557                 ipRect.origin.x += [ts cellSize].width * 2 *
558                                    (imRange.location + imRange.length);
559         }
561         if (MMInsertionPointHorizontal == insertionPointShape) {
562             int frac = ([ts cellSize].height * insertionPointFraction + 99)/100;
563             ipRect.origin.y += ipRect.size.height - frac;
564             ipRect.size.height = frac;
565         } else if (MMInsertionPointVertical == insertionPointShape) {
566             int frac = ([ts cellSize].width * insertionPointFraction + 99)/100;
567             ipRect.size.width = frac;
568         } else if (MMInsertionPointVerticalRight == insertionPointShape) {
569             int frac = ([ts cellSize].width * insertionPointFraction + 99)/100;
570             ipRect.origin.x += ipRect.size.width - frac;
571             ipRect.size.width = frac;
572         }
574         [[self insertionPointColor] set];
575         if (MMInsertionPointHollow == insertionPointShape) {
576             NSFrameRect(ipRect);
577         } else {
578             NSRectFill(ipRect);
579         }
581         // NOTE: We only draw the cursor once and rely on Vim to say when it
582         // should be drawn again.
583         shouldDrawInsertionPoint = NO;
585         //NSLog(@"%s draw insertion point %@ shape=%d color=%@", _cmd,
586         //        NSStringFromRect(ipRect), insertionPointShape,
587         //        [self insertionPointColor]);
588     }
590 #if 0
591     // this code invalidates the shadow, so we don't 
592     // get shifting ghost text on scroll and resize
593     // but makes speed unusable
594     MMTextStorage *ts = (MMTextStorage*)[self textStorage];
595     if ([ts defaultBackgroundAlpha] < 1.0f) {
596         if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_1)
597         {
598             [[self window] setHasShadow:NO];
599             [[self window] setHasShadow:YES];
600         }
601         else
602             [[self window] invalidateShadow];
604     }
605 #endif
608 - (void)keyDown:(NSEvent *)event
610     [helper keyDown:event];
613 - (void)insertText:(id)string
615     [helper insertText:string];
618 - (void)doCommandBySelector:(SEL)selector
620     [helper doCommandBySelector:selector];
623 - (BOOL)performKeyEquivalent:(NSEvent *)event
625     return [helper performKeyEquivalent:event];
628 - (BOOL)hasMarkedText
630     //NSLog(@"%s", _cmd);
631     //return markedText && [markedText length] > 0;
632     return markedRange.length > 0 ? YES : NO;
635 - (NSRange)markedRange
637     if ([self hasMarkedText]) {
638         return markedRange;
639     } else
640         return NSMakeRange(NSNotFound, 0);
643 - (NSDictionary *)markedTextAttributes
645     return markedTextAttributes;
648 - (void)setMarkedTextAttributes:(NSDictionary *)attr
650     if (attr != markedTextAttributes) {
651         [markedTextAttributes release];
652         markedTextAttributes = [attr retain];
653     }
657 - (void)setMarkedText:(id)text selectedRange:(NSRange)range
659     //NSLog(@"setMarkedText:'%@' selectedRange:%@", text,
660     //        NSStringFromRange(range));
662     MMTextStorage *ts = (MMTextStorage*)[self textStorage];
663     if (!ts)
664         return;
665     [self unmarkText];
667     if (text && [text length] > 0) {
668         if ([text isKindOfClass:[NSAttributedString class]]) {
669             [self setMarkedTextAttributes:
670                 [NSDictionary dictionaryWithObjectsAndKeys:
671                     [ts fontWide], NSFontAttributeName,
672                     [ts defaultBackgroundColor], NSBackgroundColorAttributeName,
673                     [ts defaultForegroundColor], NSForegroundColorAttributeName,
674                     nil]];
675             markedText = [[NSMutableAttributedString alloc]
676                     initWithString:[text string]
677                         attributes:[self markedTextAttributes]];
678         } else {
679             [self setMarkedTextAttributes:
680                 [NSDictionary dictionaryWithObjectsAndKeys:
681                     [ts font], NSFontAttributeName,
682                     [ts defaultBackgroundColor], NSBackgroundColorAttributeName,
683                     [ts defaultForegroundColor], NSForegroundColorAttributeName,
684                     nil]];
685             markedText = [[NSMutableAttributedString alloc]
686                     initWithString:text
687                         attributes:[self markedTextAttributes]];
688         }
690         markedRange = NSMakeRange(0, [markedText length]);
691         if (markedRange.length) {
692             [markedText addAttribute:NSUnderlineStyleAttributeName
693                                value:[NSNumber numberWithInt:1]
694                                range:markedRange];
695         }
696         imRange = range;
697         if (range.length) {
698             [markedText addAttribute:NSUnderlineStyleAttributeName
699                                value:[NSNumber numberWithInt:2]
700                                range:range];
701         }
702     }
703     [self setNeedsDisplay: YES];
706 - (void)unmarkText
708     //NSLog(@"%s", _cmd);
709     imRange = NSMakeRange(0, 0);
710     markedRange = NSMakeRange(NSNotFound, 0);
711     [markedText release];
712     markedText = nil;
715 - (NSRect)firstRectForCharacterRange:(NSRange)range
717     //NSLog(@"%s%@", _cmd, NSStringFromRange(range));
718     // HACK!  This method is called when the input manager wants to pop up an
719     // auxiliary window.  The position where this should be is controller by
720     // Vim by sending SetPreEditPositionMsgID so compute a position based on
721     // the pre-edit (row,column) pair.
722     MMTextStorage *ts = (MMTextStorage*)[self textStorage];
724     NSRect rect = [ts boundingRectForCharacterAtRow:preEditRow
725                                              column:preEditColumn];
726     rect.origin.x += [self textContainerOrigin].x;
727     rect.origin.y += [self textContainerOrigin].y + [ts cellSize].height;
729     rect.origin = [self convertPoint:rect.origin toView:nil];
730     rect.origin = [[self window] convertBaseToScreen:rect.origin];
732     return rect;
735 - (void)scrollWheel:(NSEvent *)event
737     [helper scrollWheel:event];
740 - (void)mouseDown:(NSEvent *)event
742     [helper mouseDown:event];
745 - (void)rightMouseDown:(NSEvent *)event
747     [helper mouseDown:event];
750 - (void)otherMouseDown:(NSEvent *)event
752     [helper mouseDown:event];
755 - (void)mouseUp:(NSEvent *)event
757     [helper mouseUp:event];
760 - (void)rightMouseUp:(NSEvent *)event
762     [helper mouseUp:event];
765 - (void)otherMouseUp:(NSEvent *)event
767     [helper mouseUp:event];
770 - (void)mouseDragged:(NSEvent *)event
772     [helper mouseDragged:event];
775 - (void)rightMouseDragged:(NSEvent *)event
777     [helper mouseDragged:event];
780 - (void)otherMouseDragged:(NSEvent *)event
782     [helper mouseDragged:event];
785 - (void)mouseMoved:(NSEvent *)event
787     [helper mouseMoved:event];
790 - (void)mouseEntered:(NSEvent *)event
792     [helper mouseEntered:event];
795 - (void)mouseExited:(NSEvent *)event
797     [helper mouseExited:event];
800 - (void)setFrame:(NSRect)frame
802     [super setFrame:frame];
803     [helper setFrame:frame];
806 - (void)viewDidMoveToWindow
808     [helper viewDidMoveToWindow];
811 - (void)viewWillMoveToWindow:(NSWindow *)newWindow
813     [helper viewWillMoveToWindow:newWindow];
816 - (NSMenu*)menuForEvent:(NSEvent *)event
818     // HACK! Return nil to disable NSTextView's popup menus (Vim provides its
819     // own).  Called when user Ctrl-clicks in the view (this is already handled
820     // in rightMouseDown:).
821     return nil;
824 - (NSArray *)acceptableDragTypes
826     return [NSArray arrayWithObjects:NSFilenamesPboardType,
827            NSStringPboardType, nil];
830 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
832     return [helper performDragOperation:sender];
835 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
837     return [helper draggingEntered:sender];
840 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
842     return [helper draggingUpdated:sender];
845 - (void)changeFont:(id)sender
847     MMTextStorage *ts = (MMTextStorage*)[self textStorage];
848     if (!ts) return;
850     NSFont *oldFont = [ts font];
851     NSFont *newFont = [sender convertFont:oldFont];
853     if (newFont) {
854         NSString *name = [newFont displayName];
855         unsigned len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
856         if (len > 0) {
857             NSMutableData *data = [NSMutableData data];
858             float pointSize = [newFont pointSize];
860             [data appendBytes:&pointSize length:sizeof(float)];
862             ++len;  // include NUL byte
863             [data appendBytes:&len length:sizeof(unsigned)];
864             [data appendBytes:[name UTF8String] length:len];
866             [[self vimController] sendMessage:SetFontMsgID data:data];
867         }
868     }
871 - (void)resetCursorRects
873     // No need to set up cursor rects since Vim handles cursor changes.
876 - (void)updateFontPanel
878     // The font panel is updated whenever the font is set.
883 // NOTE: The menu items cut/copy/paste/undo/redo/select all/... must be bound
884 // to the same actions as in IB otherwise they will not work with dialogs.  All
885 // we do here is forward these actions to the Vim process.
887 - (IBAction)cut:(id)sender
889     [[self windowController] vimMenuItemAction:sender];
892 - (IBAction)copy:(id)sender
894     [[self windowController] vimMenuItemAction:sender];
897 - (IBAction)paste:(id)sender
899     [[self windowController] vimMenuItemAction:sender];
902 - (IBAction)undo:(id)sender
904     [[self windowController] vimMenuItemAction:sender];
907 - (IBAction)redo:(id)sender
909     [[self windowController] vimMenuItemAction:sender];
912 - (IBAction)selectAll:(id)sender
914     [[self windowController] vimMenuItemAction:sender];
917 - (BOOL)validateMenuItem:(NSMenuItem *)item
919     if ([item action] == @selector(cut:)
920             || [item action] == @selector(copy:)
921             || [item action] == @selector(paste:)
922             || [item action] == @selector(undo:)
923             || [item action] == @selector(redo:)
924             || [item action] == @selector(selectAll:))
925         return [item tag];
927     return YES;
929 @end // MMTextView
934 @implementation MMTextView (Private)
936 - (BOOL)convertRow:(int)row column:(int)column toPoint:(NSPoint *)point
938     MMTextStorage *ts = (MMTextStorage*)[self textStorage];
939     NSSize cellSize = [ts cellSize];
940     if (!(point && cellSize.width > 0 && cellSize.height > 0))
941         return NO;
943     *point = [self textContainerOrigin];
944     point->x += column * cellSize.width;
945     point->y += row * cellSize.height;
947     return YES;
950 - (BOOL)convertRow:(int)row column:(int)column numRows:(int)nr
951         numColumns:(int)nc toRect:(NSRect *)rect
953     MMTextStorage *ts = (MMTextStorage*)[self textStorage];
954     NSSize cellSize = [ts cellSize];
955     if (!(rect && cellSize.width > 0 && cellSize.height > 0))
956         return NO;
958     rect->origin = [self textContainerOrigin];
959     rect->origin.x += column * cellSize.width;
960     rect->origin.y += row * cellSize.height;
961     rect->size.width = cellSize.width * nc;
962     rect->size.height = cellSize.height * nr;
964     return YES;
967 - (MMWindowController *)windowController
969     id windowController = [[self window] windowController];
970     if ([windowController isKindOfClass:[MMWindowController class]])
971         return (MMWindowController*)windowController;
972     return nil;
975 - (MMVimController *)vimController
977     return [[self windowController] vimController];
980 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
981                        fraction:(int)percent color:(NSColor *)color
983     //NSLog(@"drawInsertionPointAtRow:%d column:%d shape:%d color:%@",
984     //        row, col, shape, color);
986     // This only stores where to draw the insertion point, the actual drawing
987     // is done in drawRect:.
988     shouldDrawInsertionPoint = YES;
989     insertionPointRow = row;
990     insertionPointColumn = col;
991     insertionPointShape = shape;
992     insertionPointFraction = percent;
994     [self setInsertionPointColor:color];
997 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nrows
998                    numColumns:(int)ncols invert:(int)invert
1000     if (invert) {
1001         // The result should be inverted.
1002         int n = numInvertRects++;
1003         invertRects = reallocf(invertRects,
1004                                numInvertRects*sizeof(NSRect));
1005         if (NULL != invertRects) {
1006             [self convertRow:row column:col numRows:nrows numColumns:ncols
1007                       toRect:&invertRects[n]];
1008             [self setNeedsDisplayInRect:invertRects[n]];
1009         } else {
1010             n = numInvertRects = 0;
1011         }
1012     } else {
1013         // The result should look normal; all we need to do is to mark
1014         // the rect for redrawing and Cocoa will redraw the text.
1015         NSRect rect;
1016         [self convertRow:row column:col numRows:nrows numColumns:ncols
1017                   toRect:&rect];
1018         [self setNeedsDisplayInRect:rect];
1019     }
1022 @end // MMTextView (Private)