mirror of
https://github.com/gnustep/libs-gui.git
synced 2025-05-31 17:50:47 +00:00
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:
parent
0b205f5da3
commit
3af7dbdf66
2 changed files with 363 additions and 189 deletions
|
@ -1,13 +1,10 @@
|
||||||
/* -*-objc-*-
|
/* -*-objc-*-
|
||||||
NSInputManager.h
|
NSInputManager.h
|
||||||
|
|
||||||
Copyright (C) 2001 Free Software Foundation, Inc.
|
Copyright (C) 2001, 2002 Free Software Foundation, Inc.
|
||||||
|
|
||||||
Author: Fred Kiefer <FredKiefer@gmx.de>
|
|
||||||
Date: August 2001
|
|
||||||
|
|
||||||
Author: Nicola Pero <n.pero@mi.flashnet.it>
|
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.
|
This file is part of the GNUstep GUI Library.
|
||||||
|
|
||||||
|
@ -57,37 +54,76 @@
|
||||||
- (void) insertText: (id)aString;
|
- (void) insertText: (id)aString;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
struct _GSInputManagerBinding
|
/* The input manager understands quite sophisticated keybindings.
|
||||||
{
|
*
|
||||||
/* The character this binding is about. */
|
* A certain keystroke (represented by a unichar + some modifiers) can
|
||||||
unichar character;
|
* be bound to a selector. For example: "Control-f" = "moveForward:";
|
||||||
|
* If you press Control-f, the selector moveForward: is invoked.
|
||||||
/* The character is bound to different selectors according to the
|
*
|
||||||
various modifiers which can be used with the character.
|
* A certain keystroke can be bound to an array of selectors. For
|
||||||
|
* example: "Control-k" = ("moveToBeginningOfLine:", "deleteToEndOfLine:");
|
||||||
There are eight selectors for the eight possibilities - each of
|
* If you press Control-k, the selector moveToBeginningOfLine: is invoked,
|
||||||
NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask might be on
|
* immediately followed by deleteToEndOfLine:.
|
||||||
or off and each combination gives a different selector.
|
*
|
||||||
|
* A certain keystroke can be bound to a dictionary of other
|
||||||
The index of the selector to use is given by
|
* keybindings. For example "Control-c" = { "Control-f" =
|
||||||
|
* "openFile:"; "Control-s" = "save:"; };
|
||||||
(modifiers & (NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask)) / 2
|
* If you press Control-c followed by Control-f, openFile: is invoked;
|
||||||
|
* if you press Control-c followed by Control-s, save: is invoked.
|
||||||
a NULL selector means use the default action */
|
*
|
||||||
SEL selector[8];
|
* 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>
|
@interface NSInputManager: NSObject <NSTextInput>
|
||||||
{
|
{
|
||||||
|
/* The current client we are working for. */
|
||||||
id _currentClient;
|
id _currentClient;
|
||||||
|
|
||||||
/* An array of bindings. */
|
|
||||||
struct _GSInputManagerBinding *_bindings;
|
|
||||||
|
|
||||||
/* The size of the array. */
|
/* This is the basic, root set of bindings. Whenever the input
|
||||||
int _bindingsCount;
|
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;
|
+ (NSInputManager *) currentInputManager;
|
||||||
|
|
||||||
|
@ -105,6 +141,41 @@ struct _GSInputManagerBinding
|
||||||
- (BOOL) wantsToDelayTextChangeNotifications;
|
- (BOOL) wantsToDelayTextChangeNotifications;
|
||||||
- (BOOL) wantsToHandleMouseEvents;
|
- (BOOL) wantsToHandleMouseEvents;
|
||||||
- (BOOL) wantsToInterpretAllKeystrokes;
|
- (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
|
@end
|
||||||
|
|
||||||
#endif /* _GNUstep_H_NSInputManager */
|
#endif /* _GNUstep_H_NSInputManager */
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
/** <title>NSInputManager</title> -*-objc-*-
|
/* NSInputManager -*-objc-*-
|
||||||
|
|
||||||
Copyright (C) 2001 Free Software Foundation, Inc.
|
Copyright (C) 2001 Free Software Foundation, Inc.
|
||||||
|
|
||||||
Author: Nicola Pero <n.pero@mi.flashnet.it>
|
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.
|
This file is part of the GNUstep GUI Library.
|
||||||
|
|
||||||
|
@ -28,8 +28,11 @@
|
||||||
#include <AppKit/NSInputServer.h>
|
#include <AppKit/NSInputServer.h>
|
||||||
#include <AppKit/NSText.h>
|
#include <AppKit/NSText.h>
|
||||||
|
|
||||||
|
#include "GSKeyBindingAction.h"
|
||||||
|
#include "GSKeyBindingTable.h"
|
||||||
|
|
||||||
/* A table mapping character names to characters, used to interpret
|
/* 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
|
#define CHARACTER_TABLE_SIZE 77
|
||||||
|
|
||||||
static struct
|
static struct
|
||||||
|
@ -124,7 +127,6 @@ character_table[CHARACTER_TABLE_SIZE] =
|
||||||
|
|
||||||
static NSInputManager *currentInputManager = nil;
|
static NSInputManager *currentInputManager = nil;
|
||||||
|
|
||||||
|
|
||||||
@implementation NSInputManager
|
@implementation NSInputManager
|
||||||
|
|
||||||
+ (NSInputManager *) currentInputManager
|
+ (NSInputManager *) currentInputManager
|
||||||
|
@ -137,24 +139,20 @@ static NSInputManager *currentInputManager = nil;
|
||||||
return currentInputManager;
|
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 */
|
int flags = 0;
|
||||||
unichar character = 0;
|
unichar c = 0;
|
||||||
unsigned flags = 0;
|
|
||||||
BOOL isFunctionKey = NO;
|
|
||||||
/* Then we parse the action into a selector */
|
|
||||||
SEL selector;
|
|
||||||
|
|
||||||
NSString *c;
|
|
||||||
|
|
||||||
/* Parse the key: first break it into segments separated by - */
|
/* Parse the key: first break it into segments separated by - */
|
||||||
NSArray *components = [key componentsSeparatedByString: @"-"];
|
NSArray *components = [key componentsSeparatedByString: @"-"];
|
||||||
|
NSString *name;
|
||||||
|
|
||||||
/* Then, parse the modifiers. The modifiers are the components
|
/* Then, parse the modifiers. The modifiers are the components
|
||||||
- all of them except the last one! */
|
- all of them except the last one! */
|
||||||
int i, count = [components count];
|
int i, count = [components count];
|
||||||
int index;
|
|
||||||
|
|
||||||
for (i = 0; i < count - 1; i++)
|
for (i = 0; i < count - 1; i++)
|
||||||
{
|
{
|
||||||
|
@ -179,122 +177,109 @@ static NSInputManager *currentInputManager = nil;
|
||||||
{
|
{
|
||||||
flags |= NSShiftKeyMask;
|
flags |= NSShiftKeyMask;
|
||||||
}
|
}
|
||||||
|
else if ([modifier isEqualToString: @"NumericPad"]
|
||||||
|
|| [modifier isEqualToString: @"Numeric"]
|
||||||
|
|| [modifier isEqualToString: @"N"])
|
||||||
|
{
|
||||||
|
flags |= NSNumericPadKeyMask;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NSLog (@"NSInputManager - unknown modifier '%@' ignored", modifier);
|
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. */
|
/* 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. */
|
/* This happens if '-' was the character. */
|
||||||
character = '-';
|
c = '-';
|
||||||
}
|
}
|
||||||
else if ([c length] == 1)
|
else if ([name length] == 1)
|
||||||
{
|
{
|
||||||
/* A single character, such as 'a'. */
|
/* A single character, such as 'a'. */
|
||||||
character = [c characterAtIndex: 0];
|
c = [name characterAtIndex: 0];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* A descriptive string, such as Tab or Home. */
|
/* A descriptive string, such as Tab or Home. */
|
||||||
for (i = 0; i < CHARACTER_TABLE_SIZE; i++)
|
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;
|
c = (character_table[i]).character;
|
||||||
isFunctionKey = YES;
|
flags |= NSFunctionKeyMask;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (i == CHARACTER_TABLE_SIZE)
|
if (i == CHARACTER_TABLE_SIZE)
|
||||||
{
|
{
|
||||||
NSLog (@"NSInputManager - unknown character '%@' ignored", c);
|
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
|
if (i == CHARACTER_TABLE_SIZE)
|
||||||
a previous keybinding. */
|
|
||||||
if ([action isEqualToString: @""])
|
|
||||||
{
|
{
|
||||||
selector = NULL;
|
NSString *c = [NSString stringWithCharacters: &character length: 1];
|
||||||
|
[description appendString: c];
|
||||||
}
|
}
|
||||||
else
|
return description;
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) loadBindingsFromFile: (NSString *)fullPath
|
- (void) loadBindingsFromFile: (NSString *)fullPath
|
||||||
|
@ -302,8 +287,6 @@ static NSInputManager *currentInputManager = nil;
|
||||||
NS_DURING
|
NS_DURING
|
||||||
{
|
{
|
||||||
NSDictionary *bindings;
|
NSDictionary *bindings;
|
||||||
NSEnumerator *e;
|
|
||||||
NSString *key;
|
|
||||||
|
|
||||||
bindings = [NSDictionary dictionaryWithContentsOfFile: fullPath];
|
bindings = [NSDictionary dictionaryWithContentsOfFile: fullPath];
|
||||||
if (bindings == nil)
|
if (bindings == nil)
|
||||||
|
@ -311,12 +294,7 @@ static NSInputManager *currentInputManager = nil;
|
||||||
[NSException raise];
|
[NSException raise];
|
||||||
}
|
}
|
||||||
|
|
||||||
e = [bindings keyEnumerator];
|
[_rootBindingTable loadBindingsFromDictionary: bindings];
|
||||||
while ((key = [e nextObject]) != nil)
|
|
||||||
{
|
|
||||||
[self bindKey: key
|
|
||||||
toAction: [bindings objectForKey: key]];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
NS_HANDLER
|
NS_HANDLER
|
||||||
{
|
{
|
||||||
|
@ -363,6 +341,46 @@ static NSInputManager *currentInputManager = nil;
|
||||||
|
|
||||||
self = [super init];
|
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
|
/* Normally, when we start up, we load all the keybindings we find
|
||||||
in the following files, in this order:
|
in the following files, in this order:
|
||||||
|
|
||||||
|
@ -436,6 +454,13 @@ static NSInputManager *currentInputManager = nil;
|
||||||
NSEvent *theEvent;
|
NSEvent *theEvent;
|
||||||
NSEnumerator *eventEnum = [eventArray objectEnumerator];
|
NSEnumerator *eventEnum = [eventArray objectEnumerator];
|
||||||
|
|
||||||
|
/* If the client has changed, reset our internal state before going
|
||||||
|
on. */
|
||||||
|
if (client != _currentClient)
|
||||||
|
{
|
||||||
|
[self resetInternalState];
|
||||||
|
}
|
||||||
|
|
||||||
_currentClient = client;
|
_currentClient = client;
|
||||||
|
|
||||||
while ((theEvent = [eventEnum nextObject]) != nil)
|
while ((theEvent = [eventEnum nextObject]) != nil)
|
||||||
|
@ -446,73 +471,151 @@ static NSInputManager *currentInputManager = nil;
|
||||||
unsigned flags = [theEvent modifierFlags] & (NSShiftKeyMask
|
unsigned flags = [theEvent modifierFlags] & (NSShiftKeyMask
|
||||||
| NSAlternateKeyMask
|
| NSAlternateKeyMask
|
||||||
| NSControlKeyMask);
|
| NSControlKeyMask);
|
||||||
BOOL done = NO;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
if ([unmodifiedCharacters length] > 0)
|
if ([unmodifiedCharacters length] > 0)
|
||||||
{
|
{
|
||||||
character = [unmodifiedCharacters characterAtIndex: 0];
|
character = [unmodifiedCharacters characterAtIndex: 0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Look up the character in the keybindings dictionary. */
|
if (!_interpretNextKeyStrokeLiterally)
|
||||||
for (i = 0; i < _bindingsCount; i++)
|
|
||||||
{
|
{
|
||||||
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)
|
/* Reset our internal state. */
|
||||||
{
|
[self resetInternalState];
|
||||||
/* Get out of loop with done = NO - we found the
|
|
||||||
keybinding, but it was bound to the default
|
/* Take the very first event we received and which we
|
||||||
action, so we stop searching and fall back on the
|
tried to interpret as a key binding, which now we
|
||||||
default action. */
|
know was the wrong thing to do. */
|
||||||
break;
|
e = [a objectAtIndex: 0];
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
[self doCommandBySelector: selector];
|
|
||||||
done = YES;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If not yet found, perform the default action. */
|
/* Interpret it literally, since interpreting it as a
|
||||||
if (!done)
|
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 (insertBacktab:)];
|
||||||
[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;
|
|
||||||
}
|
}
|
||||||
|
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
|
- (BOOL) handleMouseEvent: (NSEvent *)theMouseEvent
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue