From 997a305d35849fc20969ec2e7d883c5ac1b84ef6 Mon Sep 17 00:00:00 2001 From: nciagra Date: Fri, 18 Jun 2010 10:40:15 -0400 Subject: [PATCH] Added CPKeyBinding for a dynamic key binding system. --- AppKit/AppKit.j | 1 + AppKit/CPKeyBinding.j | 233 ++++++++++++++++++++++++++++++++++++++++++ AppKit/CPResponder.j | 44 +++----- 3 files changed, 248 insertions(+), 30 deletions(-) create mode 100644 AppKit/CPKeyBinding.j diff --git a/AppKit/AppKit.j b/AppKit/AppKit.j index 1feb31525..9c820be65 100644 --- a/AppKit/AppKit.j +++ b/AppKit/AppKit.j @@ -55,6 +55,7 @@ @import "CPGeometry.j" @import "CPImage.j" @import "CPImageView.j" +@import "CPKeyBinding.j" @import "CPMenu.j" @import "CPMenuItem.j" @import "CPOpenPanel.j" diff --git a/AppKit/CPKeyBinding.j b/AppKit/CPKeyBinding.j new file mode 100644 index 000000000..e68b16c3c --- /dev/null +++ b/AppKit/CPKeyBinding.j @@ -0,0 +1,233 @@ +/* + * CPKeyBinding.j + * AppKit + * + * Created by Nicholas Small. + * Copyright 2010, 280 North, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +@import + + +CPStandardKeyBindings = { + @"@.": @"cancelOperation:", + + @"^a": @"moveToBeginningOfParagraph:", + @"^$a": @"moveToBeginningOfParagraphAndModifySelection:", + @"^b": @"moveBackward:", + @"^$b": @"moveBackwardAndModifySelection:", + @"^~$b": @"moveWordBackwardAndModifySelection:", + @"^d": @"deleteForward:", + @"^e": @"moveToEndOfParagraph:", + @"^$e": @"moveToEndOfParagraphAndModifySelection:", + @"^f": @"moveForward:", + @"^$f": @"moveForwardAndModifySelection:", + @"^~f": @"moveWordForward:", + @"^~$f": @"moveWordForwardAndModifySelection:", + @"^h": @"deleteBackward:", + @"^k": @"deleteToEndOfParagraph:", + @"^l": @"centerSelectionInVisibleArea:", + @"^n": @"moveDown:", + @"^$n": @"moveDownAndModifySelection:", + @"^o": [@"insertNewlineIgnoringFieldEditor:", @"moveBackward:"], + @"^p": @"moveUp:", + @"^$p": @"moveUpAndModifySelection:", + @"^t": @"transpose:", + @"^v": @"pageDown:", + @"^v": @"pageDownAndModifySelection:", + @"^y": @"yank:" +}; + +CPStandardKeyBindings[CPNewlineCharacter] = @"insertNewline:"; +CPStandardKeyBindings[CPCarriageReturnCharacter] = @"insertNewline:"; +CPStandardKeyBindings[CPEnterCharacter] = @"insertNewline:"; +CPStandardKeyBindings[@"~" + CPNewlineCharacter] = @"insertNewlineIgnoringFieldEditor:"; +CPStandardKeyBindings[@"~" + CPCarriageReturnCharacter] = @"insertNewlineIgnoringFieldEditor:"; +CPStandardKeyBindings[@"~" + CPEnterCharacter] = @"insertNewlineIgnoringFieldEditor:"; +CPStandardKeyBindings[@"^" + CPNewlineCharacter] = @"insertLineBreak:"; +CPStandardKeyBindings[@"^" + CPCarriageReturnCharacter] = @"insertLineBreak:"; +CPStandardKeyBindings[@"^" + CPEnterCharacter] = @"insertLineBreak:"; + +CPStandardKeyBindings[CPBackspaceCharacter] = @"deleteBackward:"; +CPStandardKeyBindings[@"~" + CPBackspaceCharacter] = @"deleteWordBackward:"; +CPStandardKeyBindings[CPDeleteCharacter] = @"deleteBackward:"; +CPStandardKeyBindings[@"@" + CPDeleteCharacter] = @"deleteToBeginningOfLine:"; +CPStandardKeyBindings[@"~" + CPDeleteCharacter] = @"deleteWordBackward:"; +CPStandardKeyBindings[@"^" + CPDeleteCharacter] = @"deleteBackwardByDecomposingPreviousCharacter:"; +CPStandardKeyBindings[@"^~" + CPDeleteCharacter] = @"deleteWordBackward:"; + +CPStandardKeyBindings[CPDeleteFunctionKey] = @"deleteForward:"; +CPStandardKeyBindings[@"~" + CPDeleteFunctionKey] = @"deleteWordForward:"; + +CPStandardKeyBindings[CPTabCharacter] = @"insertTab:"; +CPStandardKeyBindings[@"~" + CPTabCharacter] = @"insertTabIgnoringFieldEditor:"; +CPStandardKeyBindings[@"^" + CPTabCharacter] = @"selectNextKeyView:"; +CPStandardKeyBindings[CPBackTabCharacter] = @"insertBacktab:"; +CPStandardKeyBindings[@"^" + CPBackTabCharacter] = @"selectPreviousKeyView:"; + +CPStandardKeyBindings[CPEscapeFunctionKey] = @"cancelOperation:"; +CPStandardKeyBindings[@"~" + CPEscapeFunctionKey] = @"complete:"; +CPStandardKeyBindings[CPF5FunctionKey] = @"complete:"; + +CPStandardKeyBindings[CPLeftArrowFunctionKey] = @"moveLeft:"; +CPStandardKeyBindings[@"~" + CPLeftArrowFunctionKey] = @"moveWordLeft:"; +CPStandardKeyBindings[@"^" + CPLeftArrowFunctionKey] = @"moveToLeftEndOfLine:"; +CPStandardKeyBindings[@"@" + CPLeftArrowFunctionKey] = @"moveToLeftEndOfLine:"; +CPStandardKeyBindings[@"$" + CPLeftArrowFunctionKey] = @"moveLeftAndModifySelection:"; +CPStandardKeyBindings[@"$~" + CPLeftArrowFunctionKey] = @"moveWordLeftAndModifySelection:"; +CPStandardKeyBindings[@"$^" + CPLeftArrowFunctionKey] = @"moveToLeftEndOfLineAndModifySelection:"; +CPStandardKeyBindings[@"$@" + CPLeftArrowFunctionKey] = @"moveToLeftEndOfLineAndModifySelection:"; +CPStandardKeyBindings[@"@^" + CPLeftArrowFunctionKey] = @"makeBaseWritingDirectionRightToLeft:"; +CPStandardKeyBindings[@"@^~" + CPLeftArrowFunctionKey] = @"makeTextWritingDirectionRightToLeft:"; + +CPStandardKeyBindings[CPRightArrowFunctionKey] = @"moveRight:"; +CPStandardKeyBindings[@"~" + CPRightArrowFunctionKey] = @"moveWordRight:"; +CPStandardKeyBindings[@"^" + CPRightArrowFunctionKey] = @"moveToRightEndOfLine:"; +CPStandardKeyBindings[@"@" + CPRightArrowFunctionKey] = @"moveToRightEndOfLine:"; +CPStandardKeyBindings[@"$" + CPRightArrowFunctionKey] = @"moveRightAndModifySelection:"; +CPStandardKeyBindings[@"$~" + CPRightArrowFunctionKey] = @"moveWordRightAndModifySelection:"; +CPStandardKeyBindings[@"$^" + CPRightArrowFunctionKey] = @"moveToRightEndOfLineAndModifySelection:"; +CPStandardKeyBindings[@"$@" + CPRightArrowFunctionKey] = @"moveToRightEndOfLineAndModifySelection:"; +CPStandardKeyBindings[@"@^" + CPRightArrowFunctionKey] = @"makeBaseWritingDirectionLeftToRight:"; +CPStandardKeyBindings[@"@^~" + CPRightArrowFunctionKey] = @"makeTextWritingDirectionLeftToRight:"; + +CPStandardKeyBindings[CPUpArrowFunctionKey] = @"moveUp:"; +CPStandardKeyBindings[@"~" + CPUpArrowFunctionKey] = [@"moveBackward:", @"moveToBeginningOfParagraph:"]; +CPStandardKeyBindings[@"^" + CPUpArrowFunctionKey] = @"scrollPageUp:"; +CPStandardKeyBindings[@"@" + CPUpArrowFunctionKey] = @"moveToBeginningOfDocument:"; +CPStandardKeyBindings[@"$" + CPUpArrowFunctionKey] = @"moveUpAndModifySelection:"; +CPStandardKeyBindings[@"$~" + CPUpArrowFunctionKey] = @"moveParagraphBackwardAndModifySelection:"; +CPStandardKeyBindings[@"$@" + CPUpArrowFunctionKey] = @"moveToBeginningOfDocumentAndModifySelection:"; + +CPStandardKeyBindings[CPDownArrowFunctionKey] = @"moveDown:"; +CPStandardKeyBindings[@"~" + CPDownArrowFunctionKey] = [@"moveForward:", @"moveToEndOfParagraph:"]; +CPStandardKeyBindings[@"^" + CPDownArrowFunctionKey] = @"scrollPageDown:"; +CPStandardKeyBindings[@"@" + CPDownArrowFunctionKey] = @"moveToEndOfDocument:"; +CPStandardKeyBindings[@"$" + CPDownArrowFunctionKey] = @"moveDownAndModifySelection:"; +CPStandardKeyBindings[@"$~" + CPDownArrowFunctionKey] = @"moveParagraphForwardAndModifySelection:"; +CPStandardKeyBindings[@"$@" + CPDownArrowFunctionKey] = @"moveToEndOfDocumentAndModifySelection:"; +CPStandardKeyBindings[@"@^" + CPDownArrowFunctionKey] = @"makeBaseWritingDirectionNatural:"; +CPStandardKeyBindings[@"@^~" + CPDownArrowFunctionKey] = @"makeTextWritingDirectionNatural:"; + +CPStandardKeyBindings[CPHomeFunctionKey] = @"scrollToBeginningOfDocument:"; +CPStandardKeyBindings[@"$" + CPHomeFunctionKey] = @"moveToBeginningOfDocumentAndModifySelection:"; +CPStandardKeyBindings[CPEndFunctionKey] = @"scrollToEndOfDocument:"; +CPStandardKeyBindings[@"$" + CPEndFunctionKey] = @"moveToEndOfDocumentAndModifySelection:"; + +CPStandardKeyBindings[CPPageUpFunctionKey] = @"scrollPageUp:"; +CPStandardKeyBindings[@"~" + CPPageUpFunctionKey] = @"pageUp:"; +CPStandardKeyBindings[@"$" + CPPageUpFunctionKey] = @"pageUpAndModifySelection:"; +CPStandardKeyBindings[CPPageDownFunctionKey] = @"scrollPageDown:"; +CPStandardKeyBindings[@"~" + CPPageDownFunctionKey] = @"pageDown:"; +CPStandardKeyBindings[@"$" + CPPageDownFunctionKey] = @"pageDownAndModifySelection:"; + +var CPKeyBindingCache = []; + +@implementation CPKeyBinding : CPObject +{ + CPString _key; + unsigned _modifierFlags; + + CPArray _selectors; +} + ++ (void)initialize +{ + if ([self class] !== CPKeyBinding) + return; + + [self createKeyBindingsFromJSObject:CPStandardKeyBindings]; +} + ++ (void)createKeyBindingsFromJSObject:(JSObject)anObject +{ + var binding; + for (binding in anObject) + { + var components = binding.split(@""), + modifierFlags = ([components containsObject:@"$"] ? CPShiftKeyMask : 0) | + ([components containsObject:@"^"] ? CPControlKeyMask : 0) | + ([components containsObject:@"~"] ? CPAlternateKeyMask : 0) | + ([components containsObject:@"@"] ? CPCommandKeyMask : 0); + + var selectors = anObject[binding]; + if (![selectors isKindOfClass:CPArray]) + selectors = [selectors]; + + var keyBinding = [[self alloc] initWithKey:[components lastObject] modifierFlags:modifierFlags selectors:selectors]; + [self cacheKeyBinding:keyBinding]; + } +} + ++ (void)cacheKeyBinding:(CPKeyBinding)aBinding +{ + if (aBinding) + [CPKeyBindingCache addObject:aBinding]; +} + ++ (CPKeyBinding)keyBindingForKey:(CPString)aKey modifierFlags:(unsigned)aFlag +{ + var tempBinding = [[self alloc] initWithKey:aKey modifierFlags:aFlag selectors:nil]; + for (var i = 0, count = CPKeyBindingCache.length; i < count; i++) + { + var binding = CPKeyBindingCache[i]; + if ([binding isEqual:tempBinding]) + return binding; + } +} + ++ (CPArray)selectorsForKey:(CPString)aKey modifierFlags:(unsigned)aFlag +{ + return [[self keyBindingForKey:aKey modifierFlags:aFlag] selectors]; +} + +- (id)initWithKey:(CPString)aKey modifierFlags:(unsigned)aFlag selectors:(CPArray)selectors +{ + self = [super init]; + + if (self) + { + _key = aKey; + _modifierFlags = aFlag; + + _selectors = selectors; + } + + return self; +} + +- (CPString)key +{ + return _key; +} + +- (unsigned)modifierFlags +{ + return _modifierFlags; +} + +- (CPArray)selectors +{ + return _selectors; +} + +- (BOOL)isEqual:(CPKeyBinding)rhs +{ + return _key === [rhs key] && _modifierFlags === [rhs modifierFlags]; +} + +@end diff --git a/AppKit/CPResponder.j b/AppKit/CPResponder.j index 9760e5449..08b27616e 100644 --- a/AppKit/CPResponder.j +++ b/AppKit/CPResponder.j @@ -104,40 +104,24 @@ CPDeleteForwardKeyCode = 46; for (; index < count; ++index) { - var event = events[index]; + var event = events[index], + modifierFlags = [event modifierFlags], + character = [event charactersIgnoringModifiers], + selectorNames = [CPKeyBinding selectorsForKey:character modifierFlags:modifierFlags]; - switch([[event characters] characterAtIndex:0]) + if (selectorNames) { - case CPPageUpFunctionKey: [self doCommandBySelector:@selector(pageUp:)]; - break; - case CPPageDownFunctionKey: [self doCommandBySelector:@selector(pageDown:)]; - break; - case CPLeftArrowFunctionKey: [self doCommandBySelector:@selector(moveLeft:)]; - break; - case CPRightArrowFunctionKey: [self doCommandBySelector:@selector(moveRight:)]; - break; - case CPUpArrowFunctionKey: [self doCommandBySelector:@selector(moveUp:)]; - break; - case CPDownArrowFunctionKey: [self doCommandBySelector:@selector(moveDown:)]; - break; - case CPDeleteCharacter: [self doCommandBySelector:@selector(deleteBackward:)]; - break; - case CPCarriageReturnCharacter: - case CPNewlineCharacter: [self doCommandBySelector:@selector(insertLineBreak:)]; - break; + for (var s = 0, scount = selectorNames.length; s < scount; s++) + { + var selector = selectorNames[s]; + if (!selector) + continue; - case CPEscapeFunctionKey: [self doCommandBySelector:@selector(cancel:)]; - break; - - case CPTabCharacter: if (!([event modifierFlags] & CPShiftKeyMask)) - [self doCommandBySelector:@selector(insertTab:)]; - else - [self doCommandBySelector:@selector(insertBackTab:)]; - break; - - default: if (!([event modifierFlags] & (CPCommandKeyMask | CPControlKeyMask))) - [self insertText:[event characters]]; + [self doCommandBySelector:CPSelectorFromString(selector)]; + } } + else if (!(modifierFlags & (CPCommandKeyMask | CPControlKeyMask)) && [self respondsToSelector:@selector(insertText:)]) + [self insertText:[event characters]]; } }