Intercepting option-close - macos

Normally closing a window with option key down closes all the windows in the application. In my application, I'd like it to close only windows related to the window that the user was closing. How can I do that? I can implement windowShouldClose for all my windows, but how can I know which window the user clicked on?

You can see if the option key was held down in the event that is being processed like this:
([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask)!=0
If this is in response to the user clicking the window's close button, then you can find the window that was clicked like this: [[NSApp currentEvent] window]
I suppose you should also check that [NSApp currentEvent] is a mouse event, etc., but it seems like this combination of tests should get you the info that you want.
If, on the other hand, this is the user choosing the "Close Window" command from the "File" menu with the option key held down, you can override the performClose: method from NSWindows default implementation to your own, where you would do the currentEvent test above before calling [super performClose: sender]

Related

Close borderless floating NSWindow on click outside

I want to create a borderless window floating above the main window. The main window should be the key window (I want it to handle the keyboard and mouse events). Also, the floating window should close as the user clicks outside of it. Basically, I'm creating a very custom context menu, just like NSMenu. It should behave likewise.
So I created my window this way:
NSWindow *menuWindow = [NSWindow windowWithContentViewController:menuViewController];
menuWindow.styleMask = NSBorderlessWindowMask;
menuWindow.level = NSFloatingWindowLevel;
[menuWindow makeKeyAndOrderFront:self];
That works perfectly, but how do I handle clicks outside to close it? The window doesn't invoke delegate's windowDidResignKey, because it's not a key window. If it does overload canBecomeKeyWindow (so that it returns YES), then the floating window grabs the user input, which is not what I want.
So, is there any way to automatically close a borderless window? Can NSPanel help? I tried using it, but with no success (the becomesKeyOnlyIfNeeded selector doesn't do what I need).
To detect clicks outside the window but within your app, you could install an event monitor using +[NSEvent addLocalMonitorForEventsMatchingMask:handler:]. If you also want to close the window when the user clicks in a different app, you could observe the NSApplicationDidResignActiveNotification notification.
NSWindow has a property hidesOnDeactivate that should do this for you.
From the Apple Docs:
The value of this property is true if the window is removed from the
screen when its application is deactivated; false if it remains
onscreen. The default value for NSWindow is false; the default value
for NSPanel is true.

OSX: Prevent an window from closing when user hits cmd+w key

In my MAC app, in one use case, I prompt an window to the user and give him 2 options (say buttons Save and Cancel). I want to force the user to select either of the 2 buttons to close the window.
But currently I find that if the user hits "Command + w" key when window has the focus, the window gets closed. In the .xib resource file, I uncheck the "close" option but that only disables the close option in the window UI.
How do I make sure that my window ignores the "Command+w" key and stays as is without closing.
Have also tried removing the notification by adding below code in awakeFromNib method but did not help.
[[NSNotificationCenter defaultCenter] removeObserver:NSWindowWillCloseNotification ];
Have also tried to implement "windowShouldClose" delegate method and return NO, but this method is never called. The documentation too says that this method is not reliable.
You should use an NSAlert for this sort of prompt, probably run as a sheet on the window. That would avoid the problem of closing it.
In any case, the window's delegate can implement -windowShouldClose: to control if a window is allowed to close. You can make an object (often the window controller) be its delegate by declaring that it adopts the NSWindowDelegate protocol and connecting the window's delegate outlet to that object.
I recently had to solve a similar problem. I'm not sure that this is the 'right' way to do it. But it worked for my purposes, and might work for you.
By default, I think, the 'Close Window' (CMD+W) menu item is bound to the action 'performClose' on first-responder. If you remove this binding and instead bind to a custom IBAction on your application delegate or main window controller, it allows you to conditionally call the close method of the current key-window if it is not matching the instance that you want to keep alive.
#property (strong, nonatomic) MyWindowController *unstoppable;
-(IBAction)killActiveWindow:(id)sender
{
NSWindow *keyWindow = [[NSApplication sharedApplication]keyWindow];
if ([keyWindow isNotEqualTo: unstoppable.window]){
NSLog(#" CMD+W Closing Window %#",keyWindow.title);
[keyWindow close];
}
}

How to catch Command+W key action in OS X?

I subclassed NSWindows and implemented the keyDown: and keyUp: method like this:
- (void)keyDown:(NSEvent*)event
{
NSLog(#"Down: '%#'", [event characters]);
[super keyDown:event];
}
- (void)keyUp:(NSEvent*)event
{
NSLog(#"Up: '%#'", [event characters]);
[super keyUp:event];
}
If I pressed "W" key, it prints both my "Down" and "Up" correctly. But If I pressed Command+W combination, It prints ONLY the "Down" message, and windows' close action was not triggered. How should I do?
Command-W is typically the key equivalent of a menu item. It is handled by the menu when it is told to -performKeyEquivalent:. The menu item sends its action method to its target. The action method is typically -performClose: and the target is usually the first responder.
So, the normal processing of Command-W does not use the window's -keyDown: or -keyUp: at all.
If you want to control whether the window closes, have the window delegate implement -windowShouldClose:. If you want to be notified when the window is about to close, implement -windowWillClose: in the window delegate or observe the NSWindowWillCloseNotification notification.
Regarding keyboard handling, a quirk of -[NSApplication sendEvent:] is that it just doesn't dispatch any key up event if the Command key was down. If for some reason you really need to see the key up event, you'll have to implement a custom subclass of NSApplication, configure your Info.plist to make sure it's used, and implement an override of -sendEvent:.

NSWindow doesn't receive keyboard events

I am creating an NSWindow from a bundle loaded at runtime so I don't have the source code for the NSWindow that is created at app launch. When I show my window, it receives mouse event but it does not receive any keyboard events. I have tried adding the window with the following methods:
[_myWindow makeKeyAndOrderFront:nil];
[[NSApplication sharedApplication].mainWindow addChildWindow:_myWindow ordered:NSWindowAbove];
[NSApp beginModalSessionForWindow:_myWindow];
In all 3 cases it appears the main window is eating all the keyboard events. The mouse events also leak through to the original window (I can see mouse over highlights and buttons respond to clicks through my window). I have also tried:
[_myWindow makeMainWindow];
[_myWindow orderWindow:NSWindowAbove relativeTo:[originalWindow windowNumber]];
Any tips to get keyboard events to land on my window would be greatly appreciated.
You can subclass NSApplication and override sendEvent: to print [[NSApplication sharedApplication] keyWindow] before sending the event to the keyWindow (don't forget to call the super to actually send the event).
If the key window does not receive the events, then you might have an event monitor somewhere that grabs the events before they are dispatched.

Closing an NSWindow

I have a NSWindow that hosts a WebView that Ive hooked up to a script handler.
Now, when the user clicks a button on a control on the WebView it calls a Objective C method on my object.
In this specific case, the action of the button is to try and close the window hosting the WebView
[[webView window] close];
This usually works, but sometimes i get a SEGFAULT or some other access violation as a result of the event loop trying to dispatcha mouse message to the now destroyed view.
The callstack is horrible when I try to close the window, the even loop has called the window has called the webView, has called my script delegate when I try and close the window. Destruction of an object from a callback from that object is generally, well, dangerous, but I can't figure out how windows should safely be closed as a result of users interacting with views on them.
Istead of closing, can't you try out the API
- (void)orderOut:(id)sender
just check whether your window is visible and orderout that window
if([[webView window] isVisible])
[[webView window] orderOut:self];
The callstack is horrible when I try to close the window, the even loop has called the window has called the webView, has called my script delegate when I try and close the window. Destruction of an object from a callback from that object is generally, well, dangerous, but I can't figure out how windows should safely be closed as a result of users interacting with views on them.
You can use performSelector:withObject:afterDelay: to put off closing the window until 0.0 seconds after the button hit.
In this specific case, the action of the button is to try and close the window hosting the WebView
[[webView window] close];
This usually works, but sometimes i get a SEGFAULT or some other access violation as a result of the event loop trying to dispatcha mouse message to the now destroyed view.
That's not likely. The event loop will only dispatch an event for a window that exists; if you have closed and thereby destroyed a window, no event can arrive at that window, nor at any view that may once have been in it.
It would help if you would edit your question to include the stack trace for that crash.
I know this is an old question, but I'm having a similar issue and I suspect the accepted answer is missing the point. I believe the window hosting the WebView is still one or more of the delegates of the WebView, and delegate methods are being called after the WebView finishes loading, which is after the window is closed.
I was looking for the right way to resolve this… I'll keep looking. :-)

Resources