Rewritten the keybinding engine

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@12735 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
Nicola Pero 2002-02-25 01:46:41 +00:00
parent 9a3e9ff590
commit 85f076c0ad
2 changed files with 363 additions and 189 deletions

View file

@ -1,13 +1,10 @@
/* -*-objc-*-
NSInputManager.h
Copyright (C) 2001 Free Software Foundation, Inc.
Author: Fred Kiefer <FredKiefer@gmx.de>
Date: August 2001
Copyright (C) 2001, 2002 Free Software Foundation, Inc.
Author: Nicola Pero <n.pero@mi.flashnet.it>
Date: December 2001
Date: December 2001, February 2002
This file is part of the GNUstep GUI Library.
@ -57,37 +54,76 @@
- (void) insertText: (id)aString;
@end
struct _GSInputManagerBinding
{
/* The character this binding is about. */
unichar character;
/* The character is bound to different selectors according to the
various modifiers which can be used with the character.
There are eight selectors for the eight possibilities - each of
NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask might be on
or off and each combination gives a different selector.
The index of the selector to use is given by
(modifiers & (NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask)) / 2
a NULL selector means use the default action */
SEL selector[8];
};
/* The input manager understands quite sophisticated keybindings.
*
* A certain keystroke (represented by a unichar + some modifiers) can
* be bound to a selector. For example: "Control-f" = "moveForward:";
* If you press Control-f, the selector moveForward: is invoked.
*
* A certain keystroke can be bound to an array of selectors. For
* example: "Control-k" = ("moveToBeginningOfLine:", "deleteToEndOfLine:");
* If you press Control-k, the selector moveToBeginningOfLine: is invoked,
* immediately followed by deleteToEndOfLine:.
*
* A certain keystroke can be bound to a dictionary of other
* keybindings. For example "Control-c" = { "Control-f" =
* "openFile:"; "Control-s" = "save:"; };
* If you press Control-c followed by Control-f, openFile: is invoked;
* if you press Control-c followed by Control-s, save: is invoked.
*
* Any keystroke which is not bound by a keybinding is basically inserted
* as it is by calling 'insertText:' of the caller.
*
* Control-g is normally bound to aborting the current keybinding
* sequence. Whenever you are confused about what the hell you have
* typed and what strange command the input manager is going to
* understand, just type Control-g to discard past pending keystrokes,
* and reset the input manager.
*
* Control-q is normally bound to literally quoting the next
* keystroke. That is, the next keystroke is *not* interpreted by the
* input manager, but rather inserted literally into the text.
*/
@class GSKeyBindingTable;
@interface NSInputManager: NSObject <NSTextInput>
{
/* The current client we are working for. */
id _currentClient;
/* An array of bindings. */
struct _GSInputManagerBinding *_bindings;
/* The size of the array. */
int _bindingsCount;
/* This is the basic, root set of bindings. Whenever the input
manager detects that the current client has changed, it immediately
resets the current key bindings to the root ones. If you are typing
and are confused about what's happening, pressing Control-g always
resets the bindings to the root bindings. */
GSKeyBindingTable *_rootBindingTable;
/* These are the bindings which will be used to interpret the next
keystroke. At the beginning, this is the same as the
_rootBindingTable. But when you type a keystroke which is the
beginning of a sequence of keystrokes producing a certain action,
then the input manager updates the _currentBindingTable to be the
table where he looks up the next keystroke you put in.
*/
GSKeyBindingTable *_currentBindingTable;
/* When we are reading multi-keystroke bindings, we need to remember
the keystrokes we read thinking they were the beginning of a
multi-keystroke binding ... just in case it turns out that they
are not :-) */
NSMutableArray *_pendingKeyEvents;
/* When it is YES, the next key stroke is interpreted literally rather
than looked up using the _currentBindingTable. */
BOOL _interpretNextKeyStrokeLiterally;
/* Extremely special keybinding which overrides any other keybinding
in all contexts - abort - normally bound to Control-g. When we
encounter this keystroke, we abort all pending keystrokes and
reset ourselves immediately into vanilla root input state. */
unichar _abortCharacter;
int _abortFlags;
}
+ (NSInputManager *) currentInputManager;
@ -105,6 +141,41 @@ struct _GSInputManagerBinding
- (BOOL) wantsToDelayTextChangeNotifications;
- (BOOL) wantsToHandleMouseEvents;
- (BOOL) wantsToInterpretAllKeystrokes;
/* GNUstep Extensions. */
/* Parses a key as found in a keybinding file.
key is something like 'Control-f' or 'Control-Shift-LeftArrow'.
Returns YES if the key could be parsed, NO if not. If the key
could be parsed, character will contain the unichar, and modifiers
the modifiers. */
+ (BOOL) parseKey: (NSString *)key
intoCharacter: (unichar *)character
andModifiers: (int *)modifiers;
/* This is used to produce a key description which can be put into a
keybinding file from an actual keystroke. The gnustep-gui never
needs this :-) since it only reads keybinding files, never writes
them, but Preferences applications might need it - they can have
the user type in the desired keystroke, then call this method to
turn the keystroke into a string which can be put in keybindings
files. Pass 0 as modifiers if you only want the name of the
keystroke, ignoring modifiers. */
+ (NSString *) describeKeyStroke: (unichar)character
withModifiers: (int)modifiers;
/* Methods used internally ... not really part of the public API, can change
without notice. */
/* Reset the internal state. Normally bound to Control-g [regardless
of context!], but also automatically done whenever the current
client changes. */
- (void) resetInternalState;
/* Quote the next key stroke. Normally bound to Control-q. */
- (void) quoteNextKeyStroke;
@end
#endif /* _GNUstep_H_NSInputManager */

View file

@ -1,9 +1,9 @@
/** <title>NSInputManager</title> -*-objc-*-
/* NSInputManager -*-objc-*-
Copyright (C) 2001 Free Software Foundation, Inc.
Author: Nicola Pero <n.pero@mi.flashnet.it>
Date: December 2001
Date: December 2001, January 2002
This file is part of the GNUstep GUI Library.
@ -28,8 +28,11 @@
#include <AppKit/NSInputServer.h>
#include <AppKit/NSText.h>
#include "GSKeyBindingAction.h"
#include "GSKeyBindingTable.h"
/* A table mapping character names to characters, used to interpret
the character names found in KeyBindings dictionary files. */
the character names found in KeyBindings dictionaries. */
#define CHARACTER_TABLE_SIZE 77
static struct
@ -124,7 +127,6 @@ character_table[CHARACTER_TABLE_SIZE] =
static NSInputManager *currentInputManager = nil;
@implementation NSInputManager
+ (NSInputManager *) currentInputManager
@ -137,24 +139,20 @@ static NSInputManager *currentInputManager = nil;
return currentInputManager;
}
- (void) bindKey: (NSString *)key toAction: (NSString *)action
+ (BOOL) parseKey: (NSString *)key
intoCharacter: (unichar *)character
andModifiers: (int *)modifiers
{
/* First we try to parse the key into a character/flags couple */
unichar character = 0;
unsigned flags = 0;
BOOL isFunctionKey = NO;
/* Then we parse the action into a selector */
SEL selector;
NSString *c;
int flags = 0;
unichar c = 0;
/* Parse the key: first break it into segments separated by - */
NSArray *components = [key componentsSeparatedByString: @"-"];
NSString *name;
/* Then, parse the modifiers. The modifiers are the components
- all of them except the last one! */
int i, count = [components count];
int index;
for (i = 0; i < count - 1; i++)
{
@ -179,122 +177,109 @@ static NSInputManager *currentInputManager = nil;
{
flags |= NSShiftKeyMask;
}
else if ([modifier isEqualToString: @"NumericPad"]
|| [modifier isEqualToString: @"Numeric"]
|| [modifier isEqualToString: @"N"])
{
flags |= NSNumericPadKeyMask;
}
else
{
NSLog (@"NSInputManager - unknown modifier '%@' ignored", modifier);
return NO;
}
}
/* The actual index in the little SEL table is the following one. */
index = flags / 2;
/* Now, parse the actual key. */
c = [components objectAtIndex: (count - 1)];
name = [components objectAtIndex: (count - 1)];
if ([c isEqualToString: @""])
if ([name isEqualToString: @""])
{
/* This happens if '-' was the character. */
character = '-';
c = '-';
}
else if ([c length] == 1)
else if ([name length] == 1)
{
/* A single character, such as 'a'. */
character = [c characterAtIndex: 0];
c = [name characterAtIndex: 0];
}
else
{
/* A descriptive string, such as Tab or Home. */
for (i = 0; i < CHARACTER_TABLE_SIZE; i++)
{
if ([c isEqualToString: (character_table[i]).name])
if ([name isEqualToString: (character_table[i]).name])
{
character = (character_table[i]).character;
isFunctionKey = YES;
c = (character_table[i]).character;
flags |= NSFunctionKeyMask;
break;
}
}
if (i == CHARACTER_TABLE_SIZE)
{
NSLog (@"NSInputManager - unknown character '%@' ignored", c);
return;
return NO;
}
}
if (character != NULL)
{
*character = c;
}
if (modifiers != NULL)
{
*modifiers = flags;
}
return YES;
}
+ (NSString *) describeKeyStroke: (unichar)character
withModifiers: (int)modifiers
{
NSMutableString *description = [NSMutableString new];
int i;
if (modifiers & NSCommandKeyMask)
{
[description appendString: @"Command-"];
}
if (modifiers & NSControlKeyMask)
{
[description appendString: @"Control-"];
}
if (modifiers & NSAlternateKeyMask)
{
[description appendString: @"Alternate-"];
}
if (modifiers & NSShiftKeyMask)
{
[description appendString: @"Shift-"];
}
if (modifiers & NSNumericPadKeyMask)
{
[description appendString: @"Numeric-"];
}
for (i = 0; i < CHARACTER_TABLE_SIZE; i++)
{
if (character == ((character_table[i]).character))
{
[description appendString: character_table[i].name];
break;
}
}
/* "" as action means disable the keybinding. It can be used to override
a previous keybinding. */
if ([action isEqualToString: @""])
if (i == CHARACTER_TABLE_SIZE)
{
selector = NULL;
NSString *c = [NSString stringWithCharacters: &character length: 1];
[description appendString: c];
}
else
{
selector = NSSelectorFromString (action);
if (selector == NULL)
{
NSLog (@"NSInputManager: unknown selector '%@' ignored", action);
return;
}
}
/* Check if there are already some bindings for this character. */
for (i = 0; i < _bindingsCount; i++)
{
if (_bindings[i].character == character)
{
(_bindings[i]).selector[index] = selector;
if (!isFunctionKey)
{
/* Not a function key - set the selector for the character
with shift as well - we don't actually know if the character
is typed with shift or not ! */
flags |= NSShiftKeyMask;
index = flags / 2;
(_bindings[i]).selector[index] = selector;
}
return;
}
}
/* Ok - allocate memory for the new binding. */
if (_bindingsCount == 0)
{
_bindingsCount = 1;
_bindings = objc_malloc (sizeof (struct _GSInputManagerBinding));
}
else
{
_bindingsCount++;
_bindings = objc_realloc (_bindings,
sizeof (struct _GSInputManagerBinding)
* _bindingsCount);
}
_bindings[_bindingsCount - 1].character = character;
/* Set to NULL all selectors. */
for (i = 0; i < 8; i++)
{
(_bindings[_bindingsCount - 1]).selector[i] = NULL;
}
/* Now save the selector. */
(_bindings[_bindingsCount - 1]).selector[index] = selector;
if (!isFunctionKey)
{
/* Not a function key - set the selector for the character
with shift as well - we don't actually know if the character
is typed with shift or not ! */
flags |= NSShiftKeyMask;
index = flags / 2;
(_bindings[_bindingsCount - 1]).selector[index] = selector;
}
}
- (void) dealloc
{
objc_free (_bindings);
return;
return description;
}
- (void) loadBindingsFromFile: (NSString *)fullPath
@ -302,8 +287,6 @@ static NSInputManager *currentInputManager = nil;
NS_DURING
{
NSDictionary *bindings;
NSEnumerator *e;
NSString *key;
bindings = [NSDictionary dictionaryWithContentsOfFile: fullPath];
if (bindings == nil)
@ -311,12 +294,7 @@ static NSInputManager *currentInputManager = nil;
[NSException raise];
}
e = [bindings keyEnumerator];
while ((key = [e nextObject]) != nil)
{
[self bindKey: key
toAction: [bindings objectForKey: key]];
}
[_rootBindingTable loadBindingsFromDictionary: bindings];
}
NS_HANDLER
{
@ -363,6 +341,46 @@ static NSInputManager *currentInputManager = nil;
self = [super init];
_rootBindingTable = [GSKeyBindingTable new];
/* Read the abort key from the user defaults. */
{
NSString *abortKey = [defaults stringForKey: @"GSAbortKey"];
if (abortKey == nil)
{
_abortCharacter = 'g';
_abortFlags = NSControlKeyMask;
}
else if (![NSInputManager parseKey: abortKey
intoCharacter: &_abortCharacter
andModifiers: &_abortFlags])
{
NSLog (@"Could not parse GSAbortKey - using Control-g");
_abortCharacter = 'g';
_abortFlags = NSControlKeyMask;
}
}
/* Read the quote key from the user defaults. */
{
NSString *quoteKey = [defaults stringForKey: @"GSQuoteKey"];
GSKeyBindingActionQuoteNextKeyStroke *quoteAction;
quoteAction = [[GSKeyBindingActionQuoteNextKeyStroke alloc] init];
if (quoteKey == nil)
{
quoteKey = @"Control-q";
}
[_rootBindingTable bindKey: quoteKey toAction: quoteAction];
RELEASE (quoteAction);
}
/* FIXME all the following is gonna change. */
/* Normally, when we start up, we load all the keybindings we find
in the following files, in this order:
@ -436,6 +454,13 @@ static NSInputManager *currentInputManager = nil;
NSEvent *theEvent;
NSEnumerator *eventEnum = [eventArray objectEnumerator];
/* If the client has changed, reset our internal state before going
on. */
if (client != _currentClient)
{
[self resetInternalState];
}
_currentClient = client;
while ((theEvent = [eventEnum nextObject]) != nil)
@ -446,73 +471,151 @@ static NSInputManager *currentInputManager = nil;
unsigned flags = [theEvent modifierFlags] & (NSShiftKeyMask
| NSAlternateKeyMask
| NSControlKeyMask);
BOOL done = NO;
int i;
if ([unmodifiedCharacters length] > 0)
{
character = [unmodifiedCharacters characterAtIndex: 0];
}
/* Look up the character in the keybindings dictionary. */
for (i = 0; i < _bindingsCount; i++)
if (!_interpretNextKeyStrokeLiterally)
{
if (_bindings[i].character == character)
GSKeyBindingAction *action;
GSKeyBindingTable *table;
BOOL found;
/* Special keybinding recognized in all contexts - abort -
normally bound to Control-g. The user is confused and
wants to go home. Abort whatever train of thoughts we
were following, discarding whatever pending keystrokes we
have, and return into default state. */
if (character == _abortCharacter && flags == _abortFlags)
{
SEL selector = (_bindings[i]).selector[flags / 2];
[self resetInternalState];
break;
}
/* Look up the character in the current keybindings table. */
found = [_currentBindingTable lookupKeyStroke: character
modifiers: flags
returningActionIn: &action
tableIn: &table];
if (found)
{
if (action != nil)
{
/* First reset our internal state - we are done
interpreting this keystroke sequence. */
[self resetInternalState];
/* Then perform the action. The action might actually
modify our internal state, which is why we reset it
before calling the action! (for example, performing
the action might cause us to interpret the next
keystroke literally). */
[action performActionWithInputManager: self];
break;
}
else if (table != nil)
{
/* It's part of a composite multi-stroke
keybinding. */
_currentBindingTable = table;
[_pendingKeyEvents addObject: theEvent];
break;
}
/* Else it is as if we didn't find it! */
}
/* Ok - the keybinding wasn't found. If we were tracking a
multi-stroke keybinding, it means we were on a false
track. */
if ([_pendingKeyEvents count] > 0)
{
NSEvent *e;
/* Save the pending events locally in this stack
frame. */
NSMutableArray *a = _pendingKeyEvents;
RETAIN (a);
if (selector == NULL)
{
/* Get out of loop with done = NO - we found the
keybinding, but it was bound to the default
action, so we stop searching and fall back on the
default action. */
break;
}
else
{
[self doCommandBySelector: selector];
done = YES;
break;
}
}
}
/* Reset our internal state. */
[self resetInternalState];
/* Take the very first event we received and which we
tried to interpret as a key binding, which now we
know was the wrong thing to do. */
e = [a objectAtIndex: 0];
/* If not yet found, perform the default action. */
if (!done)
/* Interpret it literally, since interpreting it as a
keybinding failed. */
_interpretNextKeyStrokeLiterally = YES;
[self handleKeyboardEvents: [NSArray arrayWithObject: e]
client: client];
/* Now feed the remaining pending key events to
ourselves for interpretation - again from
scratch. */
[a removeObjectAtIndex: 0];
[a addObject: theEvent];
[self handleKeyboardEvents: a
client: client];
RELEASE (a);
break;
}
}
/* We couldn't (or shouldn't) find the keybinding ... perform
the default action - literally interpreting the
keystroke. */
/* If this was a forced literal interpretation, make sure the
next one is interpreted normally. */
_interpretNextKeyStrokeLiterally = NO;
switch (character)
{
switch (character)
case NSBackspaceCharacter:
[self doCommandBySelector: @selector (deleteBackward:)];
break;
case NSTabCharacter:
if (flags & NSShiftKeyMask)
{
case NSBackspaceCharacter:
[self doCommandBySelector: @selector (deleteBackward:)];
break;
case NSTabCharacter:
if (flags & NSShiftKeyMask)
{
[self doCommandBySelector: @selector (insertBacktab:)];
}
else
{
[self doCommandBySelector: @selector (insertTab:)];
}
break;
case NSEnterCharacter:
case NSFormFeedCharacter:
case NSCarriageReturnCharacter:
[self doCommandBySelector: @selector (insertNewline:)];
break;
default:
[self insertText: characters];
break;
[self doCommandBySelector: @selector (insertBacktab:)];
}
else
{
[self doCommandBySelector: @selector (insertTab:)];
}
break;
case NSEnterCharacter:
case NSFormFeedCharacter:
case NSCarriageReturnCharacter:
[self doCommandBySelector: @selector (insertNewline:)];
break;
default:
[self insertText: characters];
break;
}
}
}
}
- (void) resetInternalState
{
_currentBindingTable = _rootBindingTable;
ASSIGN (_pendingKeyEvents, [NSMutableArray array]);
_interpretNextKeyStrokeLiterally = NO;
}
- (void) quoteNextKeyStroke
{
_interpretNextKeyStrokeLiterally = YES;
}
- (BOOL) handleMouseEvent: (NSEvent *)theMouseEvent
{