···
OK, certain things have come to light. I found a problem on OSX (Python 2.7.13 and wxPython 3.0.3.dev2486), even when handling EVT_MOUSE_CAPTURE_LOST.
In a matplotlib Panel, I have RIGHT_DOWN bring up a Popup window which might then cause a separate Frame to be created and raised. When this happens on OSX )at least), the mouse capture appears to not be released by the Panel, so that a subsequent *_DOWN event in the Panel will try to capture the mouse, raising a C++ assertion error and effectively killing interactivity with the application.
To be clear, in MPL FigureCanvasWx subclasses wx.Panel, and includes (among other) event bindings:
self.Bind(wx.EVT_RIGHT_DOWN, self._onRightButtonDown)
self.Bind(wx.EVT_RIGHT_DCLICK, self._onRightButtonDClick)
self.Bind(wx.EVT_RIGHT_UP, self._onRightButtonUp)
self.Bind(wx.EVT_LEFT_DOWN, self._onLeftButtonDown)
self.Bind(wx.EVT_LEFT_DCLICK, self._onLeftButtonDClick)
self.Bind(wx.EVT_LEFT_UP, self._onLeftButtonUp)
self.Bind(wx.EVT_MIDDLE_DOWN, self._onMiddleButtonDown)
self.Bind(wx.EVT_MIDDLE_DCLICK, self._onMiddleButtonDClick)
self.Bind(wx.EVT_MIDDLE_UP, self._onMiddleButtonUp)
In the original version of the PR, I added
self.Bind(wx.EVT_MOUSE_CAPTURE_CHANGED, self._onCaptureLost)
self.Bind(wx.EVT_MOUSE_CAPTURE_LOST, self._onCaptureLost)
to release the mouse, as suggested in the wxPython docs.
For simplicity, I have consolidated the calls to CaptureMouse() and RelaseMouse() to a single method:
def _set_capture(self, capture=True): # Version 1: capture or release
"""control wx mouse capture """
if capture:
self.CaptureMouse()
elif self.HasCapture():
self.ReleaseMouse()
and then have self._set_capture(True)
in the three _onButtonDown() methods and the three _onButtonDClick() methods, and have self._set_capture(False)
in the three _on*ButtonUp() methods, and in the_onCaptureLost() method. All of these event handlers also include ‘event.Skip()’.
Again, with the actions described above, this version of _set_capture fails on Mac OSX, with
wx._core.wxAssertionError: C++ assertion “!wxMouseCapture::IsInCaptureStack(this)” failed at /Users/robind/projects/buildbots/macosx-vm4/dist-osx-py27/Phoenix/ext/wxWidgets/src/common/wincmn.cpp(3271) in CaptureMouse(): Recapturing the mouse in the same window?
To recap the situation:
On Windows: Capturing the mouse is definitely needed in order for "out of window mouse Motion and Up events to be restored on the combination of (“MouseDown”, “Mouse move out of Window”, “Mouse move back into Window”, “MouseUp”). The C++ assertions seem to never happen, and handling MOUSE_CAPTURE_LOST is not necessary.
On OSX (and I believe Linux): Capturing the mouse is not necessary for the “move mouse out of window and back” events to work as desired, and handling MOUSE_CAPTURE_LOST is not sufficient to prevent C++ assertions.
For an improved solution, this modified version of _set_capture() works on OSX:
def _set_capture(self, capture=True): # Version 2: always release before capture
"""control wx mouse capture """
if self.HasCapture():
self.ReleaseMouse()
if capture:
self.CaptureMouse()
That is, ALWAYS release a captured mouse, and re-acquire if capture is requested. Given the above information and previous experience, this will also always wotk on Windows. Being away from a Windows machine, it is proven, but not tested ;). But, to be clear, this also works:
def _set_capture(self, capture=True): # Version 3: don't bother with Capture/Release on posix
"""control wx mouse capture """
if [os.name](http://os.name) == 'posix':
return
if capture:
self.CaptureMouse()
elif self.HasCapture():
self.ReleaseMouse()
In fact, handling MOUSE_CAPTURE_LOST events appears to be unimportant.
The current version of the PR for MPL includes handling MOUSE_CAPTURE_LOST events (I believe they are unimportant, but having them in the code ought to prevent someone from making the mistake I made of thinking that adding them will solve some problem). It uses the 2nd version of _set_capture() above, always releasing the mouse before trying to capture it.
Except on OSX on Linux, where CaptureMouse() is not necessary for this behavior.
Actually, it wasn’t good enough. The panel was only releasing if captured, but it is also important to ensure the mouse is released before trying to re-capture it, at least on OSX.
More testing would be helpful.
Cheers,
yup – that is the point of CaptureMouse()
That’s exactly what the original PR did. After testing on Windows, I did notice that some mouse events get lost without CaptureMouse(). Specifically, on Windows, doing LeftDown on the wx.Panel with the MPL plot canvas then dragging the mouse out of that Window means that subsequent Motion or LeftUp events are not sent to the MPL plot Panel.
Why not just remove the CaptureMouse call from MPL?
Yup – I think that’s the way to handle it. Oddly, you can get problems calling ReleaseMouse() if it’s not currently captured – I’d think wx would have a guard against that, but I guess not. This approach has worked for me in other apps – so I think we’re good.
So, I changed the PR for the MPL backed to explicitly handle the MOUSE_CAPTURE events, with an event handler that just does
if self.HasCapture(): self.ReleaseMouse()
This appears to work better (that is, not loose Mouse events when they stray out of the Window) on Windows as well as on Linux and Mac OSX.
With this change, I was not able to generate C++ assertions on any platform, though I am not certain that these cannot happen.
–Matt