windowNumbersWithOptions: on Yosemite fails to return NSPanel windows of my application

407 views Asked by At

I have a document-based application, with a main document window and several "satellite" NSPanel windows showing related info. They are not floating, they can (and do) become key, and seem to be at the same layer as the main window.

I try to implement a show/hide action like thus: If a panel is not visible - show it. If it is visible, but not front - make it front. If it is visible and front - hide it.

For that I need to know if an NSPanel is "frontmost". Sadly no NSWindow API exists for that. I tried to use windowNumbersWithOptions to compare z-order of my Panels for that.

-(void) togglePanelVisibility:(PMXPanelController *)panelController {
    NSPanel *panel = [panelController window];
    if ([panel isVisible]) {
        NSArray *windowNumbers = [NSWindow windowNumbersWithOptions:0];
        if ([panel windowNumber] == [[windowNumbers firstObject] integerValue]) {
            [panel orderOut:self];
        }
        else {
            [panel makeKeyAndOrderFront:self];
        }
    } else
        [panelController showWindow:self];
}

Alas, the array I receive only includes one number - for my main document window. I can see my panels in the "Window" menu, I can click them to bring them to front, I can close them and use them - but I can't get their number via windowNumbersWithOptions:

Ideas anyone?

2

There are 2 answers

0
Motti Shneor On BEST ANSWER

Following the given suggestions, I finally came out with the following, which seems to work for me reasonably.

/* Here is the logic: If panel of panelController is frontMost - hide it.
    If it is visible, but not frontMost - bring it to front.
    If it is not visible - Show it, and bring it to front. */

-(void) togglePanelVisibility:(PMXPanelController *)panelController 
{
    NSWindow *panel = [panelController window];
    if ([panel isVisible]) {
        BOOL panelIsFront = [self isPanelFront:panel];
        if (panelIsFront)
           [panel orderOut:self];
        else
           [panel makeKeyAndOrderFront:self];
    } else
        [panelController showWindow:self];     
}

// Attempt to determine if a given panel (window) is front window
-(BOOL) isPanelFront:(NSWindow *)panel 
{
    BOOL panelIsFront = NO;
#if 0
    // This simple implementation won't work because orderedWindows don't  contain panels.
    panelIsFront = [panel isEqualTo:[[NSApp orderedWindows] firstObject]];

    // This used to work, but broke on OS X 10.10 Yosemite. I only receive my main window.
    panelIsFront = ([panel windowNumber] == [[[NSWindow windowNumbersWithOptions:0] firstObject] integerValue]);
#endif

    // Last resort, - using CoreGraphics window list. Seems to work on all systems up to 10.10.4
    NSMutableArray *windowNumbers = [NSMutableArray arrayWithCapacity:32];    // I rely on window numbers to determine "frontness".
    CFArrayRef windowInfoArray = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
    for (NSDictionary* nswindowsdescription in (__bridge NSArray*)windowInfoArray)  {
        NSNumber *windowLayer = (NSNumber*)[nswindowsdescription objectForKey:(NSString *)kCGWindowLayer];
        if (windowLayer.integerValue != 0)  // filter out windows not in our normal window layer.
            continue;

        NSNumber* windowid = (NSNumber*)[nswindowsdescription objectForKey:(NSString *)kCGWindowNumber];
        if(windowid)
            [windowNumbers addObject:windowid];
    }
    CFRelease(windowInfoArray);

    panelIsFront = ([panel windowNumber] == [[windowNumbers firstObject] integerValue]);
    return panelIsFront;
}
2
Ian Bytchek On

It doesn't state this anywhere, but judging by the small number of windows returned with NSWindowNumberListAllApplications it looks like windowNumbersWithOptions only returns windows from the normal level or applies some kind of filtering that doesn't include other windows. The best alternative is orderedWindows property of NSApplication instance, but since you've mentioned panels be warned:

Only windows that are typically scriptable are included in the array. For example, panels are not included.

It's good to remember that both methods depend on Quartz API and probably use CGWindowListCopyWindowInfo – in worst case scenario you can go to lower level and use that instead.