1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
3 * VIM - Vi IMproved by Bram Moolenaar
4 * MacVim GUI port by Bjorn Winckler
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.
13 * Dispatches keyboard and mouse input to the backend. Handles drag-n-drop of
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.
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;
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];
68 } else if ([typesetterString isEqual:@"MMTypesetter2"]) {
69 NSTypesetter *typesetter = [[MMTypesetter2 alloc] init];
70 [lm setTypesetter:typesetter];
73 // Only MMTypesetter supports different cell width multipliers.
74 [[NSUserDefaults standardUserDefaults]
75 setFloat:1.0 forKey:MMCellWidthMultiplierKey];
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.
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];
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.
120 imRange = NSMakeRange(0, 0);
121 [markedText release];
131 [helper setTextView:nil];
132 [helper dealloc]; helper = nil;
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).
146 - (void)setShouldDrawInsertionPoint:(BOOL)on
148 shouldDrawInsertionPoint = on;
151 - (void)setPreEditRow:(int)row column:(int)col
157 #define MM_DEBUG_DRAWING 0
159 - (void)performBatchDrawWithData:(NSData *)data
161 MMTextStorage *textStorage = (MMTextStorage *)[self textStorage];
165 const void *bytes = [data bytes];
166 const void *end = bytes + [data length];
167 int cursorRow = -1, cursorCol = 0;
170 NSLog(@"====> BEGIN %s", _cmd);
172 [textStorage beginEditing];
174 // TODO: Sanity check input
176 while (bytes < end) {
177 int type = *((int*)bytes); bytes += sizeof(int);
179 if (ClearAllDrawType == type) {
181 NSLog(@" Clear all");
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);
192 NSLog(@" Clear block (%d,%d) -> (%d,%d)", row1, col1,
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);
207 NSLog(@" Delete %d line(s) from %d", count, row);
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];
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] : @"");
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
242 [textStorage drawString:string
243 atRow:row column:col cells:cells
245 foregroundColor:[NSColor colorWithRgbInt:fg]
246 backgroundColor:[NSColor colorWithArgbInt:bg]
247 specialColor:[NSColor colorWithRgbInt:sp]];
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);
259 NSLog(@" Insert %d line(s) at row %d", count, row);
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);
272 NSLog(@" Draw cursor at (%d,%d)", row, col);
274 [self drawInsertionPointAtRow:row column:col shape:shape
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);
285 NSLog(@" Draw inverted rect: row=%d col=%d nrows=%d ncols=%d",
288 [self drawInvertedRectAtRow:row column:col numRows:nr numColumns:nc
290 } else if (SetCursorPosDrawType == type) {
291 cursorRow = *((int*)bytes); bytes += sizeof(int);
292 cursorCol = *((int*)bytes); bytes += sizeof(int);
294 NSLog(@"WARNING: Unknown draw type (type=%d)", type);
298 [textStorage endEditing];
300 if (cursorRow >= 0) {
301 unsigned off = [textStorage characterIndexForRow:cursorRow
303 unsigned maxoff = [[textStorage string] length];
304 if (off > maxoff) off = maxoff;
306 [self setSelectedRange:NSMakeRange(off, 0)];
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];
314 NSLog(@"<==== END %s", _cmd);
318 - (void)setMouseShape:(int)shape
320 [helper setMouseShape:shape];
323 - (void)setAntialias:(BOOL)state
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];
345 return [(MMTextStorage*)[self textStorage] cellSize];
348 - (void)setLinespace:(float)newLinespace
350 return [(MMTextStorage*)[self textStorage] setLinespace:newLinespace];
355 MMTextStorage *ts = (MMTextStorage *)[self textStorage];
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
400 newSize.width += [self textContainerOrigin].x + right;
401 newSize.height += [self textContainerOrigin].y + bot;
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;
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;
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))
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),
457 - (void)drawRect:(NSRect)rect
459 NSGraphicsContext *context = [NSGraphicsContext currentContext];
460 [context setShouldAntialias:antialias];
462 [super drawRect:rect];
465 CGContextRef cgctx = (CGContextRef)[context graphicsPort];
466 CGContextSaveGState(cgctx);
467 CGContextSetBlendMode(cgctx, kCGBlendModeDifference);
468 CGContextSetRGBFillColor(cgctx, 1.0, 1.0, 1.0, 1.0);
471 CGRect *rect = (CGRect*)invertRects;
472 for (i = 0; i < numInvertRects; ++i)
473 CGContextFillRect(cgctx, rect[i]);
475 CGContextRestoreGState(cgctx);
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])
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)];
519 // Check whether there're charecters that aren't drawn at
520 // the first line. If everything is already done, the follow
524 // Calculate How many rows are needed to draw all the left
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(
534 (preEditRow + r)*[ts cellSize].height
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);
557 ipRect.origin.x += [ts cellSize].width * 2 *
558 (imRange.location + imRange.length);
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;
574 [[self insertionPointColor] set];
575 if (MMInsertionPointHollow == insertionPointShape) {
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]);
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)
598 [[self window] setHasShadow:NO];
599 [[self window] setHasShadow:YES];
602 [[self window] invalidateShadow];
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]) {
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];
657 - (void)setMarkedText:(id)text selectedRange:(NSRange)range
659 //NSLog(@"setMarkedText:'%@' selectedRange:%@", text,
660 // NSStringFromRange(range));
662 MMTextStorage *ts = (MMTextStorage*)[self textStorage];
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,
675 markedText = [[NSMutableAttributedString alloc]
676 initWithString:[text string]
677 attributes:[self markedTextAttributes]];
679 [self setMarkedTextAttributes:
680 [NSDictionary dictionaryWithObjectsAndKeys:
681 [ts font], NSFontAttributeName,
682 [ts defaultBackgroundColor], NSBackgroundColorAttributeName,
683 [ts defaultForegroundColor], NSForegroundColorAttributeName,
685 markedText = [[NSMutableAttributedString alloc]
687 attributes:[self markedTextAttributes]];
690 markedRange = NSMakeRange(0, [markedText length]);
691 if (markedRange.length) {
692 [markedText addAttribute:NSUnderlineStyleAttributeName
693 value:[NSNumber numberWithInt:1]
698 [markedText addAttribute:NSUnderlineStyleAttributeName
699 value:[NSNumber numberWithInt:2]
703 [self setNeedsDisplay: YES];
708 //NSLog(@"%s", _cmd);
709 imRange = NSMakeRange(0, 0);
710 markedRange = NSMakeRange(NSNotFound, 0);
711 [markedText release];
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];
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:).
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];
850 NSFont *oldFont = [ts font];
851 NSFont *newFont = [sender convertFont:oldFont];
854 NSString *name = [newFont displayName];
855 unsigned len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
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];
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:))
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))
943 *point = [self textContainerOrigin];
944 point->x += column * cellSize.width;
945 point->y += row * cellSize.height;
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))
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;
967 - (MMWindowController *)windowController
969 id windowController = [[self window] windowController];
970 if ([windowController isKindOfClass:[MMWindowController class]])
971 return (MMWindowController*)windowController;
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
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]];
1010 n = numInvertRects = 0;
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.
1016 [self convertRow:row column:col numRows:nrows numColumns:ncols
1018 [self setNeedsDisplayInRect:rect];
1022 @end // MMTextView (Private)