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

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];
}
}

Related

Re-open an NSWindow after it has been closed?

I have a subclass of an NSWindowController called UpgradeWindowController.
So far this works for the first click; the window launches. However if you close that window, and click the button again to show upgrade window, nothing happens.
- (IBAction)showUpgradeWindow:(id)sender {
if (!self.upgradeController){
self.upgradeController = [[UpgradeWindowController alloc] initWithWindowNibName:#"UpgradeWindow"];
}
[self.upgradeController showWindow:self];
}
Any ideas? Thanks
Ok, the problem was the XIB. The file's owner needed to be connected to the Window.
Give you a suggestion, the window can be objects stored in a variable or array, although this is closed, but is actually hidden, the next time we need to use it, can go to check the window if the object is loaded, if loaded to call and display it. This can be more interesting and convenient.

Intercepting option-close

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]

NSWindow tracking

I would like to track each time a certain window appears (becomes visible to the user) in a OS X app. Where would be the most adequate place to call the tracker?
windowWillLoad, maybe?
I expected to find something like windowWillAppear but it seems I'm thinking too much iOS.
How about getting notification such as NSWindowDidBecomeMainNotification, By main I guess the one which is top most on screen directly visible by user.
see : Apple Documentation
Yes, one would expect that a window would notify its delegate or its controller with a windowWillAppear or windowDidAppear message, or post a documented notification like NSWindowDidAppearNotification. But alas, none of those exist. I filed a bug report with Apple and was given the advice to use a storyboard and a view controller instead. This is unhelpful in legacy apps that already use a bunch of window controllers and xibs.
You could subclass NSWindow and override orderWindow:relativeTo: to send a notification. Most, but not quite all, of the messages that make a window show itself ultimately go through this method, including orderBack:, orderFront:, makeKeyAndOrderFront:, and -[NSWindowController showWindow:]. But orderFrontRegardless does not go through orderWindow:relativeTo:, so you would also want to override that for completeness.
Another way to be notified is to make a subclass of NSViewController that controls some view that's always visible in the window. The view controller will receive viewWillAppear and viewDidAppear.
If you're subclassing NSWindow or NSViewController already for some other reason, either of these is a reasonable solution.
If you're not subclassing NSWindow already, and don't have an NSViewController subclass for a view that's always visible in the window, then another way is to use Cocoa bindings to connect the window's visible binding to a property one of your objects. For example, I have a custom NSWindowController subclass. I gave it a windowIsVisible property:
#interface MyWindowController ()
#property (nonatomic) BOOL windowIsVisible;
#end
and I implemented the accessors like this:
- (BOOL)windowIsVisible { return self.window.visible; }
- (void)setWindowIsVisible:(BOOL)windowIsVisible {
NSLog(#"window %# became %s", self.window, windowIsVisible ? "visible" : "hidden");
}
and in awakeFromNib, I bind the window's visible binding to the property like this:
- (void)awakeFromNib {
[super awakeFromNib];
[self.window bind:NSVisibleBinding toObject:self withKeyPath:NSStringFromSelector(#selector(windowIsVisible)) options:nil];
}
When the window becomes visible, the setWindowIsVisible: setter is called with an argument of YES. Note that if the whole app is hidden and reappears, the setter is called again, even though it wasn't called with argument NO when the app was hidden. So be careful not to assume the window was previously hidden.
Also, the binding might create a retain cycle, so you should probably unbind it when the window is closed, unless you want to keep the window and controller around. Note that the window does post NSWindowWillCloseNotification when it's closing, so you don't need any special magic to detect that.

-shortcutRecorder:keyComboDidChange: isn’t executed

I am writing a Cocoa application and I'd like to implement a global hotkey function.
I implemented the ShortcutRecorder.framework from Waffle Software and I added a customView to my xib. Then I subclassed the CustomView to SRRecorderControl. Now I see the Recorder in my Window, but how can I get the KeyCombo and how can I react on that?
I implemented the keyComboDidChange method with no luck to get the keycode. What am I doing wrong?
Here is my code for getting the keycode:
- (void)shortcutRecorder:(SRRecorderControl *)aRecorder keyComboDidChange:(KeyCombo)newKeyCombo
{
if (aRecorder == shortcutRecorder)
{
NSLog{"We got a new Key Combo");
}
}
shortcutrecorder is my IBOutlet btw.
Do I have to implement a protocol or setDelegate:self or something like that?
Edited to add
Actually I have declared my shortcutRecorder outlet in my Preferences.h. Then in the Identity Inspector I put "Preferences" as Custom Class for Files Owner in and I connect the delegate to my Shortcut Recorder... but the keyComboDidChange is never called ... I don't understand why.
Let me explain the steps I took to get it working:
Create a window xib
The file's owner of this class is, in my case, PreferencesWindowController
Create a referencing outlet from the window to the file's owner, by right clicking the window and dragging it to the file's owner
Add the custom view to your window
You have to connect the delegate of the ShortcutRecorder to the "File's owner". To do this, right-click the SRRecorderControl and drag the delegate to the "File's owner" on your left.
After this: the ShortcutRecorder only records the hotkey and leaves it to you what to do with it. You need to use the PTHotKeyCenter (which is shipped with ShortcutRecorder) or you could implement the shortcut handling yourself.
The ShortcutRecorder contains a great demo which demonstrates the use of the ShortcutRecorder in combination with the PTHotKeyCenter. It works like this:
Listen to events from the ShortcutRecorder (which you already do, but without setting the delegate)
Check if the globalHotKey variable is set
If so, unload the previous hotkey
Init a new hotkey with the settings from the ShortcutRecorder
Set the target and action to actually capture the hotkey, once pressed
Save the hotkey to the shared center (from this moment on, the hotkey will work)
Little sample, from their source:
if (globalHotKey != nil)
{
[[PTHotKeyCenter sharedCenter] unregisterHotKey: globalHotKey];
[globalHotKey release];
globalHotKey = nil;
}
globalHotKey = [[PTHotKey alloc] initWithIdentifier:#"SRTest"
keyCombo:[PTKeyCombo keyComboWithKeyCode:[shortcutRecorder keyCombo].code
modifiers:[shortcutRecorder cocoaToCarbonFlags: [shortcutRecorder keyCombo].flags]]];
[globalHotKey setTarget: self];
[globalHotKey setAction: #selector(hitHotKey:)];
[[PTHotKeyCenter sharedCenter] registerHotKey: globalHotKey];
The only thing left to do is the hotkey handler:
- (void)hitHotKey:(PTHotKey *)hotKey
{
NSLog(#"Hotkey pressed!");
}
You could easily save the hotkey settings to the UserDefaults to load them every time you application starts.
Actually I have declared my shortcutRecorder outlet in my Preferences.h. Then in the Identity Inspector I put "Preferences" as Custom Class for Files Owner in and I connect the delegate to my Shortcut Recorder... but the keyComboDidChange is never called ... I don't understand why -.-

Make NSView in NSPanel first responder without key window status

Is it possible to give an NSView inside an NSPanel first responder status without giving the NSPanel key window status (making the main application window resign key)?
Thanks.
Well, I ended up figuring this one out, but it took a lot of research so I'll post the details here in case anyone else runs into the same problem. First of all, a few basics:
It's impossible to have 2 windows actually be key at the same time
It's possible to fake a window into thinking it's key by overriding -isKeyWindow but that won't give the views contained in the window first responder status.
My Scenario:
I added a child window containing an NSTableView into my main application window (the reason is irrelavant). The child window was an NSPanel with NSBorderlessWindowMask. I wanted to give the NSTableView first responder status without making the panel the key window because it took away focus from the main window (and the whole point of the child window illusion was to make the child window look like it was part of the main window).
The first thing I tried was fooling the table view into thinking that it was inside the key window by overriding isKeyWindow to return YES. This made the table view draw as if it were the first responder, but still did not give it first responder status.
The Solution:
So by default, NSBorderlessWindowMask will not allow the window to become key. To make the table view first responder, the window had to be key so I overrode canBecomeKeyWindow in the borderless window subclass to return YES. This, of course, took away key status from the main window, which was one of the things I wanted to avoid. To fix this, I subclassed my main window and overrode the following methods:
- (BOOL)isMainWindow
{
return YES;
}
- (BOOL)isKeyWindow
{
return ([NSApp isActive]) ? YES : [super isKeyWindow];
}
This subclass checks if the application is active, and if it is, it always returns YES so that no matter what window is active in your application, the main window will always behave as if it is still key. This sort of gives the illusion that you can have multiple windows be key at the same time and enables you to shift key window status to another window without losing it on your main window. Hope this helps!

Resources