Running event loop while showing non-standard popup

  • None of this code was written by me, so I don't know the reasoning behind the design. We have a popup that shows an NSWindow with an NSBrowser in it. There is a NSPopUpButtonCell subclass that handles the click with trackMouse:inRect:ofView:untilMouseUp:. This trackMouse method calls [NSApp run].

    When a click happens outside the browser window, it calls [NSApp stop:nil] from our NSWindowDidResignKeyNotification observer for the browser window. At that point, it seems like the app is actually trying to terminate, because all hell breaks loose - release builds crash with no crash log and debug builds crash with a stack of:

    #0-9 is in the dtor for one of our global objects.
    #10    0x91a1cebf in __cxa_finalize ()
    #11    0x91a1fdb9 in exit ()
    #12    0x957ba692 in NSApplicationMain ()
    #13    0x01310fb8 in main ()

    This leads me to believe that using run and stop: is the wrong way to do this. What's another way to start and stop a "local" run loop?

    --
    Steve Mills
    office: 952-818-3871
    home: 952-401-6255
    cell: 612-803-6157
  • On May 20, 2013, at 12:00 PM, Steve Mills wrote:

    > None of this code was written by me, so I don't know the reasoning behind the design. We have a popup that shows an NSWindow with an NSBrowser in it. There is a NSPopUpButtonCell subclass that handles the click with trackMouse:inRect:ofView:untilMouseUp:. This trackMouse method calls [NSApp run].
    >
    > When a click happens outside the browser window, it calls [NSApp stop:nil] from our NSWindowDidResignKeyNotification observer for the browser window. At that point, it seems like the app is actually trying to terminate, because all hell breaks loose - release builds crash with no crash log and debug builds crash with a stack of:
    >
    > #0-9 is in the dtor for one of our global objects.
    > #10    0x91a1cebf in __cxa_finalize ()
    > #11    0x91a1fdb9 in exit ()
    > #12    0x957ba692 in NSApplicationMain ()
    > #13    0x01310fb8 in main ()
    >
    > This leads me to believe that using run and stop: is the wrong way to do this.

    Yes.

    > What's another way to start and stop a "local" run loop?

    -[NSApplication runModalForWindow:] and either one of the -stopModal… or -abortModal methods.  To detect a click outside of the browser window, you may need to add a local event monitor (+[NSEvent addLocalMonitorForEventsMatchingMask:handler:]) as well as monitoring the window's key status.

    Regards,
    Ken
  • On May 20, 2013, at 13:19:51, Ken Thomases <ken...> wrote:

    > -[NSApplication runModalForWindow:] and either one of the -stopModal… or -abortModal methods.  To detect a click outside of the browser window, you may need to add a local event monitor (+[NSEvent addLocalMonitorForEventsMatchingMask:handler:]) as well as monitoring the window's key status.

    Thanks a million, Ken. The window is running modally and that's great. The only thing is that mousedowns outside the modal window only beep and don't get caught by the event monitor. Should they, or is the very nature of runModalForWindow preventing that?

    --
    Steve Mills
    office: 952-818-3871
    home: 952-401-6255
    cell: 612-803-6157
  • On May 20, 2013, at 8:06 PM, Steve Mills <smills...> wrote:

    > On May 20, 2013, at 13:19:51, Ken Thomases <ken...> wrote:
    >
    >> -[NSApplication runModalForWindow:] and either one of the -stopModal… or -abortModal methods.  To detect a click outside of the browser window, you may need to add a local event monitor (+[NSEvent addLocalMonitorForEventsMatchingMask:handler:]) as well as monitoring the window's key status.
    >
    > Thanks a million, Ken. The window is running modally and that's great. The only thing is that mousedowns outside the modal window only beep and don't get caught by the event monitor. Should they, or is the very nature of runModalForWindow preventing that?

    The docs for +[NSEvent addLocalMonitor…] state that it does not work with nested event loops like the kind -[NSApplication runModalForWindow:] uses.

    --Kyle Sluder
  • On May 21, 2013, at 1:58 AM, Kyle Sluder wrote:

    > On May 20, 2013, at 8:06 PM, Steve Mills <smills...> wrote:
    >
    >> On May 20, 2013, at 13:19:51, Ken Thomases <ken...> wrote:
    >>
    >>> -[NSApplication runModalForWindow:] and either one of the -stopModal… or -abortModal methods.  To detect a click outside of the browser window, you may need to add a local event monitor (+[NSEvent addLocalMonitorForEventsMatchingMask:handler:]) as well as monitoring the window's key status.
    >>
    >> Thanks a million, Ken. The window is running modally and that's great. The only thing is that mousedowns outside the modal window only beep and don't get caught by the event monitor. Should they, or is the very nature of runModalForWindow preventing that?
    >
    > The docs for +[NSEvent addLocalMonitor…] state that it does not work with nested event loops like the kind -[NSApplication runModalForWindow:] uses.

    Oh.  Oops.  My apologies for the bad advice.

    Regards,
    Ken
  • On May 21, 2013, at 01:58:00, Kyle Sluder <kyle...> wrote:

    > The docs for +[NSEvent addLocalMonitor…] state that it does not work with nested event loops like the kind -[NSApplication runModalForWindow:] uses.

    The docs don't specifically say runModalForWindow, which leads the reader to assume it would work. Docs should always spell out exactly when something won't work if the vendor already knows of such things.

    So then, what's the next thing to try? Seems like the next best thing is to subclass NSApplication, override sendEvent, and add special code to it just to handle this one small thing in a huge complex app. Or is there a way to create a new "regular" event loop?

    --
    Steve Mills
    office: 952-818-3871
    home: 952-401-6255
    cell: 612-803-6157
  • On May 21, 2013, at 6:51 AM, Steve Mills <smills...> wrote:

    > On May 21, 2013, at 01:58:00, Kyle Sluder <kyle...> wrote:
    >
    >> The docs for +[NSEvent addLocalMonitor…] state that it does not work with nested event loops like the kind -[NSApplication runModalForWindow:] uses.
    >
    > The docs don't specifically say runModalForWindow, which leads the reader to assume it would work. Docs should always spell out exactly when something won't work if the vendor already knows of such things.

    It quite clearly says it will not work with nested event loops. +runModalForWindow: is but one way to run a nested event loop. It's not the job of every method's documentation to teach you how the entire framework works.

    >
    > So then, what's the next thing to try? Seems like the next best thing is to subclass NSApplication, override sendEvent, and add special code to it just to handle this one small thing in a huge complex app.

    You could try it, but +runModalForWindow might not invoke -sendEvent: to dispatch the events it pulls off the event queue within its nested runloop invocation.

    > Or is there a way to create a new "regular" event loop?

    Let's back up to your original question. Why do you need to run a runloop at all? NSPopupButtonCell will do that for you, and it will correctly handle the case where the user clicks outside the menu to dismiss.

    Look up -performClickWithFrame:inView:.

    --Kyle Sluder
  • On May 21, 2013, at 10:11:45, Kyle Sluder <kyle...> wrote:

    > It quite clearly says it will not work with nested event loops. +runModalForWindow: is but one way to run a nested event loop. It's not the job of every method's documentation to teach you how the entire framework works.

    The whole point of documentation is to educate the user, not to make them waste time guessing and trying things.

    > You could try it, but +runModalForWindow might not invoke -sendEvent: to dispatch the events it pulls off the event queue within its nested runloop invocation.

    I meant using sendEvent for handling all the events for this popup window *instead of* using runModalForWindow, since that won't work for clicks out side the window to dismiss it.

    > Let's back up to your original question. Why do you need to run a runloop at all? NSPopupButtonCell will do that for you, and it will correctly handle the case where the user clicks outside the menu to dismiss.
    >
    > Look up -performClickWithFrame:inView:.

    I'm not sure how performClickWithFrame helps here. It's documentation says it displays the menu. That's not what I've been asking about. In our NSPopUpButtonCell subclass's trackMouse method, we create a window to display instead of showing the menu. This window has a 3-column NSBrowser in it for displaying a tree type structure. Clicks in column 1 will change what's shown in columns 2 and 3. Clicks in column 2 will change what's shown in column 3. Column 3 is the place where choices are ultimately made. Each column is individually scrollable. So, this control deviates from normal popup usage in that you can't mouseDown/drag/mouseUp to make a choice. A mouseDown/mouseUp shows it, multiple mouseDown/mouseUps can be made until a final mouseDown/mouseUp is made in the 3rd column, or the arrow and return keys can be used to navigate and make a final choice.

    --
    Steve Mills
    office: 952-818-3871
    home: 952-401-6255
    cell: 612-803-6157
  • On May 21, 2013, at 9:52 AM, Steve Mills <smills...> wrote:

    > On May 21, 2013, at 10:11:45, Kyle Sluder <kyle...> wrote:
    >
    >> You could try it, but +runModalForWindow might not invoke -sendEvent: to dispatch the events it pulls off the event queue within its nested runloop invocation.
    >
    > I meant using sendEvent for handling all the events for this popup window *instead of* using runModalForWindow, since that won't work for clicks out side the window to dismiss it.

    One concern is that you're not going to get a mouse event for clicks that happen in other apps' windows, but that might actually be what you want.

    An alternative approach would be to put up a transparent shielding window underneath your browser, and have any clicks on it dismiss the browser.

    >
    >> Let's back up to your original question. Why do you need to run a runloop at all? NSPopupButtonCell will do that for you, and it will correctly handle the case where the user clicks outside the menu to dismiss.
    >>
    >> Look up -performClickWithFrame:inView:.
    >
    > I'm not sure how performClickWithFrame helps here. It's documentation says it displays the menu. That's not what I've been asking about. In our NSPopUpButtonCell subclass's trackMouse method, we create a window to display instead of showing the menu. This window has a 3-column NSBrowser in it for displaying a tree type structure. Clicks in column 1 will change what's shown in columns 2 and 3. Clicks in column 2 will change what's shown in column 3. Column 3 is the place where choices are ultimately made. Each column is individually scrollable. So, this control deviates from normal popup usage in that you can't mouseDown/drag/mouseUp to make a choice. A mouseDown/mouseUp shows it, multiple mouseDown/mouseUps can be made until a final mouseDown/mouseUp is made in the 3rd column, or the arrow and return keys can be used to navigate and make a final choice.

    Oh, I misunderstood you to mean the popup button cell was located on a modally-running browser window, not that it _spawned_ the window.

    --Kyle Sluder
  • On May 21, 2013, at 12:06:15, Kyle Sluder <kyle...> wrote:

    > One concern is that you're not going to get a mouse event for clicks that happen in other apps' windows, but that might actually be what you want.

    Good point. No, this window should behave like it's really is a popup in the sense that *any* click outside it will dismiss it, and if those clicks are in other apps, the window will dismiss and the click will go through to the other app.

    > An alternative approach would be to put up a transparent shielding window underneath your browser, and have any clicks on it dismiss the browser.

    Hmm. But if the shielding window gets the click to dismiss the popup window, then the click won't go through to other apps, so it wouldn't have that "clicking outside the popup on another app dismisses the popup and switches to that app" behavior. Is it safe to create a new mouseDown event for the clicked point, but without the window filled in, and post it to the OS so it goes through to the other app? This is an area I've never dabbled in before. (Heck, this whole thing is on my undabbled list.:)

    --
    Steve Mills
    office: 952-818-3871
    home: 952-401-6255
    cell: 612-803-6157
  • Sent from my iPad

    On May 21, 2013, at 1:32 PM, Steve Mills <smills...> wrote:

    > On May 21, 2013, at 12:06:15, Kyle Sluder <kyle...> wrote:
    >
    >> One concern is that you're not going to get a mouse event for clicks that happen in other apps' windows, but that might actually be what you want.
    >
    > Good point. No, this window should behave like it's really is a popup in the sense that *any* click outside it will dismiss it, and if those clicks are in other apps, the window will dismiss and the click will go through to the other app.
    >
    >> An alternative approach would be to put up a transparent shielding window underneath your browser, and have any clicks on it dismiss the browser.
    >
    > Hmm. But if the shielding window gets the click to dismiss the popup window, then the click won't go through to other apps, so it wouldn't have that "clicking outside the popup on another app dismisses the popup and switches to that app" behavior. Is it safe to create a new mouseDown event for the clicked point, but without the window filled in, and post it to the OS so it goes through to the other app? This is an area I've never dabbled in before. (Heck, this whole thing is on my undabbled list.:)
    >

    Hi Steve.  Reading your issue sparked a flashback to the mid '90s where we had the same issue in Director products.  Not sure if this is able to be handled the same way but we would create a shielding rect to catch the click behind our popup style graphic and if it registered, issue a dismiss to the graphic and a "pass" that allowed the event to continue propagating.  One other option may be to catch the click at location, dismiss your GUI element and rebroadcast the event so that it could be recaught by whatever is in scope.

    Of course, you would need to have the proper context to issue the event to if you went that route.

    Just a thought/2.

    > --
    > Steve Mills
    > office: 952-818-3871
    > home: 952-401-6255
    > cell: 612-803-6157
previous month may 2013 next month
MTWTFSS
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31    
Go to today