NSOpenPanel above a fullscreen NSWindow? - objective-c

I open a window with the following:
NSRect screenRect = [[NSScreen mainScreen] frame];
[super initWithContentRect:screenRect
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO];
int windowLevel = CGShieldingWindowLevel();
[self setLevel:windowLevel];
... so the window is fullscreen & above all other window levels (including modal windows). I later want to display an open panel, however the following opens the dialog below the window I created above (it seems that the runModal stuff overrides the requested window level I try to set):
NSOpenPanel *OP = [NSOpenPanel openPanel];
int windowLevel = CGShieldingWindowLevel();
[OP setLevel:windowLevel];
int returnCode = [OP runModal];
... and the following opens a sheet on the window created above (good), however it also winds up showing the menu bar, which I had previously hidden (not what I want):
NSOpenPanel *OP = [NSOpenPanel openPanel];
[OP beginSheetModalForWindow:[self window]
completionHandler:^(NSInteger returnCode) {
NSLog(#"completionHandler called with %d", returnCode);
}];
... so my questions are:
Does anyone know how to open a modal window above the CGShieldingWindowLevel ?
Is there any way to get the menu bar to not show up on the sheet solution I'm trying above ?
Thanks all :-)

OK, here's an even better option - missed this one completely when I was reviewing the docs:
NSOpenPanel *OP = [NSOpenPanel openPanel];
[OP setLevel:CGShieldingWindowLevel()];
[OP beginWithCompletionHandler:^(NSInteger returnCode) {
NSLog(#"completionHandler called with %d", returnCode);
}];
... ie: open the panel as it's own window, which was exactly what I wanted to do in the fist place (duh!)

You can create a category of NSSavePanel like this :
#implementation NSSavePanel (SavePanelSetLevel)
- (void)setLevel:(NSInteger)newLevel
{
[super setLevel:CGShieldingWindowLevel()] ; // NSWindow implementation call
}
#end
because runModal reset the level previously set !

OK, 5 years later, I sorta can make this work - the trick is to open up a second window, promote it to the CGShieldingWindowLevel, make it key & order front, then attach the open sheet to it - the sheet magically appears from wherever the second window is, and although not perfect, it looks a lot better than the solution I came up with originally. Here's the change:
NSOpenPanel *OP = [NSOpenPanel openPanel];
// this is the new bit - make the window 1x1 # the location of your liking
NSRect windowRect = NSMakeRect(0, 1000, 1, 1);
NSWindow *OPW = [[NSWindow alloc] initWithContentRect:windowRect
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO];
int windowLevel = CGShieldingWindowLevel();
[OPW setLevel:windowLevel];
[OPW makeKeyAndOrderFront:nil];
// end of new bit, apart from passing OPW for beginSheetModalForWindow
// instead of [self window]
[OP beginSheetModalForWindow:OPW
completionHandler:^(NSInteger returnCode) {
NSLog(#"completionHandler called with %d", returnCode);
}];
... the only think to watch out for is that with the below you can wind up opening up several open dialogs, since the sheet is modal for a window other than the main window - the main window can still accept mouse click events ...

Related

Disable focus on second screen in objective-c

i have an app and i try it on multi screen. I have two screens and i want to disable focus on the second screen when the both sreens are enterFullscreen, i want to force the focus on the main screen.
I tried solutions i found here but doesn't change anything.
following code shows how i enterFullScreen for my mainWindow and my second Window
[self.window.contentView enterFullScreenMode:[[NSScreen screens] firstObject] withOptions:nil];
[windowArray insertObject:self.window atIndex:0];
NSRect screenRect;
NSArray *screenArray = [NSScreen screens];
for (NSInteger index = 1; index < [screenArray count]; index++)
{
NSScreen *screen = [screenArray objectAtIndex: index];
screenRect = CGRectMake(0, 0, screen.frame.size.width , screen.frame.size.height);
NSWindow *window = [[NSWindow alloc] initWithContentRect:screenRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO screen:screen];
[window.contentView setWantsLayer:YES];
window.contentView.layer.backgroundColor = [NSColor blackColor].CGColor;
[window.contentView enterFullScreenMode:[[NSScreen screens] objectAtIndex:index] withOptions:nil];
[windowArray addObject:window];
}
When both screens are in fullScreen mode, when i click on the second window i have the focus on the second window (normal event) but i want to disable that and force to put the focus on my main Window. I tried to disable mouse event on the second screen but.. not working.
If someone can help me ! thanks in advance
I don't know if it's the best answer for you, but you can iterate through all the subviews of the content view, and call setEnabled: on all of them. You can see a better and complete answer here: https://stackoverflow.com/a/16336624/2923506
for (NSView *object in [self.window.contentView subviews]) {
// Check it out the object
// set enable to NO [(id)object setEnabled:NO];
}

NSWindow subclass does not draw title bar on second rendering

I'm writing some glue GUI code for a scripting language implementation, and need to be able to programmatically create windows and intercept the key and mouse commands on their views. I've subclassed both NSWindow and NSView.
When I open a window for the first time, I get a window with a title bar and controls. If I close this window and open another, the controls and title don't appear in the new window. However, if I click where the controls should be, the new window (with invisible controls) still closes.
Is there something I'm doing in my window initialization that could cause this?
+ (HMSLWindow*)hmslWindowWithTitle:(NSString *)title frame:(NSRect)frame {
HMSLWindow* hmslWindow = [[HMSLWindow alloc]
initWithContentRect: frame
styleMask: NSMiniaturizableWindowMask | NSTitledWindowMask | NSClosableWindowMask
backing: NSBackingStoreBuffered
defer: YES];
hmslWindow.title = [title retain];
[hmslWindow setContentView:[[HMSLView alloc] initWithFrame:frame]];
hmslWindow.delegate = [[HMSLWindowDelegate alloc] init];
[hmslWindow cascadeTopLeftFromPoint:NSZeroPoint];
[hmslWindow makeKeyAndOrderFront:self];
[[HMSLWindow windowDictionary] setObject:hmslWindow forKey:[NSNumber numberWithInteger:hmslWindow.windowNumber]];
return hmslWindow;
}
- (void)close {
[[HMSLWindow windowDictionary]
removeObjectForKey:[NSNumber numberWithInteger:self.windowNumber]];
[self.contentView autorelease];
[self.delegate autorelease];
[self.title autorelease];
[super close];
}
First window:
Second window (contentView with white background is correct, but the titlebar is now empty):

How to override a fullscreen game with keywindow borderless window (overlay)?

I've created the app which sometimes shows up an overlay with label and textbox. It works nice, but I need it to work even with other apps are in full-screen mode and active.
For overlay, I've create custom window class and overridden canBecomeKeyWindow method to let borderless window become the key window (simply returns YES).
So it works, but when I run e.g. Minecraft, and then make it full screen, my overlay can override it. But I can't type in NSTextField in the overlay. How to fix it?
I'm creating an overlay like this:
[[NSWorkspace sharedWorkspace] hideOtherApplications];
NSRect frame = [[NSScreen mainScreen] frame];
_fadedWindow = [[CustonWindow alloc] initWithContentRect:frame
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO];
[_fadedWindow setAcceptsMouseMovedEvents:YES];
[_fadedWindow setOpaque:NO];
[_fadedWindow setLevel:CGShieldingWindowLevel()];
[_fadedWindow setBackgroundColor:[NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:0.8]];
NSApplicationPresentationOptions options = NSApplicationPresentationDisableProcessSwitching + NSApplicationPresentationHideDock + NSApplicationPresentationDisableForceQuit + NSApplicationPresentationDisableSessionTermination + NSApplicationPresentationDisableHideApplication;
[NSApp setPresentationOptions:options];
_fadedWindow.alphaValue = 0;
[_fadedWindow orderFrontRegardless];
[[_fadedWindow animator] setAlphaValue:1];
[_fadedWindow toggleFullScreen:self];
[_fadedWindow makeKeyAndOrderFront:self];
[NSApp activateIgnoringOtherApps:YES];
[_fadedWindow orderFront:self];
But still, I can't seem to populate overlay's NSTextField with keyboard input.
Try this. Create a subclass for _fadedWindow. Then put this in:
-(BOOL)canBecomeKeyWindow {
return YES;
}

How to correctly display a "progress" sheet modally while using Grand Central Dispatch to process something?

I'm trying to display a sheet on a window containing a single progress bar, to show the progress of some long function running asynchronously using Grand Central Dispatch. I've almost got it, but can't get the sheet to appear to be in focus, probably because I haven't used runModalForWindow: or similar.
This is approximately what I'm doing at the moment, it happens as a result of a button press on the main window:
// Prepare sheet and show it...
[NSApp beginSheet:progressSheet modalForWindow:window modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
[progressSheet makeKeyAndOrderFront:self];
[progressBar setIndeterminate:NO];
[progressBar setDoubleValue:0.f];
[progressBar startAnimation:self];
// Start computation using GCD...
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 1000; i ++) {
// Do some large computation here
// ...
// Update the progress bar which is in the sheet:
dispatch_async(dispatch_get_main_queue(), ^{
[progressBar setDoubleValue:(double)i];
});
}
// Calculation finished, remove sheet on main thread
dispatch_async(dispatch_get_main_queue(), ^{
[progressBar setIndeterminate:YES];
[NSApp endSheet:progressSheet];
[progressSheet orderOut:self];
});
});
This works, except the main window is still in focus, the sheet is out of focus, and the progress bar doesn't animate (unless I use setUsesThreadedAnimation:YES on it).
The problem I think I'm having is that I'm not sure how to run the sheet modally without blocking the main thread before I start the asynchronous computation?
As stated by Brad, it should work.
To do a quick test, I created a sheet programmatically (normally, you would probably use a nib file, but they are hard to paste into this text). If I call the code below from a button in a normal Cocoa window, it works as expected. Notice that the text field on the sheet is first responder, and if you type on the keyboard while it is open, it will accept the input.
#define maxloop 1000
- (IBAction)startTask:(id)sender
{
// Prepare sheet and show it...
breakLoop = NO;
NSRect sheetRect = NSMakeRect(0, 0, 400, 114);
NSWindow *progSheet = [[NSWindow alloc] initWithContentRect:sheetRect
styleMask:NSTitledWindowMask
backing:NSBackingStoreBuffered
defer:YES];
NSView *contentView = [[NSView alloc] initWithFrame:sheetRect];
NSProgressIndicator *progInd = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(143, 74, 239, 20)];
NSTextField *inputField = [[NSTextField alloc] initWithFrame:NSMakeRect(145, 48, 235, 22)];
NSButton *cancelButton = [[NSButton alloc] initWithFrame:NSMakeRect(304, 12, 82, 32)];
cancelButton.bezelStyle = NSRoundedBezelStyle;
cancelButton.title = #"Cancel";
cancelButton.action = #selector(cancelTask:);
cancelButton.target = self;
[contentView addSubview:progInd];
[contentView addSubview:inputField];
[contentView addSubview:cancelButton];
[progSheet setContentView:contentView];
[NSApp beginSheet:progSheet
modalForWindow:self.window
modalDelegate:nil
didEndSelector:NULL
contextInfo:NULL];
[progSheet makeKeyAndOrderFront:self];
[progInd setIndeterminate:NO];
[progInd setDoubleValue:0.f];
[progInd startAnimation:self];
// Start computation using GCD...
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < maxloop; i++) {
[NSThread sleepForTimeInterval:0.01];
if (breakLoop)
{
break;
}
// Update the progress bar which is in the sheet:
dispatch_async(dispatch_get_main_queue(), ^{
[progInd setDoubleValue: (double)i/maxloop * 100];
});
}
// Calculation finished, remove sheet on main thread
dispatch_async(dispatch_get_main_queue(), ^{
[progInd setIndeterminate:YES];
[NSApp endSheet:progSheet];
[progSheet orderOut:self];
});
});
}
- (IBAction)cancelTask:(id)sender
{
NSLog(#"Cancelling");
breakLoop = YES;
}
Apologies for the ugly sheet, but apart from that this code works as expected, so the issue you are seeing is probably unrelated to GCD.
I had exactly the same problem. With some trial and error, I found the solution. Make sure your sheet's window is (a) an NSWindow not an NSPanel (this may not matter) and that the window has a Title Bar (which, as it's a sheet you're using) will not be displayed.
I turned the Title Bar off for that reason, but somehow it's required to correctly achieve focus. Ticking the Title Bar checkbox gives my progress bar sheet focus.

Dismiss Custom Window for NSStatusItem

I've got a custom window popping up when an NSStatusItem is clicked. The code is based on MAAtachedwindow. Everything is working great but I can't figure out a way to dismiss the window when the user clicks on something else like another status bar item, or another app.
Here's my code for creating the window:
statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:width] retain];
//setup custom status menu view
CGFloat height = [[NSStatusBar systemStatusBar] thickness];
NSRect viewFrame = NSMakeRect(0.0f, 0.0f, width, height);
statusMenuView = [[[_ISStatusMenuView alloc] initWithFrame:viewFrame] retain];
statusMenuView.offset = aOffset;
statusItem.view = statusMenuView;
//setup the window to show when clicked
NSRect contentRect = NSZeroRect;
contentRect.size = aView.frame.size;
statusMenuWindow = [[[NSWindow alloc] initWithContentRect:contentRect
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO] retain];
[statusMenuWindow setLevel:NSPopUpMenuWindowLevel];
[statusMenuWindow setBackgroundColor:[NSColor clearColor]];
[statusMenuWindow setMovableByWindowBackground:NO];
[statusMenuWindow setExcludedFromWindowsMenu:YES];
[statusMenuWindow setOpaque:NO];
[statusMenuWindow setHasShadow:NO];
[statusMenuWindow useOptimizedDrawing:YES];
[[statusMenuWindow contentView] addSubview:aView];
[statusMenuWindow setDelegate:self];
statusMenuView.statusMenuWindow = statusMenuWindow;
And here is how I'm showing the window:
- (void)centerView{
NSRect menuFrame = self.window.frame;
NSRect windowFrame = self.statusMenuWindow.frame;
NSPoint menuPoint = NSMakePoint(NSMidX(menuFrame), NSMinY(menuFrame));
menuPoint.x -= windowFrame.size.width*0.5f;
menuPoint.y -= windowFrame.size.height+self.offset;
[self.statusMenuWindow setFrameOrigin:menuPoint];
[self.statusMenuWindow makeKeyAndOrderFront:self];
}
I was hoping the windowDidResignKey delegate method would do the trick but it doesn't go off with this configuration. The delegate is working because windowDidMove does run.
- (void)windowDidResignKey:(NSNotification *)notification{
NSLog(#"windowDidResignKey");
[statusMenuView hideView];
}
- (void)windowDidResignMain:(NSNotification *)notification{
NSLog(#"windowDidResignMain");
}
- (void)windowDidMove:(NSNotification *)notification{
NSLog(#"windowDidMove");
}
So to recap, how can I hide my custom window when the user clicks on anything else, the way the standard status bar menus work?
Edit
After looking at the Popup example the only thing I was missing was I had to subclass NSPanel and make it so it could become the key window.
#interface Panel : NSPanel
#end
#implementation Panel
- (BOOL)canBecomeKeyWindow{
return YES;
}
#end
You need to make sure your window can become the key window, and call your window's orderOut: method when it resigns key. You should be using a custom NSWindowController, if you are not already, in which case you would just call its "close" method to dismiss your window.
Instead of posting a bunch of code, I would suggest you just look at this excellent, recently-posted example of attaching a window to a status item:
Shpakovski Popup Window Example