NSMenuItem and bare esc key equiv - objective-c

I'm trying to bind a bare esc key press to an NSMenuItem that toggles full screen (currently just a stub function). Manually selecting the menu item sends the desired IBAction. When I set the NSMenuItem's Key Equiv. in Interface Builder to an arbitrary bare key (eg. w) that key command sends the desired IBAction. When I set the Key Equiv. to command + esc, that key command sends the desired IBAction. But a bare esc key press is ignored.
I'm assuming the esc key is special-cased. Other applications (eg. Bannister's various emulators) are able to achieve this, any idea how?

I'm no Objective-C veteran, so apologies if I'm misunderstanding the question. But have you tried moving up the responder chain and grabbing the keyDown event in NSWindow? Something like:
- (void)keyDown: (NSEvent *) event {
if ([event keyCode] == 53) {
NSLog(#"Esc. pressed");
}
}
Of course, this solution will require that you subclass NSWindow.

The Escape key is tightly bound to the cancelOperation class of NSResponder.
Try to subclass your NSWindow and give it this method:
- (void)cancelOperation:(id)sender {
if (![SomeController doSomeAction]) {
[super cancelOperation:sender];
}
}
Your window will then react to the Escape key and evoke doSomeAction. That would be the method your NSMenuItem would have liked to call, but it refused to do so :)
In the doSomeAction method you should return a Boolean that indicates whether the action did actually do something. If it did, good. If it did not (and returned NO), your NSWindow will pass on the Escape key event to the next responder in the chain.
I like this solution because it gives the user audio feedback about whether his key press actually did something. Because if your method didn't do anything, and no other repsonder did do anything, there will be some "beep" sound.
Note that in an NSView without an attached window, you might have to use [self nextResponder] instead of super.

Please have a look at http://developer.apple.com/mac/library/documentation/UserExperience/Conceptual/AppleHIGuidelines/XHIGUserInput/XHIGUserInput.html
“… The Esc (Escape) key basically means “let me out of here.” It has specific meanings in certain contexts. The user can press Esc in the following situations: …”
Greetings

Related

Capture the space key in a window containing several NSTextFields

I have a large window with a number of NSTextField fields on it, and I would like to intercept the space bar even while any of the text fields have focus, so that pressing space at any time will cause a separate action.
Subclassing NSTextField and overriding -keyUp: allows me to detect the space key, but a blank is still inserted in the text field. -keyDown: does not fire at all.
For other keys like Return and the arrow keys, I could use the control:textView:doCommandBySelector: delegate method, but it does not fire with the space bar.
There's a lot of advice out there for NSTextView, but I have found none for NSTextField.
Have you experimented with adding an event monitor? You create them via the NSEvent class method addLocalMonitorForEventsMatchingMask(_:handler:)
and they give you first dibs on the events you specify. In your case you'd add a monitor for keyDown events, then in the associated block you decide what to do with them. At the end of the block you can return the event just as it arrived, in which case it will behave normally, you can swallow the event (return nil), or you can modify the event by creating a new event object and returning that instead. Here's an example:
// You can add monitors anywhere - I've chosen the AppDelegate for
// this simple example.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask
handler:^NSEvent * (NSEvent * theEvent) {
if (theEvent.keyCode == 49) {
NSLog(#"swallowing spacebar");
theEvent = nil;
}
return theEvent;
}];
}
Since the behavior you're trying to implement is at the level of the window (or perhaps a view that's an ancestor of all of the text fields), you should consider implementing -performKeyEquivalent: at that level instead of trying to capture the space key in each individual text field.
In your implementation, check the event's modifierFlags to make sure none of Control, Option, or Command are pressed (assuming you only want to handle unmodified space key presses) and if its charactersIgnoringModifiers equals #" ". If so, do what you want and then return YES to indicate you've handled the key event. Otherwise, return NO to allow it to be handled normally.
If you know how to do it for NSTextView, then you know how to do it for NSTextField, because NSTextField uses an NSTextView called the "field editor" for editing. You can get the window's default field editor like so:
NSTextView* fieldEd = [myWindow fieldEditor: YES forObject: nil];
You can also use a window delegate to provide a custom field editor.
So you might want to look into ReactiveCocoa
Go to the video on this page and look at 5:14 to see something similar to what you might like.
ReactiveCocoa can be hooked into all your text fields and then any time a space is hit a signal handler could pick it up. (Probably, however, more than you want to get into).

Losing key-up event on OS X

I am writing a game-like app for OS X and need to know if the left and right arrows are pressed during the game loop. I also check if the space bar is down. For this I implement
-(void)keyDown:(NSEvent *)event;
-(void)keyUp:(NSEvent *)event
in my view and store the state of each key in some flags. I also check if the command button is pressed using:
-(void)flagsChanged:(NSEvent *)event;
If the window isn't main (another window gets focus) or resigns key (spotlight opens) I reset the flags. I do this by implementing:
-(void)windowDidBecomeKey:(NSNotification *)notification;
-(void)windowDidResignKey:(NSNotification *)notification;
-(void)windowDidResignMain:(NSNotification *)notification;
-(void)windowDidBecomeMain:(NSNotification *)notification;
This works almost all the time. If I press command and then space, spotlight opens and my app resigns key. However, if I hold for example the left arrow key, and first presses and holds space, then command, spotlight won't show up, but I sometimes lose the key-up event for the arrow key when I release them. It doesn't happen every time but (could depend on the release order) but it is easily reproduced in a few tries. So the key gets stuck down until the next press.
Is there another state my app enters that I'm not aware of? If not, this approach seems a bit fragile.
Is there a more robust way of checking that a key is down (that doesn't require installation of a logger tool or enabling accessibility for the app in preferences)?
For this purpose I use NSEvent's addLocalMonitorForEventsMatchingMask:handler:-static method. It works while the app is active. Use addGlobalMonitor... for handling global events but be aware your app may be rejected from the AppStore. Here some code sample.
id monitor=[NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask handler:(NSEvent *)^(NSEvent *theEvent){
if (theEvent.keyCode==/*your key code*/) // you should check the key modifiers too
{
// your code here
}
return theEvent; // you may return the event to pass the key to the receiver or nil if no need
}];
// remove monitor
[NSEvent removeMonitor:monitor];

How to correctly handle <Return> + modifier keys in a NSTextField?

I want to be able to handle three different key combinations when a user is editing a NSTextField:
↩
⌘↩
⇧↩
Each of these combinations will be responded to differently.
Ideally, I would like to achieve this through IB alone. However, my understanding is that Cocoa only allows one sent action per control.
I can bind this sent action to an IBAction and set the NSTextField to only send it when the return key is pressed, but I can't find a way to get which modifier keys are currently held down (as the action only receives (id)sender, not an event object). Is that possible?
Alternatively, I guess it would be possible to subclass NSTextField, override keyDown:, and manually run the appropriate actions. However, I can't think of how to wire this up correctly in IB. (I could use an IBOutlet for the target, but I can't think of a way to wire up a specific method.) I'm also worried about the efficiency of this approach.
What is the normal way of handling certain key events on an NSTextField?
The approach which I'm leaning towards at the moment is writing a custom protocol and putting an outlet on the text field subclass:
#property (weak, nonatomic) IBOutlet id<TextFieldReturnHandler> target
which could then be called from the keypress event. This seems really convoluted, though.
You can wire the sent action up to a method on your controller:
- (IBAction)cellDidEndEditing:(id)sender;
If you only want this method to be called when the return key is pressed, and not, eg, when the user clicks away, you can set the Action property on the NSTextField in IB to 'Send On Enter Only' instead of 'Send On End Editing'.
Within this method, you can access the event with:
NSEvent *evt = [NSApp currentEvent];
After checking whether the event is valid, you can obtain and act on the necessary modifier flags:
if (evt && evt.type == NSKeyDown) {
evt.modifierFlags // NSShiftKeyMask, NSCommandKeyMas, NSAlternateKeyMask
// Perform actions
}

Silence Cocoa error beep

I have a Cocoa application that captures keypresses through a custom view in the view hierarchy. This view implements the keyUp and keyDown methods, and the keypresses are received. Even so, Cocoa still insists on playing the system error sound/ding every time I press a key. Any solutions?
Note: Although I tried to make this view first responder, it didn't work. That may have something to do with it.
If you have unsuccessfully tried to make the view the first responder, it's most likely because NSView returns NO for acceptsFirstResponder. You can have your NSView subclass override acceptsFirstResponder to return YES:
- (BOOL)acceptsFirstResponder {
return YES;
}
That should eliminate the beeps. Alternatively, you could have the NSView subclass override NSResponder's performKeyEquivalent: method to return YES, which should also eliminate the NSBeeps:
- (BOOL)performKeyEquivalent:(NSEvent *)event {
return YES;
}
UPDATE:
Not sure what to suggest. I actually wrote a "Keyboard Cleaner Helper" app that's designed to basically do something similar to what you want. (I used it on my laptop when I wanted to clean the keyboard and didn't the hundreds of key presses to randomly rename files or result in repeated error beeps).
Sample project: http://www.markdouma.com/developer/KeyboardCleanerHelper.zip
Running that app, I can't get it to beep at all (notice calls are logged to Console).

Solve Bug in UITextField: When it is empty, Pressing backspace doesn't Trigger correct function

Men, I don't believe that this will be a problem to me. If I press a key in keyboard, the UITextField delegate trigger the function:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if ([string lenght]==0) NSLog("Backspace was pressed");
}
But the problem is: when the text is empty and I press backspace, this function IS NOT called. There are a way to detect the backspace was pressed in this situation?
Or I will have to send this bug to Apple?
Ps. I need this for the cursor go to previous UITextFild (if have one character and press backspace, it work)
Well I don't think this is a bug because the textfield is empty and therefore no characters have been changed in the range hence why the method isn't being fired. The UITextFieldDelegate Documentation by Apple says:
The text field calls this method whenever the user types a new character in the text field or deletes an existing character.
There are no existing characters in the TextField so the method is not fired. It doesn't help answer the question but it's not a bug in the SDK
To get the behaviour you want, this question is already answered here: Can I detect the delete key even if the UITextField is empty?
Actually this is not a bug. Here the delegate methods are not catching the event as there is no change in any values of textfield.
This can be implemented by subclassing UITextField and adding it as custom class of desired textfield.
Then override the method "deleteBackward"
This method will catch all the backspace events.
Code in Swift:
override func deleteBackward() {
super.deleteBackward()
// Enter your stuff here
}
Also make sure you that are calling super method too, as it is doing the basic functionalities of the event.