Add IM support to ATSUI renderer
[MacVim.git] / src / MacVim / MMTextView.m
blobd9faf52334f42c2711e3d0929094390afcdcce9e
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 - (MMWindowController *)windowController;
39 - (MMVimController *)vimController;
40 - (void)setShouldDrawInsertionPoint:(BOOL)on;
41 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
42                        fraction:(int)percent;
43 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nrows
44                    numColumns:(int)ncols invert:(int)invert;
45 @end
49 @implementation MMTextView
51 - (id)initWithFrame:(NSRect)frame
53     // Set up a Cocoa text system.  Note that the textStorage is released in
54     // -[MMVimView dealloc].
55     MMTextStorage *textStorage = [[MMTextStorage alloc] init];
56     NSLayoutManager *lm = [[NSLayoutManager alloc] init];
57     NSTextContainer *tc = [[NSTextContainer alloc] initWithContainerSize:
58                     NSMakeSize(1.0e7,1.0e7)];
60     NSString *typesetterString = [[NSUserDefaults standardUserDefaults]
61             stringForKey:MMTypesetterKey];
62     if ([typesetterString isEqual:@"MMTypesetter"]) {
63         NSTypesetter *typesetter = [[MMTypesetter alloc] init];
64         [lm setTypesetter:typesetter];
65         [typesetter release];
66     } else if ([typesetterString isEqual:@"MMTypesetter2"]) {
67         NSTypesetter *typesetter = [[MMTypesetter2 alloc] init];
68         [lm setTypesetter:typesetter];
69         [typesetter release];
70     } else {
71         // Only MMTypesetter supports different cell width multipliers.
72         [[NSUserDefaults standardUserDefaults]
73                 setFloat:1.0 forKey:MMCellWidthMultiplierKey];
74     }
76     // The characters in the text storage are in display order, so disable
77     // bidirectional text processing (this call is 10.4 only).
78     [[lm typesetter] setBidiProcessingEnabled:NO];
80     [tc setWidthTracksTextView:NO];
81     [tc setHeightTracksTextView:NO];
82     [tc setLineFragmentPadding:0];
84     [textStorage addLayoutManager:lm];
85     [lm addTextContainer:tc];
87     // The text storage retains the layout manager which in turn retains
88     // the text container.
89     [tc autorelease];
90     [lm autorelease];
92     // NOTE: This will make the text storage the principal owner of the text
93     // system.  Releasing the text storage will in turn release the layout
94     // manager, the text container, and finally the text view (self).  This
95     // complicates deallocation somewhat, see -[MMVimView dealloc].
96     if (![super initWithFrame:frame textContainer:tc]) {
97         [textStorage release];
98         return nil;
99     }
101     helper = [[MMTextViewHelper alloc] init];
102     [helper setTextView:self];
104     // NOTE: If the default changes to 'NO' then the intialization of
105     // p_antialias in option.c must change as well.
106     antialias = YES;
108     return self;
111 - (void)dealloc
113     LOG_DEALLOC
115     if (invertRects) {
116         free(invertRects);
117         invertRects = NULL;
118         numInvertRects = 0;
119     }
121     [helper setTextView:nil];
122     [helper dealloc];  helper = nil;
124     [super dealloc];
127 - (BOOL)shouldDrawInsertionPoint
129     // NOTE: The insertion point is drawn manually in drawRect:.  It would be
130     // nice to be able to use the insertion point related methods of
131     // NSTextView, but it seems impossible to get them to work properly (search
132     // the cocoabuilder archives).
133     return NO;
136 - (void)setPreEditRow:(int)row column:(int)col
138     [helper setPreEditRow:row column:col];
141 #define MM_DEBUG_DRAWING 0
143 - (void)performBatchDrawWithData:(NSData *)data
145     MMTextStorage *textStorage = (MMTextStorage *)[self textStorage];
146     if (!textStorage)
147         return;
149     const void *bytes = [data bytes];
150     const void *end = bytes + [data length];
151     int cursorRow = -1, cursorCol = 0;
153 #if MM_DEBUG_DRAWING
154     NSLog(@"====> BEGIN %s", _cmd);
155 #endif
156     [textStorage beginEditing];
158     // TODO: Sanity check input
160     while (bytes < end) {
161         int type = *((int*)bytes);  bytes += sizeof(int);
163         if (ClearAllDrawType == type) {
164 #if MM_DEBUG_DRAWING
165             NSLog(@"   Clear all");
166 #endif
167             [textStorage clearAll];
168         } else if (ClearBlockDrawType == type) {
169             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
170             int row1 = *((int*)bytes);  bytes += sizeof(int);
171             int col1 = *((int*)bytes);  bytes += sizeof(int);
172             int row2 = *((int*)bytes);  bytes += sizeof(int);
173             int col2 = *((int*)bytes);  bytes += sizeof(int);
175 #if MM_DEBUG_DRAWING
176             NSLog(@"   Clear block (%d,%d) -> (%d,%d)", row1, col1,
177                     row2,col2);
178 #endif
179             [textStorage clearBlockFromRow:row1 column:col1
180                     toRow:row2 column:col2
181                     color:[NSColor colorWithArgbInt:color]];
182         } else if (DeleteLinesDrawType == type) {
183             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
184             int row = *((int*)bytes);  bytes += sizeof(int);
185             int count = *((int*)bytes);  bytes += sizeof(int);
186             int bot = *((int*)bytes);  bytes += sizeof(int);
187             int left = *((int*)bytes);  bytes += sizeof(int);
188             int right = *((int*)bytes);  bytes += sizeof(int);
190 #if MM_DEBUG_DRAWING
191             NSLog(@"   Delete %d line(s) from %d", count, row);
192 #endif
193             [textStorage deleteLinesFromRow:row lineCount:count
194                     scrollBottom:bot left:left right:right
195                            color:[NSColor colorWithArgbInt:color]];
196         } else if (DrawStringDrawType == type) {
197             int bg = *((int*)bytes);  bytes += sizeof(int);
198             int fg = *((int*)bytes);  bytes += sizeof(int);
199             int sp = *((int*)bytes);  bytes += sizeof(int);
200             int row = *((int*)bytes);  bytes += sizeof(int);
201             int col = *((int*)bytes);  bytes += sizeof(int);
202             int cells = *((int*)bytes);  bytes += sizeof(int);
203             int flags = *((int*)bytes);  bytes += sizeof(int);
204             int len = *((int*)bytes);  bytes += sizeof(int);
205             NSString *string = [[NSString alloc]
206                     initWithBytes:(void*)bytes length:len
207                          encoding:NSUTF8StringEncoding];
208             bytes += len;
210 #if MM_DEBUG_DRAWING
211             NSLog(@"   Draw string at (%d,%d) length=%d flags=%d fg=0x%x "
212                     "bg=0x%x sp=0x%x (%@)", row, col, len, flags, fg, bg, sp,
213                     len > 0 ? [string substringToIndex:1] : @"");
214 #endif
215             // NOTE: If this is a call to draw the (block) cursor, then cancel
216             // any previous request to draw the insertion point, or it might
217             // get drawn as well.
218             if (flags & DRAW_CURSOR)
219                 [self setShouldDrawInsertionPoint:NO];
221             [textStorage drawString:string
222                               atRow:row column:col cells:cells
223                           withFlags:flags
224                     foregroundColor:[NSColor colorWithRgbInt:fg]
225                     backgroundColor:[NSColor colorWithArgbInt:bg]
226                        specialColor:[NSColor colorWithRgbInt:sp]];
228             [string release];
229         } else if (InsertLinesDrawType == type) {
230             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
231             int row = *((int*)bytes);  bytes += sizeof(int);
232             int count = *((int*)bytes);  bytes += sizeof(int);
233             int bot = *((int*)bytes);  bytes += sizeof(int);
234             int left = *((int*)bytes);  bytes += sizeof(int);
235             int right = *((int*)bytes);  bytes += sizeof(int);
237 #if MM_DEBUG_DRAWING
238             NSLog(@"   Insert %d line(s) at row %d", count, row);
239 #endif
240             [textStorage insertLinesAtRow:row lineCount:count
241                              scrollBottom:bot left:left right:right
242                                     color:[NSColor colorWithArgbInt:color]];
243         } else if (DrawCursorDrawType == type) {
244             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
245             int row = *((int*)bytes);  bytes += sizeof(int);
246             int col = *((int*)bytes);  bytes += sizeof(int);
247             int shape = *((int*)bytes);  bytes += sizeof(int);
248             int percent = *((int*)bytes);  bytes += sizeof(int);
250 #if MM_DEBUG_DRAWING
251             NSLog(@"   Draw cursor at (%d,%d)", row, col);
252 #endif
253             [helper setInsertionPointColor:[NSColor colorWithRgbInt:color]];
254             [self drawInsertionPointAtRow:row column:col shape:shape
255                                  fraction:percent];
256         } else if (DrawInvertedRectDrawType == type) {
257             int row = *((int*)bytes);  bytes += sizeof(int);
258             int col = *((int*)bytes);  bytes += sizeof(int);
259             int nr = *((int*)bytes);  bytes += sizeof(int);
260             int nc = *((int*)bytes);  bytes += sizeof(int);
261             int invert = *((int*)bytes);  bytes += sizeof(int);
263 #if MM_DEBUG_DRAWING
264             NSLog(@"   Draw inverted rect: row=%d col=%d nrows=%d ncols=%d",
265                     row, col, nr, nc);
266 #endif
267             [self drawInvertedRectAtRow:row column:col numRows:nr numColumns:nc
268                                  invert:invert];
269         } else if (SetCursorPosDrawType == type) {
270             cursorRow = *((int*)bytes);  bytes += sizeof(int);
271             cursorCol = *((int*)bytes);  bytes += sizeof(int);
272         } else {
273             NSLog(@"WARNING: Unknown draw type (type=%d)", type);
274         }
275     }
277     [textStorage endEditing];
279     if (cursorRow >= 0) {
280         unsigned off = [textStorage characterIndexForRow:cursorRow
281                                                   column:cursorCol];
282         unsigned maxoff = [[textStorage string] length];
283         if (off > maxoff) off = maxoff;
285         [self setSelectedRange:NSMakeRange(off, 0)];
286     }
288     // NOTE: During resizing, Cocoa only sends draw messages before Vim's rows
289     // and columns are changed (due to ipc delays). Force a redraw here.
290     [self displayIfNeeded];
292 #if MM_DEBUG_DRAWING
293     NSLog(@"<==== END   %s", _cmd);
294 #endif
297 - (void)setMouseShape:(int)shape
299     [helper setMouseShape:shape];
302 - (void)setAntialias:(BOOL)state
304     antialias = state;
307 - (NSFont *)font
309     return [(MMTextStorage*)[self textStorage] font];
312 - (void)setFont:(NSFont *)newFont
314     [(MMTextStorage*)[self textStorage] setFont:newFont];
317 - (NSFont *)fontWide
319     return [(MMTextStorage*)[self textStorage] fontWide];
322 - (void)setWideFont:(NSFont *)newFont
324     [(MMTextStorage*)[self textStorage] setWideFont:newFont];
327 - (NSSize)cellSize
329     return [(MMTextStorage*)[self textStorage] cellSize];
332 - (void)setLinespace:(float)newLinespace
334     return [(MMTextStorage*)[self textStorage] setLinespace:newLinespace];
337 - (int)maxRows
339     MMTextStorage *ts = (MMTextStorage *)[self textStorage];
340     return [ts maxRows];
343 - (int)maxColumns
345     MMTextStorage *ts = (MMTextStorage *)[self textStorage];
346     return [ts maxColumns];
349 - (void)getMaxRows:(int*)rows columns:(int*)cols
351     return [(MMTextStorage*)[self textStorage] getMaxRows:rows columns:cols];
354 - (void)setMaxRows:(int)rows columns:(int)cols
356     return [(MMTextStorage*)[self textStorage] setMaxRows:rows columns:cols];
359 - (NSRect)rectForRowsInRange:(NSRange)range
361     return [(MMTextStorage*)[self textStorage] rectForRowsInRange:range];
364 - (NSRect)rectForColumnsInRange:(NSRange)range
366     return [(MMTextStorage*)[self textStorage] rectForColumnsInRange:range];
369 - (void)setDefaultColorsBackground:(NSColor *)bgColor
370                         foreground:(NSColor *)fgColor
372     [self setBackgroundColor:bgColor];
373     return [(MMTextStorage*)[self textStorage]
374             setDefaultColorsBackground:bgColor foreground:fgColor];
377 - (NSColor *)defaultBackgroundColor
379     return [(MMTextStorage*)[self textStorage] defaultBackgroundColor];
382 - (NSColor *)defaultForegroundColor
384     return [(MMTextStorage*)[self textStorage] defaultForegroundColor];
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 - (NSPoint)pointForRow:(int)row column:(int)col
454     // Return the upper-left coordinate for (row,column).
455     // NOTE: The coordinate system is flipped!
456     NSPoint pt = [self textContainerOrigin];
457     MMTextStorage *ts = (MMTextStorage*)[self textStorage];
458     NSSize cellSize = [ts cellSize];
460     pt.x += col * cellSize.width;
461     pt.y += row * cellSize.height;
463     return pt;
466 - (NSRect)rectForRow:(int)row column:(int)col numRows:(int)nr
467           numColumns:(int)nc
469     // Return the rect for the block which covers the specified rows and
470     // columns.  The upper-left corner is the origin of this rect.
471     // NOTE: The coordinate system is flipped!
472     NSRect rect;
473     MMTextStorage *ts = (MMTextStorage*)[self textStorage];
474     NSSize cellSize = [ts cellSize];
476     rect.origin = [self textContainerOrigin];
477     rect.origin.x += col * cellSize.width;
478     rect.origin.y += row * cellSize.height;
479     rect.size.width = cellSize.width * nc;
480     rect.size.height = cellSize.height * nr;
482     return rect;
485 - (BOOL)isOpaque
487     return NO;
490 - (void)drawRect:(NSRect)rect
492     NSGraphicsContext *context = [NSGraphicsContext currentContext];
493     [context setShouldAntialias:antialias];
495     [super drawRect:rect];
497     if (invertRects) {
498         CGContextRef cgctx = (CGContextRef)[context graphicsPort];
499         CGContextSaveGState(cgctx);
500         CGContextSetBlendMode(cgctx, kCGBlendModeDifference);
501         CGContextSetRGBFillColor(cgctx, 1.0, 1.0, 1.0, 1.0);
503         int i;
504         CGRect *rect = (CGRect*)invertRects;
505         for (i = 0; i < numInvertRects; ++i)
506             CGContextFillRect(cgctx, rect[i]);
508         CGContextRestoreGState(cgctx);
510         free(invertRects);
511         invertRects = NULL;
512         numInvertRects = 0;
513     }
515     if ([self hasMarkedText]) {
516         shouldDrawInsertionPoint = YES;
517         MMTextStorage *ts = (MMTextStorage*)[self textStorage];
518         NSSize inset = [self textContainerInset];
520         // HACK! Get the baseline of the zeroth glyph and use that as the
521         // baseline for the marked text.  (Is there a better way to figure out
522         // what baseline NSTextView uses?)
523         NSLayoutManager *lm = [self layoutManager];
524         NSTypesetter *tsr = [lm typesetter];
525         float baseline = [tsr baselineOffsetInLayoutManager:lm glyphIndex:0];
527         // Also adjust for 'linespace' option (TODO: Why not .5*linespace?)
528         baseline -= floor([ts linespace]);
530         inset.height -= baseline;
532         int len = [[helper markedText] length];
533         // The following implementation should be re-written with
534         // more efficient way...
536         // Calculate how many wide-font characters can be inserted at
537         // a first line, and draw those characters.
538         int cols = ([ts actualColumns] - insertionPointColumn);
539         NSFont *theFont = [[self markedTextAttributes]
540                 valueForKey:NSFontAttributeName];
541         if (theFont == [ts fontWide])
542             cols = cols / 2;
543         int done = 0;
544         int lend = cols > len ? len : cols;
545         NSAttributedString *aString = [[helper markedText]
546                 attributedSubstringFromRange:NSMakeRange(done, lend)];
547         [aString drawAtPoint:NSMakePoint(
548                 [helper preEditColumn]*[ts cellSize].width + inset.width,
549                 [helper preEditRow]*[ts cellSize].height + inset.height)];
551         done = lend;
552         // Check whether there're charecters that aren't drawn at
553         // the first line. If everything is already done, the follow
554         // check fails.
555         if (done != len) {
556             int r;
557             // Calculate How many rows are needed to draw all the left
558             // characters.
559             int rows = (len - done) / ([ts actualColumns] / 2) + 1;
560             for (r = 1; r <= rows; r++) {
561                 lend = len - done > [ts actualColumns] / 2
562                         ? [ts actualColumns] / 2 : len - done;
563                 aString = [[helper markedText] attributedSubstringFromRange:
564                         NSMakeRange(done, lend)];
565                 [aString drawAtPoint:NSMakePoint(
566                         inset.width,
567                         ([helper preEditRow] + r)*[ts cellSize].height
568                             + inset.height)];
569                 done += lend;
570             }
571         }
572     }
574     if (shouldDrawInsertionPoint) {
575         MMTextStorage *ts = (MMTextStorage*)[self textStorage];
577         NSRect ipRect = [ts boundingRectForCharacterAtRow:[helper preEditRow]
578                                                    column:[helper preEditColumn]];
579         ipRect.origin.x += [self textContainerOrigin].x;
580         ipRect.origin.y += [self textContainerOrigin].y;
582         // Draw insertion point inside marked text.
583         if ([self hasMarkedText]) {
584             NSFont *theFont = [[self markedTextAttributes]
585                     valueForKey:NSFontAttributeName];
586             if (theFont == [ts font])
587                 ipRect.origin.x += [ts cellSize].width *
588                                    ([helper imRange].location +
589                                    [helper imRange].length);
590             else
591                 ipRect.origin.x += [ts cellSize].width * 2 *
592                                    ([helper imRange].location +
593                                    [helper imRange].length);
594         }
596         if (MMInsertionPointHorizontal == insertionPointShape) {
597             int frac = ([ts cellSize].height * insertionPointFraction + 99)/100;
598             ipRect.origin.y += ipRect.size.height - frac;
599             ipRect.size.height = frac;
600         } else if (MMInsertionPointVertical == insertionPointShape) {
601             int frac = ([ts cellSize].width * insertionPointFraction + 99)/100;
602             ipRect.size.width = frac;
603         } else if (MMInsertionPointVerticalRight == insertionPointShape) {
604             int frac = ([ts cellSize].width * insertionPointFraction + 99)/100;
605             ipRect.origin.x += ipRect.size.width - frac;
606             ipRect.size.width = frac;
607         }
609         [[helper insertionPointColor] set];
610         if (MMInsertionPointHollow == insertionPointShape) {
611             NSFrameRect(ipRect);
612         } else {
613             NSRectFill(ipRect);
614         }
616         // NOTE: We only draw the cursor once and rely on Vim to say when it
617         // should be drawn again.
618         shouldDrawInsertionPoint = NO;
620         //NSLog(@"%s draw insertion point %@ shape=%d color=%@", _cmd,
621         //        NSStringFromRect(ipRect), insertionPointShape,
622         //        [helper insertionPointColor]);
623     }
625 #if 0
626     // this code invalidates the shadow, so we don't 
627     // get shifting ghost text on scroll and resize
628     // but makes speed unusable
629     MMTextStorage *ts = (MMTextStorage*)[self textStorage];
630     if ([ts defaultBackgroundAlpha] < 1.0f) {
631         if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_1)
632         {
633             [[self window] setHasShadow:NO];
634             [[self window] setHasShadow:YES];
635         }
636         else
637             [[self window] invalidateShadow];
639     }
640 #endif
643 - (void)keyDown:(NSEvent *)event
645     [helper keyDown:event];
648 - (void)insertText:(id)string
650     [helper insertText:string];
653 - (void)doCommandBySelector:(SEL)selector
655     [helper doCommandBySelector:selector];
658 - (BOOL)performKeyEquivalent:(NSEvent *)event
660     return [helper performKeyEquivalent:event];
663 - (BOOL)hasMarkedText
665     return [helper hasMarkedText];
668 - (NSRange)markedRange
670     return [helper markedRange];
673 - (NSDictionary *)markedTextAttributes
675     return [helper markedTextAttributes];
678 - (void)setMarkedTextAttributes:(NSDictionary *)attr
680     [helper setMarkedTextAttributes:attr];
683 - (void)setMarkedText:(id)text selectedRange:(NSRange)range
685     [helper setMarkedText:text selectedRange:range];
688 - (void)unmarkText
690     [helper unmarkText];
693 - (NSRect)firstRectForCharacterRange:(NSRange)range
695     return [helper firstRectForCharacterRange:range];
698 - (void)scrollWheel:(NSEvent *)event
700     [helper scrollWheel:event];
703 - (void)mouseDown:(NSEvent *)event
705     [helper mouseDown:event];
708 - (void)rightMouseDown:(NSEvent *)event
710     [helper mouseDown:event];
713 - (void)otherMouseDown:(NSEvent *)event
715     [helper mouseDown:event];
718 - (void)mouseUp:(NSEvent *)event
720     [helper mouseUp:event];
723 - (void)rightMouseUp:(NSEvent *)event
725     [helper mouseUp:event];
728 - (void)otherMouseUp:(NSEvent *)event
730     [helper mouseUp:event];
733 - (void)mouseDragged:(NSEvent *)event
735     [helper mouseDragged:event];
738 - (void)rightMouseDragged:(NSEvent *)event
740     [helper mouseDragged:event];
743 - (void)otherMouseDragged:(NSEvent *)event
745     [helper mouseDragged:event];
748 - (void)mouseMoved:(NSEvent *)event
750     [helper mouseMoved:event];
753 - (void)mouseEntered:(NSEvent *)event
755     [helper mouseEntered:event];
758 - (void)mouseExited:(NSEvent *)event
760     [helper mouseExited:event];
763 - (void)setFrame:(NSRect)frame
765     [super setFrame:frame];
766     [helper setFrame:frame];
769 - (void)viewDidMoveToWindow
771     [helper viewDidMoveToWindow];
774 - (void)viewWillMoveToWindow:(NSWindow *)newWindow
776     [helper viewWillMoveToWindow:newWindow];
779 - (NSMenu*)menuForEvent:(NSEvent *)event
781     // HACK! Return nil to disable NSTextView's popup menus (Vim provides its
782     // own).  Called when user Ctrl-clicks in the view (this is already handled
783     // in rightMouseDown:).
784     return nil;
787 - (NSArray *)acceptableDragTypes
789     return [NSArray arrayWithObjects:NSFilenamesPboardType,
790            NSStringPboardType, nil];
793 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
795     return [helper performDragOperation:sender];
798 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
800     return [helper draggingEntered:sender];
803 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
805     return [helper draggingUpdated:sender];
808 - (void)changeFont:(id)sender
810     MMTextStorage *ts = (MMTextStorage*)[self textStorage];
811     if (!ts) return;
813     NSFont *oldFont = [ts font];
814     NSFont *newFont = [sender convertFont:oldFont];
816     if (newFont) {
817         NSString *name = [newFont displayName];
818         unsigned len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
819         if (len > 0) {
820             NSMutableData *data = [NSMutableData data];
821             float pointSize = [newFont pointSize];
823             [data appendBytes:&pointSize length:sizeof(float)];
825             ++len;  // include NUL byte
826             [data appendBytes:&len length:sizeof(unsigned)];
827             [data appendBytes:[name UTF8String] length:len];
829             [[self vimController] sendMessage:SetFontMsgID data:data];
830         }
831     }
834 - (void)resetCursorRects
836     // No need to set up cursor rects since Vim handles cursor changes.
839 - (void)updateFontPanel
841     // The font panel is updated whenever the font is set.
846 // NOTE: The menu items cut/copy/paste/undo/redo/select all/... must be bound
847 // to the same actions as in IB otherwise they will not work with dialogs.  All
848 // we do here is forward these actions to the Vim process.
850 - (IBAction)cut:(id)sender
852     [[self windowController] vimMenuItemAction:sender];
855 - (IBAction)copy:(id)sender
857     [[self windowController] vimMenuItemAction:sender];
860 - (IBAction)paste:(id)sender
862     [[self windowController] vimMenuItemAction:sender];
865 - (IBAction)undo:(id)sender
867     [[self windowController] vimMenuItemAction:sender];
870 - (IBAction)redo:(id)sender
872     [[self windowController] vimMenuItemAction:sender];
875 - (IBAction)selectAll:(id)sender
877     [[self windowController] vimMenuItemAction:sender];
880 - (BOOL)validateMenuItem:(NSMenuItem *)item
882     if ([item action] == @selector(cut:)
883             || [item action] == @selector(copy:)
884             || [item action] == @selector(paste:)
885             || [item action] == @selector(undo:)
886             || [item action] == @selector(redo:)
887             || [item action] == @selector(selectAll:))
888         return [item tag];
890     return YES;
893 @end // MMTextView
898 @implementation MMTextView (Private)
900 - (MMWindowController *)windowController
902     id windowController = [[self window] windowController];
903     if ([windowController isKindOfClass:[MMWindowController class]])
904         return (MMWindowController*)windowController;
905     return nil;
908 - (MMVimController *)vimController
910     return [[self windowController] vimController];
913 - (void)setShouldDrawInsertionPoint:(BOOL)on
915     shouldDrawInsertionPoint = on;
918 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
919                        fraction:(int)percent
921     // This only stores where to draw the insertion point, the actual drawing
922     // is done in drawRect:.
923     shouldDrawInsertionPoint = YES;
924     insertionPointRow = row;
925     insertionPointColumn = col;
926     insertionPointShape = shape;
927     insertionPointFraction = percent;
930 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nrows
931                    numColumns:(int)ncols invert:(int)invert
933     if (invert) {
934         // The result should be inverted.
935         int n = numInvertRects++;
936         invertRects = reallocf(invertRects,
937                                numInvertRects*sizeof(NSRect));
938         if (NULL != invertRects) {
939             invertRects[n] = [self rectForRow:row column:col numRows:nrows
940                                    numColumns:ncols];
941             [self setNeedsDisplayInRect:invertRects[n]];
942         } else {
943             n = numInvertRects = 0;
944         }
945     } else {
946         // The result should look normal; all we need to do is to mark
947         // the rect for redrawing and Cocoa will redraw the text.
948         NSRect rect = [self rectForRow:row column:col numRows:nrows
949                             numColumns:ncols];
950         [self setNeedsDisplayInRect:rect];
951     }
954 @end // MMTextView (Private)