Exit full-screen if the window moves
[MacVim.git] / src / MacVim / MMTextView.m
blobe793b77ba1de0e7e361e9d9064bc2fb0ce433345
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     if ([self inLiveResize])
291         [self display];
293 #if MM_DEBUG_DRAWING
294     NSLog(@"<==== END   %s", _cmd);
295 #endif
298 - (void)setMouseShape:(int)shape
300     [helper setMouseShape:shape];
303 - (void)setAntialias:(BOOL)state
305     antialias = state;
308 - (NSFont *)font
310     return [(MMTextStorage*)[self textStorage] font];
313 - (void)setFont:(NSFont *)newFont
315     [(MMTextStorage*)[self textStorage] setFont:newFont];
318 - (NSFont *)fontWide
320     return [(MMTextStorage*)[self textStorage] fontWide];
323 - (void)setWideFont:(NSFont *)newFont
325     [(MMTextStorage*)[self textStorage] setWideFont:newFont];
328 - (NSSize)cellSize
330     return [(MMTextStorage*)[self textStorage] cellSize];
333 - (void)setLinespace:(float)newLinespace
335     return [(MMTextStorage*)[self textStorage] setLinespace:newLinespace];
338 - (int)maxRows
340     MMTextStorage *ts = (MMTextStorage *)[self textStorage];
341     return [ts maxRows];
344 - (int)maxColumns
346     MMTextStorage *ts = (MMTextStorage *)[self textStorage];
347     return [ts maxColumns];
350 - (void)getMaxRows:(int*)rows columns:(int*)cols
352     return [(MMTextStorage*)[self textStorage] getMaxRows:rows columns:cols];
355 - (void)setMaxRows:(int)rows columns:(int)cols
357     return [(MMTextStorage*)[self textStorage] setMaxRows:rows columns:cols];
360 - (NSRect)rectForRowsInRange:(NSRange)range
362     return [(MMTextStorage*)[self textStorage] rectForRowsInRange:range];
365 - (NSRect)rectForColumnsInRange:(NSRange)range
367     return [(MMTextStorage*)[self textStorage] rectForColumnsInRange:range];
370 - (void)setDefaultColorsBackground:(NSColor *)bgColor
371                         foreground:(NSColor *)fgColor
373     [self setBackgroundColor:bgColor];
374     return [(MMTextStorage*)[self textStorage]
375             setDefaultColorsBackground:bgColor foreground:fgColor];
378 - (NSColor *)defaultBackgroundColor
380     return [(MMTextStorage*)[self textStorage] defaultBackgroundColor];
383 - (NSColor *)defaultForegroundColor
385     return [(MMTextStorage*)[self textStorage] defaultForegroundColor];
388 - (NSSize)constrainRows:(int *)rows columns:(int *)cols toSize:(NSSize)size
390     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
391     int right = [ud integerForKey:MMTextInsetRightKey];
392     int bot = [ud integerForKey:MMTextInsetBottomKey];
394     size.width -= [self textContainerOrigin].x + right;
395     size.height -= [self textContainerOrigin].y + bot;
397     NSSize newSize = [(MMTextStorage*)[self textStorage] fitToSize:size
398                                                               rows:rows
399                                                            columns:cols];
401     newSize.width += [self textContainerOrigin].x + right;
402     newSize.height += [self textContainerOrigin].y + bot;
404     return newSize;
407 - (NSSize)desiredSize
409     NSSize size = [(MMTextStorage*)[self textStorage] size];
411     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
412     int right = [ud integerForKey:MMTextInsetRightKey];
413     int bot = [ud integerForKey:MMTextInsetBottomKey];
415     size.width += [self textContainerOrigin].x + right;
416     size.height += [self textContainerOrigin].y + bot;
418     return size;
421 - (NSSize)minSize
423     NSSize cellSize = [(MMTextStorage*)[self textStorage] cellSize];
424     NSSize size = { MMMinColumns*cellSize.width, MMMinRows*cellSize.height };
426     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
427     int right = [ud integerForKey:MMTextInsetRightKey];
428     int bot = [ud integerForKey:MMTextInsetBottomKey];
430     size.width += [self textContainerOrigin].x + right;
431     size.height += [self textContainerOrigin].y + bot;
433     return size;
436 - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column
438     MMTextStorage *ts = (MMTextStorage*)[self textStorage];
439     NSSize cellSize = [ts cellSize];
440     if (!(cellSize.width > 0 && cellSize.height > 0))
441         return NO;
442     NSPoint origin = [self textContainerOrigin];
444     if (row) *row = floor((point.y-origin.y-1) / cellSize.height);
445     if (column) *column = floor((point.x-origin.x-1) / cellSize.width);
447     //NSLog(@"convertPoint:%@ toRow:%d column:%d", NSStringFromPoint(point),
448     //        *row, *column);
450     return YES;
453 - (NSPoint)pointForRow:(int)row column:(int)col
455     // Return the upper-left coordinate for (row,column).
456     // NOTE: The coordinate system is flipped!
457     NSPoint pt = [self textContainerOrigin];
458     MMTextStorage *ts = (MMTextStorage*)[self textStorage];
459     NSSize cellSize = [ts cellSize];
461     pt.x += col * cellSize.width;
462     pt.y += row * cellSize.height;
464     return pt;
467 - (NSRect)rectForRow:(int)row column:(int)col numRows:(int)nr
468           numColumns:(int)nc
470     // Return the rect for the block which covers the specified rows and
471     // columns.  The upper-left corner is the origin of this rect.
472     // NOTE: The coordinate system is flipped!
473     NSRect rect;
474     MMTextStorage *ts = (MMTextStorage*)[self textStorage];
475     NSSize cellSize = [ts cellSize];
477     rect.origin = [self textContainerOrigin];
478     rect.origin.x += col * cellSize.width;
479     rect.origin.y += row * cellSize.height;
480     rect.size.width = cellSize.width * nc;
481     rect.size.height = cellSize.height * nr;
483     return rect;
486 - (BOOL)isOpaque
488     return NO;
491 - (void)drawRect:(NSRect)rect
493     NSGraphicsContext *context = [NSGraphicsContext currentContext];
494     [context setShouldAntialias:antialias];
496     [super drawRect:rect];
498     if (invertRects) {
499         CGContextRef cgctx = (CGContextRef)[context graphicsPort];
500         CGContextSaveGState(cgctx);
501         CGContextSetBlendMode(cgctx, kCGBlendModeDifference);
502         CGContextSetRGBFillColor(cgctx, 1.0, 1.0, 1.0, 1.0);
504         int i;
505         CGRect *rect = (CGRect*)invertRects;
506         for (i = 0; i < numInvertRects; ++i)
507             CGContextFillRect(cgctx, rect[i]);
509         CGContextRestoreGState(cgctx);
511         free(invertRects);
512         invertRects = NULL;
513         numInvertRects = 0;
514     }
516     if ([self hasMarkedText]) {
517         shouldDrawInsertionPoint = YES;
518         MMTextStorage *ts = (MMTextStorage*)[self textStorage];
519         NSSize inset = [self textContainerInset];
521         // HACK! Get the baseline of the zeroth glyph and use that as the
522         // baseline for the marked text.  (Is there a better way to figure out
523         // what baseline NSTextView uses?)
524         NSLayoutManager *lm = [self layoutManager];
525         NSTypesetter *tsr = [lm typesetter];
526         float baseline = [tsr baselineOffsetInLayoutManager:lm glyphIndex:0];
528         // Also adjust for 'linespace' option (TODO: Why not .5*linespace?)
529         baseline -= floor([ts linespace]);
531         inset.height -= baseline;
533         int len = [[helper markedText] length];
534         // The following implementation should be re-written with
535         // more efficient way...
537         // Calculate how many wide-font characters can be inserted at
538         // a first line, and draw those characters.
539         int cols = ([ts actualColumns] - insertionPointColumn);
540         NSFont *theFont = [[self markedTextAttributes]
541                 valueForKey:NSFontAttributeName];
542         if (theFont == [ts fontWide])
543             cols = cols / 2;
544         int done = 0;
545         int lend = cols > len ? len : cols;
546         NSAttributedString *aString = [[helper markedText]
547                 attributedSubstringFromRange:NSMakeRange(done, lend)];
548         [aString drawAtPoint:NSMakePoint(
549                 [helper preEditColumn]*[ts cellSize].width + inset.width,
550                 [helper preEditRow]*[ts cellSize].height + inset.height)];
552         done = lend;
553         // Check whether there're charecters that aren't drawn at
554         // the first line. If everything is already done, the follow
555         // check fails.
556         if (done != len) {
557             int r;
558             // Calculate How many rows are needed to draw all the left
559             // characters.
560             int rows = (len - done) / ([ts actualColumns] / 2) + 1;
561             for (r = 1; r <= rows; r++) {
562                 lend = len - done > [ts actualColumns] / 2
563                         ? [ts actualColumns] / 2 : len - done;
564                 aString = [[helper markedText] attributedSubstringFromRange:
565                         NSMakeRange(done, lend)];
566                 [aString drawAtPoint:NSMakePoint(
567                         inset.width,
568                         ([helper preEditRow] + r)*[ts cellSize].height
569                             + inset.height)];
570                 done += lend;
571             }
572         }
573     }
575     if (shouldDrawInsertionPoint) {
576         MMTextStorage *ts = (MMTextStorage*)[self textStorage];
578         NSRect ipRect = [ts boundingRectForCharacterAtRow:[helper preEditRow]
579                                                    column:[helper preEditColumn]];
580         ipRect.origin.x += [self textContainerOrigin].x;
581         ipRect.origin.y += [self textContainerOrigin].y;
583         // Draw insertion point inside marked text.
584         if ([self hasMarkedText]) {
585             NSFont *theFont = [[self markedTextAttributes]
586                     valueForKey:NSFontAttributeName];
587             if (theFont == [ts font])
588                 ipRect.origin.x += [ts cellSize].width *
589                                    ([helper imRange].location +
590                                    [helper imRange].length);
591             else
592                 ipRect.origin.x += [ts cellSize].width * 2 *
593                                    ([helper imRange].location +
594                                    [helper imRange].length);
595         }
597         if (MMInsertionPointHorizontal == insertionPointShape) {
598             int frac = ([ts cellSize].height * insertionPointFraction + 99)/100;
599             ipRect.origin.y += ipRect.size.height - frac;
600             ipRect.size.height = frac;
601         } else if (MMInsertionPointVertical == insertionPointShape) {
602             int frac = ([ts cellSize].width * insertionPointFraction + 99)/100;
603             ipRect.size.width = frac;
604         } else if (MMInsertionPointVerticalRight == insertionPointShape) {
605             int frac = ([ts cellSize].width * insertionPointFraction + 99)/100;
606             ipRect.origin.x += ipRect.size.width - frac;
607             ipRect.size.width = frac;
608         }
610         [[helper insertionPointColor] set];
611         if (MMInsertionPointHollow == insertionPointShape) {
612             NSFrameRect(ipRect);
613         } else {
614             NSRectFill(ipRect);
615         }
617         // NOTE: We only draw the cursor once and rely on Vim to say when it
618         // should be drawn again.
619         shouldDrawInsertionPoint = NO;
621         //NSLog(@"%s draw insertion point %@ shape=%d color=%@", _cmd,
622         //        NSStringFromRect(ipRect), insertionPointShape,
623         //        [helper insertionPointColor]);
624     }
626 #if 0
627     // this code invalidates the shadow, so we don't 
628     // get shifting ghost text on scroll and resize
629     // but makes speed unusable
630     MMTextStorage *ts = (MMTextStorage*)[self textStorage];
631     if ([ts defaultBackgroundAlpha] < 1.0f) {
632         if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_1)
633         {
634             [[self window] setHasShadow:NO];
635             [[self window] setHasShadow:YES];
636         }
637         else
638             [[self window] invalidateShadow];
640     }
641 #endif
644 - (void)keyDown:(NSEvent *)event
646     [helper keyDown:event];
649 - (void)insertText:(id)string
651     [helper insertText:string];
654 - (void)doCommandBySelector:(SEL)selector
656     [helper doCommandBySelector:selector];
659 - (BOOL)performKeyEquivalent:(NSEvent *)event
661     return [helper performKeyEquivalent:event];
664 - (BOOL)hasMarkedText
666     return [helper hasMarkedText];
669 - (NSRange)markedRange
671     return [helper markedRange];
674 - (NSDictionary *)markedTextAttributes
676     return [helper markedTextAttributes];
679 - (void)setMarkedTextAttributes:(NSDictionary *)attr
681     [helper setMarkedTextAttributes:attr];
684 - (void)setMarkedText:(id)text selectedRange:(NSRange)range
686     [helper setMarkedText:text selectedRange:range];
689 - (void)unmarkText
691     [helper unmarkText];
694 - (NSRect)firstRectForCharacterRange:(NSRange)range
696     return [helper firstRectForCharacterRange:range];
699 - (void)scrollWheel:(NSEvent *)event
701     [helper scrollWheel:event];
704 - (void)mouseDown:(NSEvent *)event
706     [helper mouseDown:event];
709 - (void)rightMouseDown:(NSEvent *)event
711     [helper mouseDown:event];
714 - (void)otherMouseDown:(NSEvent *)event
716     [helper mouseDown:event];
719 - (void)mouseUp:(NSEvent *)event
721     [helper mouseUp:event];
724 - (void)rightMouseUp:(NSEvent *)event
726     [helper mouseUp:event];
729 - (void)otherMouseUp:(NSEvent *)event
731     [helper mouseUp:event];
734 - (void)mouseDragged:(NSEvent *)event
736     [helper mouseDragged:event];
739 - (void)rightMouseDragged:(NSEvent *)event
741     [helper mouseDragged:event];
744 - (void)otherMouseDragged:(NSEvent *)event
746     [helper mouseDragged:event];
749 - (void)mouseMoved:(NSEvent *)event
751     [helper mouseMoved:event];
754 - (void)mouseEntered:(NSEvent *)event
756     [helper mouseEntered:event];
759 - (void)mouseExited:(NSEvent *)event
761     [helper mouseExited:event];
764 - (void)setFrame:(NSRect)frame
766     [super setFrame:frame];
767     [helper setFrame:frame];
770 - (void)viewDidMoveToWindow
772     [helper viewDidMoveToWindow];
775 - (void)viewWillMoveToWindow:(NSWindow *)newWindow
777     [helper viewWillMoveToWindow:newWindow];
780 - (NSMenu*)menuForEvent:(NSEvent *)event
782     // HACK! Return nil to disable NSTextView's popup menus (Vim provides its
783     // own).  Called when user Ctrl-clicks in the view (this is already handled
784     // in rightMouseDown:).
785     return nil;
788 - (NSArray *)acceptableDragTypes
790     return [NSArray arrayWithObjects:NSFilenamesPboardType,
791            NSStringPboardType, nil];
794 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
796     return [helper performDragOperation:sender];
799 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
801     return [helper draggingEntered:sender];
804 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
806     return [helper draggingUpdated:sender];
809 - (void)changeFont:(id)sender
811     MMTextStorage *ts = (MMTextStorage*)[self textStorage];
812     if (!ts) return;
814     NSFont *oldFont = [ts font];
815     NSFont *newFont = [sender convertFont:oldFont];
817     if (newFont) {
818         NSString *name = [newFont displayName];
819         unsigned len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
820         if (len > 0) {
821             NSMutableData *data = [NSMutableData data];
822             float pointSize = [newFont pointSize];
824             [data appendBytes:&pointSize length:sizeof(float)];
826             ++len;  // include NUL byte
827             [data appendBytes:&len length:sizeof(unsigned)];
828             [data appendBytes:[name UTF8String] length:len];
830             [[self vimController] sendMessage:SetFontMsgID data:data];
831         }
832     }
835 - (void)resetCursorRects
837     // No need to set up cursor rects since Vim handles cursor changes.
840 - (void)updateFontPanel
842     // The font panel is updated whenever the font is set.
847 // NOTE: The menu items cut/copy/paste/undo/redo/select all/... must be bound
848 // to the same actions as in IB otherwise they will not work with dialogs.  All
849 // we do here is forward these actions to the Vim process.
851 - (IBAction)cut:(id)sender
853     [[self windowController] vimMenuItemAction:sender];
856 - (IBAction)copy:(id)sender
858     [[self windowController] vimMenuItemAction:sender];
861 - (IBAction)paste:(id)sender
863     [[self windowController] vimMenuItemAction:sender];
866 - (IBAction)undo:(id)sender
868     [[self windowController] vimMenuItemAction:sender];
871 - (IBAction)redo:(id)sender
873     [[self windowController] vimMenuItemAction:sender];
876 - (IBAction)selectAll:(id)sender
878     [[self windowController] vimMenuItemAction:sender];
881 - (BOOL)validateMenuItem:(NSMenuItem *)item
883     if ([item action] == @selector(cut:)
884             || [item action] == @selector(copy:)
885             || [item action] == @selector(paste:)
886             || [item action] == @selector(undo:)
887             || [item action] == @selector(redo:)
888             || [item action] == @selector(selectAll:))
889         return [item tag];
891     return YES;
894 @end // MMTextView
899 @implementation MMTextView (Private)
901 - (MMWindowController *)windowController
903     id windowController = [[self window] windowController];
904     if ([windowController isKindOfClass:[MMWindowController class]])
905         return (MMWindowController*)windowController;
906     return nil;
909 - (MMVimController *)vimController
911     return [[self windowController] vimController];
914 - (void)setShouldDrawInsertionPoint:(BOOL)on
916     shouldDrawInsertionPoint = on;
919 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
920                        fraction:(int)percent
922     // This only stores where to draw the insertion point, the actual drawing
923     // is done in drawRect:.
924     shouldDrawInsertionPoint = YES;
925     insertionPointRow = row;
926     insertionPointColumn = col;
927     insertionPointShape = shape;
928     insertionPointFraction = percent;
931 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nrows
932                    numColumns:(int)ncols invert:(int)invert
934     if (invert) {
935         // The result should be inverted.
936         int n = numInvertRects++;
937         invertRects = reallocf(invertRects,
938                                numInvertRects*sizeof(NSRect));
939         if (NULL != invertRects) {
940             invertRects[n] = [self rectForRow:row column:col numRows:nrows
941                                    numColumns:ncols];
942             [self setNeedsDisplayInRect:invertRects[n]];
943         } else {
944             n = numInvertRects = 0;
945         }
946     } else {
947         // The result should look normal; all we need to do is to mark
948         // the rect for redrawing and Cocoa will redraw the text.
949         NSRect rect = [self rectForRow:row column:col numRows:nrows
950                             numColumns:ncols];
951         [self setNeedsDisplayInRect:rect];
952     }
955 @end // MMTextView (Private)