- Changed the way input text field is place in dialogs
[MacVim/jjgod.git] / MMTextView.m
blob69b2f79ef09c9642c48a183c67dcdca97364569d
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 #import "MMTextView.h"
12 #import "MMTextStorage.h"
13 #import "MMWindowController.h"
14 #import "MMVimController.h"
15 #import "MacVim.h"
19 @interface MMTextView (Private)
20 - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column;
21 - (void)dispatchKeyEvent:(NSEvent *)event;
22 - (MMVimController *)vimController;
23 @end
27 @implementation MMTextView
29 - (void)dealloc
31     [lastMouseDownEvent release];
32     [super dealloc];
35 - (NSEvent *)lastMouseDownEvent
37     return lastMouseDownEvent;
40 - (void)setShouldDrawInsertionPoint:(BOOL)enable
42     shouldDrawInsertionPoint = enable;
45 - (BOOL)shouldDrawInsertionPoint
47     return shouldDrawInsertionPoint;
50 - (void)insertText:(id)string
52     // NOTE!  This method is called for normal key presses but also for
53     // Option-key presses --- even when Ctrl is held as well as Option.  When
54     // Ctrl is held, the AppKit translates the character to a Ctrl+key stroke,
55     // so 'string' need not be a printable character!  In this case it still
56     // works to pass 'string' on to Vim as a printable character (since
57     // modifiers are already included and should not be added to the input
58     // buffer using CSI, K_MODIFIER).
60     NSEvent *event = [NSApp currentEvent];
61     //NSLog(@"%s%@ (event=%@)", _cmd, string, event);
63     // HACK!  In order to be able to bind to <S-Space> etc. we have to watch
64     // for when space was pressed.
65     if ([event type] == NSKeyDown
66             && [[event charactersIgnoringModifiers] length] > 0
67             && [[event charactersIgnoringModifiers] characterAtIndex:0] == ' '
68             && [event modifierFlags]
69                 & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask))
70     {
71         [self dispatchKeyEvent:event];
72         return;
73     }
75     [NSCursor setHiddenUntilMouseMoves:YES];
77     [[self vimController] sendMessage:InsertTextMsgID
78                  data:[string dataUsingEncoding:NSUTF8StringEncoding]
79                  wait:NO];
83 - (void)doCommandBySelector:(SEL)selector
85     // By ignoring the selector we effectively disable the key binding
86     // mechanism of Cocoa.  Hopefully this is what the user will expect
87     // (pressing Ctrl+P would otherwise result in moveUp: instead of previous
88     // match, etc.).
89     //
90     // We usually end up here if the user pressed Ctrl+key (but not
91     // Ctrl+Option+key).
93     //NSLog(@"%s%@", _cmd, NSStringFromSelector(selector));
94     [self dispatchKeyEvent:[NSApp currentEvent]];
97 - (BOOL)performKeyEquivalent:(NSEvent *)event
99     // Called for Cmd+key keystrokes, function keys, arrow keys, page
100     // up/down, home, end.
102     if ([event type] != NSKeyDown)
103         return NO;
105     // HACK!  Let the main menu try to handle any key down event, before
106     // passing it on to vim, otherwise key equivalents for menus will
107     // effectively be disabled.
108     if ([[NSApp mainMenu] performKeyEquivalent:event])
109         return YES;
111     // HACK!  KeyCode 50 represent the key which switches between windows
112     // within an application (like Cmd+Tab is used to switch between
113     // applications).  Return NO here, else the window switching does not work.
114     //
115     // Will this hack work for all languages / keyboard layouts?
116     if ([event keyCode] == 50)
117         return NO;
119     //NSLog(@"%s%@", _cmd, event);
121     NSMutableData *data = [NSMutableData data];
122     NSString *string = [event charactersIgnoringModifiers];
123     int flags = [event modifierFlags];
124     int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
126     [data appendBytes:&flags length:sizeof(int)];
127     [data appendBytes:&len length:sizeof(int)];
128     [data appendBytes:[string UTF8String] length:len];
130     [[self vimController] sendMessage:CmdKeyMsgID data:data wait:NO];
132     return YES;
135 - (void)setMarkedText:(id)text selectedRange:(NSRange)range
137     // TODO: Figure out a way to handle marked text, at the moment the user
138     // has no way of knowing what has been added so far in a multi-stroke key.
139     // E.g. hitting Option-e and then e will result in an 'e' with acute, but
140     // nothing is displayed immediately after hitting Option-e.
142     NSLog(@"setMarkedText:'%@' selectedRange:(%d,%d)", text, range.location,
143             range.length);
146 - (void)scrollWheel:(NSEvent *)event
148     if ([event deltaY] == 0)
149         return;
151     int row, col;
152     NSPoint pt = [self convertPoint:[event locationInWindow] fromView:nil];
153     if (![self convertPoint:pt toRow:&row column:&col])
154         return;
156     int flags = [event modifierFlags];
157     float dy = [event deltaY];
158     NSMutableData *data = [NSMutableData data];
160     [data appendBytes:&row length:sizeof(int)];
161     [data appendBytes:&col length:sizeof(int)];
162     [data appendBytes:&flags length:sizeof(int)];
163     [data appendBytes:&dy length:sizeof(float)];
165     [[self vimController] sendMessage:ScrollWheelMsgID data:data wait:NO];
168 - (void)mouseDown:(NSEvent *)event
170     int row, col;
171     NSPoint pt = [self convertPoint:[event locationInWindow] fromView:nil];
172     if (![self convertPoint:pt toRow:&row column:&col])
173         return;
175     lastMouseDownEvent = [event copy];
177     int button = [event buttonNumber];
178     int flags = [event modifierFlags];
179     int count = [event clickCount];
180     NSMutableData *data = [NSMutableData data];
182     // If desired, intepret Ctrl-Click as a right mouse click.
183     if ([[NSUserDefaults standardUserDefaults]
184             boolForKey:MMTranslateCtrlClickKey]
185             && button == 0 && flags & NSControlKeyMask) {
186         button = 1;
187         flags &= ~NSControlKeyMask;
188     }
190     [data appendBytes:&row length:sizeof(int)];
191     [data appendBytes:&col length:sizeof(int)];
192     [data appendBytes:&button length:sizeof(int)];
193     [data appendBytes:&flags length:sizeof(int)];
194     [data appendBytes:&count length:sizeof(int)];
196     [[self vimController] sendMessage:MouseDownMsgID data:data wait:NO];
199 - (void)rightMouseDown:(NSEvent *)event
201     [self mouseDown:event];
204 - (void)otherMouseDown:(NSEvent *)event
206     [self mouseDown:event];
209 - (void)mouseUp:(NSEvent *)event
211     int row, col;
212     NSPoint pt = [self convertPoint:[event locationInWindow] fromView:nil];
213     if (![self convertPoint:pt toRow:&row column:&col])
214         return;
216     int flags = [event modifierFlags];
217     NSMutableData *data = [NSMutableData data];
219     [data appendBytes:&row length:sizeof(int)];
220     [data appendBytes:&col length:sizeof(int)];
221     [data appendBytes:&flags length:sizeof(int)];
223     [[self vimController] sendMessage:MouseUpMsgID data:data wait:NO];
226 - (void)rightMouseUp:(NSEvent *)event
228     [self mouseUp:event];
231 - (void)otherMouseUp:(NSEvent *)event
233     [self mouseUp:event];
236 - (void)mouseDragged:(NSEvent *)event
238     int row, col;
239     NSPoint pt = [self convertPoint:[event locationInWindow] fromView:nil];
240     if (![self convertPoint:pt toRow:&row column:&col])
241         return;
243     int flags = [event modifierFlags];
244     NSMutableData *data = [NSMutableData data];
246     [data appendBytes:&row length:sizeof(int)];
247     [data appendBytes:&col length:sizeof(int)];
248     [data appendBytes:&flags length:sizeof(int)];
250     [[self vimController] sendMessage:MouseDraggedMsgID data:data wait:NO];
253 - (void)rightMouseDragged:(NSEvent *)event
255     [self mouseDragged:event];
258 - (void)otherMouseDragged:(NSEvent *)event
260     [self mouseDragged:event];
263 - (NSMenu*)menuForEvent:(NSEvent *)event
265     // HACK! Return nil to disable NSTextView's popup menus (Vim provides its
266     // own).  Called when user Ctrl-clicks in the view (this is already handled
267     // in rightMouseDown:).
268     return nil;
271 - (NSArray *)acceptableDragTypes
273     return [NSArray arrayWithObjects:NSFilenamesPboardType,
274            NSStringPboardType, nil];
277 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
279     NSPasteboard *pboard = [sender draggingPasteboard];
281     if ([[pboard types] containsObject:NSFilenamesPboardType]) {
282         NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];
283         int i, numberOfFiles = [files count];
284         NSMutableData *data = [NSMutableData data];
286         [data appendBytes:&numberOfFiles length:sizeof(int)];
288 #if 0
289         int row, col;
290         NSPoint pt = [self convertPoint:[sender draggingLocation] fromView:nil];
291         if (![self convertPoint:pt toRow:&row column:&col])
292             return NO;
294         [data appendBytes:&row length:sizeof(int)];
295         [data appendBytes:&col length:sizeof(int)];
296 #endif
298         for (i = 0; i < numberOfFiles; ++i) {
299             NSString *file = [files objectAtIndex:i];
300             int len = [file lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
302             if (len > 0) {
303                 ++len;  // append NUL as well
304                 [data appendBytes:&len length:sizeof(int)];
305                 [data appendBytes:[file UTF8String] length:len];
306             }
307         }
309         [[self vimController] sendMessage:DropFilesMsgID data:data wait:NO];
310         return YES;
311     } else if ([[pboard types] containsObject:NSStringPboardType]) {
312         NSString *string = [pboard stringForType:NSStringPboardType];
313         NSMutableData *data = [NSMutableData data];
314         int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
316         [data appendBytes:&len length:sizeof(int)];
317         [data appendBytes:[string UTF8String] length:len];
319         [[self vimController] sendMessage:DropStringMsgID data:data wait:NO];
320         return YES;
321     }
323     return NO;
326 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
328     NSDragOperation sourceDragMask = [sender draggingSourceOperationMask];
329     NSPasteboard *pboard = [sender draggingPasteboard];
331     if ( [[pboard types] containsObject:NSFilenamesPboardType]
332             && (sourceDragMask & NSDragOperationCopy) )
333         return NSDragOperationCopy;
334     if ( [[pboard types] containsObject:NSStringPboardType]
335             && (sourceDragMask & NSDragOperationCopy) )
336         return NSDragOperationCopy;
338     return NSDragOperationNone;
341 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
343     NSDragOperation sourceDragMask = [sender draggingSourceOperationMask];
344     NSPasteboard *pboard = [sender draggingPasteboard];
346     if ( [[pboard types] containsObject:NSFilenamesPboardType]
347             && (sourceDragMask & NSDragOperationCopy) )
348         return NSDragOperationCopy;
349     if ( [[pboard types] containsObject:NSStringPboardType]
350             && (sourceDragMask & NSDragOperationCopy) )
351         return NSDragOperationCopy;
353     return NSDragOperationNone;
356 - (void)changeFont:(id)sender
358     MMTextStorage *ts = (MMTextStorage*)[self textStorage];
359     if (!ts) return;
361     NSFont *oldFont = [ts font];
362     NSFont *newFont = [sender convertFont:oldFont];
364     if (newFont) {
365         NSString *name = [newFont displayName];
366         unsigned len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
367         if (len > 0) {
368             NSMutableData *data = [NSMutableData data];
369             float pointSize = [newFont pointSize];
371             [data appendBytes:&pointSize length:sizeof(float)];
373             ++len;  // include NUL byte
374             [data appendBytes:&len length:sizeof(unsigned)];
375             [data appendBytes:[name UTF8String] length:len];
377             [[self vimController] sendMessage:SetFontMsgID data:data wait:NO];
378         }
379     }
382 @end // MMTextView
387 @implementation MMTextView (Private)
389 - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column
391     NSLayoutManager *lm = [self layoutManager];
392     NSTextContainer *tc = [self textContainer];
393     MMTextStorage *ts = (MMTextStorage*)[self textStorage];
395     if (!(lm && tc && ts))
396         return NO;
398     unsigned glyphIdx = [lm glyphIndexForPoint:point inTextContainer:tc];
399     unsigned charIdx = [lm characterIndexForGlyphAtIndex:glyphIdx];
401     int mod = [ts maxColumns] + 1;
403     if (row) *row = (int)(charIdx / mod);
404     if (column) *column = (int)(charIdx % mod);
406     return YES;
409 - (void)keyDown:(NSEvent *)event
411     // HACK! If a modifier is held, don't pass the event along to
412     // interpretKeyEvents: since some keys are bound to multiple commands which
413     // means doCommandBySelector: is called several times.
414     //
415     // TODO: Figure out a way to disable Cocoa key bindings entirely, without
416     // affecting input management.
418     if ([event modifierFlags] & NSControlKeyMask)
419         [self dispatchKeyEvent:event];
420     else
421         [super keyDown:event];
424 - (void)dispatchKeyEvent:(NSEvent *)event
426     // Only handle the command if it came from a keyDown event
427     if ([event type] != NSKeyDown)
428         return;
430     NSString *chars = [event characters];
431     NSString *imchars = [event charactersIgnoringModifiers];
432     unichar c = [chars characterAtIndex:0];
433     unichar imc = [imchars characterAtIndex:0];
434     int len = 0;
435     const char *bytes = 0;
437     //NSLog(@"%s chars=0x%x unmodchars=0x%x", _cmd, c, imc);
439     if (' ' == imc && 0xa0 != c) {
440         // HACK!  The AppKit turns <C-Space> into <C-@> which is not standard
441         // Vim behaviour, so bypass this problem.  (0xa0 is <M-Space>, which
442         // should be passed on as is.)
443         len = [imchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
444         bytes = [imchars UTF8String];
445     } else if (imc == c && '2' == c) {
446         // HACK!  Translate Ctrl+2 to <C-@>.
447         static char ctrl_at = 0;
448         len = 1;  bytes = &ctrl_at;
449     } else if (imc == c && '6' == c) {
450         // HACK!  Translate Ctrl+6 to <C-^>.
451         static char ctrl_hat = 0x1e;
452         len = 1;  bytes = &ctrl_hat;
453     } else if (c == 0x19 && imc == 0x19) {
454         // HACK! AppKit turns back tab into Ctrl-Y, so we need to handle it
455         // separately (else Ctrl-Y doesn't work).
456         static char back_tab[2] = { 'k', 'B' };
457         len = 2; bytes = back_tab;
458     } else if (c == 0x3 && imc == 0x3) {
459         // HACK! AppKit turns enter (not return) into Ctrl-C, so we need to
460         // handle it separately (else Ctrl-C doesn't work).
461         static char enter[2] = { 'K', 'A' };
462         len = 2; bytes = enter;
463     } else if (c == 0x3 && imc == 0x63) {
464         // HACK! Intercept Ctrl-C and send SIGINT to Vim.
465         int pid = [[self vimController] pid];
466         if (pid > 0) {
467             kill(pid, SIGINT);
468             return;
469         }
470     } else {
471         len = [chars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
472         bytes = [chars UTF8String];
473     }
475     if (len > 0 && bytes) {
476         NSMutableData *data = [NSMutableData data];
477         int flags = [event modifierFlags];
479         [data appendBytes:&flags length:sizeof(int)];
480         [data appendBytes:&len length:sizeof(int)];
481         [data appendBytes:bytes length:len];
483         [NSCursor setHiddenUntilMouseMoves:YES];
485         //NSLog(@"%s len=%d bytes=0x%x", _cmd, len, bytes[0]);
486         [[self vimController] sendMessage:KeyDownMsgID data:data wait:NO];
487     }
490 - (MMVimController *)vimController
492     id windowController = [[self window] windowController];
494     // TODO: Make sure 'windowController' is a MMWindowController before type
495     // casting.
496     return [(MMWindowController*)windowController vimController];
499 @end // MMTextView (Private)