Window won't close - macos

I made my own progress window dialog to use in my plugin for an Apple application. But when I try to close it programmatically it stays on screen. The weird thing is that closing the window has been working fine since El Capitan and it's only since Mojave that this issue has arisen.
The window is defined in a XIB file and subclassed as #interface ProgressWindow : NSWindowController.
The window is called in the plugin code with
ProgressWindow *progress = [[ProgressWindow alloc] initWithWindowNibName:#"ProgressWindow"];
[progress showWindow:self];
...
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while(...) {
// do work here
dispatch_async(dispatch_get_main_queue(), ^{
[progress updatePercentage];
});
}
dispatch_async(dispatch_get_main_queue(), ^{
[progress close];
[progress release];
});
});
The progress window stubbornly stays on screen. I traced the code path to close and release the window and it is called.
Something else has taken a reference to it ?

Related

NSWindow beginSheet completionHandler not called

I am showing a sheet within my main window. I present the sheet using this code:
AddContactWindowController *addContact = [[AddContactWindowController alloc] initWithWindowNibName:#"AddContactWindow"];
addContact.currentViewController = myView;
self.addWindowController = addContact;
[self.view.window beginSheet: addContact.window completionHandler:^(NSModalResponse returnCode) {
NSLog(#"completionHandler called");
}];
AddContactWindowController is a NSWindowController subclass. It has a view controller within it. Inside the view is a "close" button which invokes this:
[[[self view] window] close];
This does close the window, but the completionHandler from beginSheet is not invoked. This causes me problems down the road.
Is there any particular way we should close the NSWindow sheet for the completion handler to be successfully called? I've also tried [[[self view] window] orderOut:self] but that doesn't work either.
Thanks.
You will want to call -endSheet:returnCode: on your window, rather than just ordering it out.
You must properly finish the modal session.
I used to call - (void)performClose:(id)sender and stop the modal session in the delegate method.
- (void)windowWillClose:(NSNotification *)notification {
[NSApp stopModal];
}
But for a sheet, endSheet looks more appropriate.
self.addWindowController = addContact;
[self.view.window beginSheet:self.addWindowController.window];
...
...
[self.view.window endSheet:self.addWindowController.window];
self.addWindowController = nil

Full Screen Cocoa Application - Force Quit

I am facing a weird issue when trying to make a full screen application. I am currently using a a NSWindow with a NSBorderlessWindowMask to make the screen show at top, and I subclassed NSWindow to accept keys:
- (BOOL) canBecomeKeyWindow
{
return YES;
}
This works fine, and I can use keys in my window, and quit out of the application with Command + Q. However, when I try to force quit the application on my Mac, the screen freezes, and I must restart my computer. I have a simple WebView inside my Window:
mainWindow = [[MyBorderWindow alloc] initWithContentRect:screenRect
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO screen:[NSScreen mainScreen]];
[mainWindow setLevel:windowLevel];
[mainWindow setBackgroundColor:[NSColor blackColor]];
[mainWindow makeKeyAndOrderFront:nil];
NSView *contentView = [mainWindow contentView];
WebView *customView = [[WebView alloc] initWithFrame:[contentView bounds]];
[customView setTranslatesAutoresizingMaskIntoConstraints:NO];
[contentView addSubview:customView];
I have tried the application on another computer, and force quit does not freeze the screen. Both computers are running OS X Mavericks.
Please let me know if there is anything else I should add.
Thanks in advance,
Bucco
Edit:
I think that my applicationWillTerminate method in AppDelegate might be part of the issue, but am not sure. Why would this method work fine on another computer, but not mine?
- (void)applicationWillTerminate:(NSNotification *)notification
{
[mainWindow orderOut:self];
// Release the display(s)
if (CGDisplayRelease( kCGDirectMainDisplay ) != kCGErrorSuccess) {
NSLog( #"Couldn't release the display(s)!" );
}
}

NSOpenGLView toggle to fullscreen from within the view

I'm trying to create a method that will toggle between fullscreen and a window. I'm trying to do this from within a class inherited from NSOpenGLView, essentially following this blogpost. That works once, going from windowed to fullscreen; trying to go back fails in various ways: the window screen doesn't get updated, or I don't even manage switch to the window but the fullscreen just blanks out. Trying to go back and forth a few times anyway (mapped it to the 'f' key), the program often locks up, and in a worst case, I have to restart my computer.
I've attached the code for the method below; for debugging purposes, I've set the full frame rectangle much smaller, so that if things freeze, the application is never at full screen.
The fullscreen example in the Apple developer examples suggest using a controller, and does not go fullscreen from within the inherited NSOpenGLView.
My questions:
should I use a controller instead, and from there switch between windowed and fullscreen (creating a separate fullscreen view each time)? Or should both methods work?
If both methods should work, which one is preferred?
If both methods can work, what am I doing wrong in the current way of implementing this?
or, is there a third, better, method?
Note that for both references, I'll have to assume that things haven't changed for 10.8 (both references seem to apply to 10.6).
Code follows:
#implementation MyOpenGLView
[...]
- (void)toggleFullscreen
{
mainWindow = [self window];
if (isFullscreen) {
[fullscreenWindow close];
[mainWindow setAcceptsMouseMovedEvents:YES];
[mainWindow setContentView: self];
[mainWindow makeKeyAndOrderFront: self];
[mainWindow makeFirstResponder: self];
isFullscreen = false;
} else {
[mainWindow setAcceptsMouseMovedEvents:NO];
//NSRect fullscreenFrame = [[NSScreen mainScreen] frame];
NSRect fullscreenFrame = { {300, 300}, {300, 300} };
fullscreenWindow = [[NSWindow alloc] initWithContentRect:fullscreenFrame
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO];
if (fullscreenWindow) {
[fullscreenWindow setAcceptsMouseMovedEvents:YES];
[fullscreenWindow setTitle:#"Full screen"];
[fullscreenWindow setReleasedWhenClosed: YES];
[fullscreenWindow setContentView: self];
[fullscreenWindow makeKeyAndOrderFront: self];
//[fullscreenWindow setOpaque:YES];
//[fullscreenWindow setHidesOnDeactivate:YES];
// Set the window level to be just above the menu bar
//[fullScreenWindow setLevel:NSMainMenuWindowLevel+1];
// Set the window level to be just below the screen saver
[fullscreenWindow setLevel:NSScreenSaverWindowLevel-1];
[fullscreenWindow makeFirstResponder:self];
isFullscreen = true;
} else {
NSLog(#"Error: could not switch to full screen.");
}
}
}
[...]
#end
I now think this can't be done, and should not be done. When windowed, the rendering context is a window, which is a different beast than a screen, when rendering fullscreen.
Thus, when switching between, things have to re-setup everytime you switch.
It is possible to simply use the native fullscreen option that is in the newest OS X variants. This will (presumably) enlarge the containg window to full screen size while removing the frame, borders and buttons. Thus, you're still rendering to a window, though it looks fullscreen.
I'm not sure if this option makes things slower: there's a window layer in between, which could make it a slower than rendering directly to a screen.
For the curious, implementing the native fullscreen is ridiculously easy (at least in 10.8 and 10.9): In XCode, select the .xib file, select the (main) window in the editor's sidebar, then select the attributes selector on the right. You can find a "Full Screen" selection between Unsupported, Primary Window or Auxiliary Window. That will automatically add the full screen toggle to the window.
Even neater, now select the main menu -> view menu in the sidebar, find the "Full Screen Menu Item" in the inspector at the bottom (there's a search bar for it), drag it into the View menu in the editor, and voilà, it will have a shortcut and automatically connect to the full screen option for the window (select the new View menu item and look at the Connections inspector to it's already connected for you).
A nice way to test all this is to grab the full screen example I linked in my question, and edit it as suggested above. Using the default control-command F shortcut to toggle back and forth between fullscreen will show the opengl view and the frame with text below it in a full screen. Using the fullscreen option as coded in the example will toggle the openglview to use the fullscreen, without any extra (Cocoa) frames, buttons or text.
I'm curious about this too- specifically your first two bullet point questions.
This doesn't address those questions, but your third one about the bug, I think you can get away with just changing the properties of the same window (works for me):
- (void)toggleFullscreen
{
if (isFullscreen) {
NSRect windowFrame = [[NSScreen mainScreen] visibleFrame];
[mainWindow setStyleMask:NSTitledWindowMask | NSClosableWindowMask |
NSMiniaturizableWindowMask | NSResizableWindowMask ];
[mainWindow setFrame:windowFrame display:true];
[mainWindow setAcceptsMouseMovedEvents:YES];
[mainWindow setLevel:NSNormalWindowLevel];
[mainWindow setTitle:#"SimpleOculus"];
[mainWindow makeKeyAndOrderFront:self];
[mainWindow makeFirstResponder:self];
isFullscreen = false;
}
else {
NSRect fullscreenFrame = [[NSScreen mainScreen] frame];
[mainWindow setStyleMask:NSBorderlessWindowMask];
[mainWindow setFrame:fullscreenFrame display:true];
[mainWindow setAcceptsMouseMovedEvents:YES];
[mainWindow setLevel:NSScreenSaverWindowLevel-1];
[mainWindow makeKeyAndOrderFront:self];
[mainWindow makeFirstResponder:self];
isFullscreen = true;
}
}

Doing something after NSOpenPanel closes

I have an NSOpenPanel and I want to do some validation of the selection after the user has clicked OK. My code is simple:
void (^openPanelHandler)(NSInteger) = ^(NSInteger returnCode) {
if (returnCode == NSFileHandlingPanelOKButton) {
// do my validation
[self presentError:error]; // uh oh, something bad happened
}
}
[openPanel beginSheetModalForWindow:[self window]
completionHandler:openPanelHandler];
[self window] is an application-modal window. The panel opens as a sheet. So far so good.
Apple's docs say that the completion handler is supposed to be called "after the user has closed the panel." But in my case, it's called immediately upon the "OK/Cancel" button press, not upon the panel having closed. The effect of this is that the error alert opens above the open panel, not after the panel has closed. It still works, but it's not Mac-like.
What I would prefer is for the user to click OK, the open panel sheet to fold up, then the alert sheet to appear.
I guess I could present the alert using a delayed selector, but that seems like a hack.
Since the panel completion handler is invoked before the panel has effectively been closed,1 one solution is to observe NSWindowDidEndSheetNotification on your modal window:
Declare an instance variable/property in your class to hold the validation error;
Declare a method that will be executed when the panel is effectively closed. Define it so that if presents the error on the current window;
Have your class listen to NSWindowDidEndSheetNotification on [self window], executing the method declared above when the notification is sent;
In the panel completion handler, if the validation fails then assign the error to the instance variable/property declared above.
By doing this, the completion handler will only set the validation error. Soon after the handler is invoked, the open panel is closed and the notification will be sent to your object, which in turn presents the validation error that has been set by the completion handler.
For example:
In your class declaration, add:
#property (retain) NSError *validationError;
- (void)openPanelDidClose:(NSNotification *)notification;
In your class implementation, add:
#synthesize validationError;
- (void)dealloc {
[validationError release];
[super dealloc];
}
- (void)openPanelDidClose:(NSNotification *)notification {
if (self.validationError) [self presentError:error];
// or [[self window] presentError:error];
// Clear validationError so that further notifications
// don't show the error unless a new error has been set
self.validationError = nil;
// If [self window] presents other sheets, you don't
// want this method to be fired for them
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSWindowDidEndSheetNotification
object:[self window]];
}
// Assuming an action fires the open panel
- (IBAction)showOpenPanel:(id)sender {
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(openPanelDidClose:)
name:NSWindowDidEndSheetNotification
object:[self window]];
void (^openPanelHandler)(NSInteger) = ^(NSInteger returnCode) {
if (returnCode == NSFileHandlingPanelOKButton) {
// do my validation
// uh oh, something bad happened
self.validationError = error;
}
};
[openPanel beginSheetModalForWindow:[self window]
completionHandler:openPanelHandler];
}
1If you think this behaviour is wrong, consider filing a bug report with Apple. I don’t really remember whether an error should be presented over an open/save panel.

How do stop panels from becomming key window on launch?

I have a xib file with a main window and a panel. On awakeFromNib I try to orderFront the main window, but the panel keeps being key window.
- (void)awakeFromNib {
[inspectionPanelOutlet orderBack:self];
[inspectionPanelOutlet orderWindow:NSWindowBelow relativeTo:0];
[window makeKeyAndOrderFront:self];
}
This code has no effect.
Are you sure awakeFromNib is called (add an NSLog message and see if it fires)? If it is called you might want to try:
[self makeMainWindow];
or
[self makeKeyWindow];

Resources