tags:

views:

892

answers:

7

Hey All, I have an application in which I'm trying to capture the shift key modifier to perform an action, however when I run the program and press and normal key without the shift key modifier I get a beep and the modifier and key are not sent to my keyDown event. The relevant code is:

NSString* eventChars = [theEvent charactersIgnoringModifiers];

if ([eventChars isEqualTo:@"w"]) {
 newPlayerRow++;
 direction = eUp;
} else if ([eventChars isEqualTo:@"x"]) {
 newPlayerRow--;
 direction = eDown;
} else if ([eventChars isEqualTo:@"a"]) {
 newPlayerCol--;
 direction = eLeft;
} else if ([eventChars isEqualTo:@"d"]) {
 newPlayerCol++;
 direction = eRight;
} else {
 [super keyDown:theEvent];
 return;
}

// handle the player firing a bullet
if (([theEvent modifierFlags] & (NSShiftKeyMask | NSAlphaShiftKeyMask)) != 0) {
 NSLog(@"Shift key");
 [self fireBulletAtColumn:newPlayerCol row:newPlayerRow inDirection:direction];
 [self setNeedsDisplay:YES];
} else {
 ...
}

I'm not sure what is causing this, but I'd like to be able to capture shift key presses. Thanks in advance for any help with this problem.

EDIT: Also I'm using a MacBook keyboard if that makes any difference.

EDIT: This is definitely a shift-centric problem as changing (NSShiftKeyMask | NSAlphaShiftKeyMask) to NSControlKeyMask does have the desired effect.

+1  A: 

From your code it looks like the code that checks for the modifier is never reached. Anything that is not "w,x,a,d" is sent to the superclass (see the else branch).

You probably want to move "[super keyDown:theEvent]; return;" at the end of your keyDown method.

diciu
I think he is testing the key first to determine the direction, then using the shift/control to select the type of action.
Nathan Kinsinger
A: 

Hey diciu, Initially I thought that might be it, but it does work if I change NSShiftKeyMask to NSControlKeyMask - so this is definitely a shift centric problem.

Eric Scrivner
If you have a comment on someone's answer, then enter it as a comment instead of in a separate post. That way it stays together.
Nathan Kinsinger
+2  A: 

Have you tried logging the values of eventChars and modifierFlags using NSLog? Perhaps it's something other than what you expected it to be. If the log statement doesn't show up in your output, then this code is not running at all and you have a problem somewhere else.

NSLog(@"Chars: %@, modifier flags: 0x%x", eventChars, [theEvent modifierFlags]);

Some other things worth noting:

  1. isEqualTo: is the AppleScript equality operator. It should work, but won't be as efficient because it goes through compare:. The proper method is either isEqualToString: or the more-generic isEqual:, either of which may do a straight equality comparison.
  2. The traditional set of movement keys in the QWERTY letter region is wsad, not wxad. This uses the inverted-T arrangement. You may also want to support the arrow keys, as many of us do have keyboards with their arrows in the inverted T arrangement.

    Furthermore, you should make these configurable by the user, and you should respond to key codes rather than letters because different layouts (French, Dvorak, etc.) will generate different letters. I have a table of key codes, adapted from Inside Macintosh, on my website. Use characters only for display purposes.

  3. Shouldn't it be “fireBulletFromColumn:row:inDirection:”? I don't know about you, but saying that the method fires a bullet at a cell tells me (and will tell you, after you haven't looked at the code for a year) that it fires the bullet toward that cell; replacing “At” with “From” avoids this moment of confusion.
Peter Hosey
+4  A: 

First, -charactersIgnoringModifiers doesn't ignore the shift key, so you will still get shifted characters (i.e UPPERCASE and !%#$%^&*) returned from it. What's probably happening in your function is: You press shift-w, your -isEqualTo: returns false because you're comparing a lowercase 'w' and an uppercase 'W', and so you return before getting to the shift-detection code at the bottom. The simplest solution is to just check for both.

However, if you want, for example, Arabic keyboardists to be able to easily use your app, you really shouldn't hardcode characters that may not even appear on the user's keyboard. The value returned by -keyCode refers to a key's position on the keyboard, not the represented character. For starters, the constants beginning in 'kVK_ANSI_' and 'kVK_' in Events.h (you may have to link to Carbon.framework and #include <Carbon/Carbon.h> to use those constants) can be compared to what -keyCode returns, and they refer to the key positions a QWERTY-using USian expects. So you can be (pretty) sure that, regardless of keyboard layout, the keycodes for 'wasd' (kVK_ANSI_W, kVK_ANSI_A, etc.) will refer to that triangle in the top left of your user's keyboard.

Boaz Stuller
A: 

Solving the immediate problem:

  1. Building on what Boaz said, the -charactersIgnoringModifiers does not include the Shift key (read the documentation) and will return capitalized characters when the Shift key is down. That is why the Control key worked and the Shift key didn't. Switch the string to lowercase before you test it:

    eventChars = [[theEvent charactersIgnoringModifiers] lowercaseString];
    
  2. After that, (as Peter suggested) change all the isEqualTo:'s to isEqualToString: (besides being correct, it's also documented as being faster).

However:

If you are planning on releasing this app to other people, you should really be using key codes (as Peter and Boaz suggested) so it will work on as many different keyboards and setups as possible.

Create a view in your preferences with an input for each command you have and record the key code for the key the user presses. Store these in your NSUserDefaults and check against those instead of your hard coded strings. You can create a default set for your current "wasd" keys.

See projects like Shortcut Recorder for examples of how to do that (or search your friend).

Nathan Kinsinger
A: 

Hey Guys, Thanks for all the tips. This is a quick and dirty prototype app, but thank you for all the code tips as they are still very helpful.

Eric Scrivner
Fair enough. My personal opinion is that, in the few cases you can use them, key codes are actually easier to use. Instead of using -isEqual, all your comparisons just become: if ([event keyCode] == kVK_ANSI_W) { ... }
Boaz Stuller
+2  A: 

If you just want to be notified of modifier key presses without a character key you should be overriding the NSResponder method flagsChanged.

- (void)flagsChanged:(NSEvent *)theEvent

Mark Thalman