5

I am using Pyobjc to create a NSStatusItem. When it is clicked, I am showing an NSPopOver. This is working fine. However, my requirement is to show the popover as soon as the application starts without any action by the user. Calling the callback directly in finishLaunching is not working. Is there any way to achieve this? It will be good enough even if can just simulate the click on NSStatusView.

class TestApp(NSApplication):

    def finishLaunching(self):
        # Make statusbar item
        statusbar = NSStatusBar.systemStatusBar()
        self.statusitem = statusbar.statusItemWithLength_(NSVariableStatusItemLength)
        self.statusitem.setTarget_(self)
        self.statusitem.setAction_('statusItemClicked:')
        self.icon = NSImage.alloc().initByReferencingFile_('app-icon.png')
        self.icon.setScalesWhenResized_(True)
        self.icon.setSize_((20, 20))
        self.statusitem.setImage_(self.icon)
        self.statusitem.setHighlightMode_(1)

        # self.statusItemClicked_(None)

    def statusItemClicked_(self, notification):
        self.viewController = SimpleXibDemoController.alloc().initWithWindowNibName_("Login")

        # Show the window
        self.viewController.showWindow_(self.viewController)
        rect = self.statusitem.valueForKey_('button').frame()
        self.viewController.popover.showRelativeToRect_ofView_preferredEdge_(rect, self.statusitem.valueForKey_('button'), NSMaxYEdge)
Pradeep Vairamani
  • 4,004
  • 3
  • 36
  • 59
  • 1
    I have the exact same problem. It would seem that positioning the popover from finishLaunching is too early, as the status item frame hasn't been position on the menu bar yet, so the popover always shows at the bottom left of the screen. – Luke Aug 30 '14 at 14:02

2 Answers2

2

I finally got a somewhat sketchy solution. I have a method which positions the popover like so:

- (IBAction)showPopover:(id)sender {
    [popover showRelativeToRect:self.statusItemView.bounds ofView:self.statusItemView preferredEdge:NSMinYEdge];
}

In applicationDidFinishLaunching, or finishLaunching in your case, instead of calling the method directly I called it with performSelector instead:

[self performSelector:@selector(showPopover:) withObject:self afterDelay:0];

Even setting the delay to 0 works, which I do not know why, and it now positions the popover correctly.

Edit: This seems to only work situationally. Even by creating a view controller and calling it from viewDidAppear, the popover only gets positioned half of the time.

Edit 2: I added a window controller to the status item's window, and overrode windowDidLoad. However as it turned out, the window is loaded before I can even set the controller's window so windowDidLoad is not called.

Luke
  • 4,908
  • 1
  • 37
  • 59
  • This approach schedules the presentation on the next run loop. However, the view is not guaranteed to be visible then and this may crash under some circumstances. – Aaron Brager Aug 30 '14 at 14:31
1

You have to wait until the view appears to present the NSPopover. Override viewDidAppear in your NSViewController subclass and present it there. (Or override loadView if your minimum deployment target is less than OS X Yosemite.)

Aaron Brager
  • 65,323
  • 19
  • 161
  • 287
  • I tried to add a view controller to my status item view, but I couldn't get the view to show up in the menu bar even after setting the status item's view to the view controller's. Would you happen to have an example of how to do this? – Luke Aug 30 '14 at 15:26