diff --git a/enable/tests/test_drag_tool.py b/enable/tests/test_drag_tool.py index fe9e1934c..4ae971740 100644 --- a/enable/tests/test_drag_tool.py +++ b/enable/tests/test_drag_tool.py @@ -8,6 +8,7 @@ # # Thanks for using Enthought open source! import unittest +import warnings from traits.api import Int @@ -19,10 +20,16 @@ class DummyTool(DragTool): canceled = Int + ended = Int + def drag_cancel(self, event): self.canceled += 1 return True + def drag_end(self, event): + self.ended += 1 + return True + class DragToolTestCase(EnableTestAssistant, unittest.TestCase): def setUp(self): @@ -68,7 +75,8 @@ def test_mouse_leave_drag_state(self): tool = self.tool tool.end_drag_on_leave = True tool._drag_state = "dragging" # force dragging state - event = self.mouse_leave(interactor=tool, x=0, y=0) + with self.assertWarns(DeprecationWarning): + event = self.mouse_leave(interactor=tool, x=0, y=0) self.assertEqual(tool.canceled, 1) self.assertEqual(tool._drag_state, "nondrag") self.assertTrue(event.handled) @@ -82,3 +90,41 @@ def test_mouse_leave_drag_state(self): self.assertEqual(tool.canceled, 1) self.assertEqual(tool._drag_state, "dragging") self.assertFalse(event.handled) + + def test_on_drag_leave(self): + # When on_drag_leave is 'cancel' then the drag_cancel is called + # and the _drag_state will be 'nondrag' + tool = self.tool + tool.on_drag_leave = 'cancel' + tool._drag_state = "dragging" # force dragging state + event = self.mouse_leave(interactor=tool, x=0, y=0) + self.assertEqual(tool.canceled, 1) + self.assertEqual(tool._drag_state, "nondrag") + self.assertTrue(event.handled) + + # When on_drag_leave is 'end' then the drag_end is called + # and the _drag_state will be 'nondrag' + tool.on_drag_leave = 'end' + tool._drag_state = "dragging" # force dragging state + event = self.mouse_leave(interactor=tool, x=0, y=0) + self.assertEqual(tool.ended, 1) + self.assertEqual(tool._drag_state, "nondrag") + self.assertTrue(event.handled) + + def test_on_drag_leave_no_op(self): + """ If end_drag_on_leave is set, on_drag_leave trait is ignored. """ + + tool = self.tool + tool.end_drag_on_leave = True + tool.on_drag_leave = 'end' + tool._drag_state = "dragging" # force dragging state + with self.assertWarns(DeprecationWarning): + event = self.mouse_leave(interactor=tool, x=0, y=0) + + # end_drag_on_leave should be handled like normal + self.assertEqual(tool.canceled, 1) + self.assertEqual(tool._drag_state, "nondrag") + self.assertTrue(event.handled) + + # even though on_drag_leave = 'end' we do nothing + self.assertEqual(tool.ended, 0) diff --git a/enable/tools/drag_tool.py b/enable/tools/drag_tool.py index 3477cba19..2a7777786 100644 --- a/enable/tools/drag_tool.py +++ b/enable/tools/drag_tool.py @@ -9,6 +9,8 @@ # Thanks for using Enthought open source! """ Defines the base DragTool class. """ +import warnings + # Enthought library imports from enable.base_tool import BaseTool, KeySpec from traits.api import Bool, Enum, List, Property, Str, Tuple, cached_property @@ -24,11 +26,19 @@ class DragTool(BaseTool): # The mouse button used for this drag operation. drag_button = Enum("left", "right") - # End the drag operation if the mouse leaves the associated component? + # Deprecated; on_drag_leave is the new, more flexible / intuitive means for + # providing this functionality + # Cancel the drag operation if the mouse leaves the associated component? # NOTE: This behavior depends on "mouse_leave" events, which in general # are not fired when `capture_mouse` is True (default). end_drag_on_leave = Bool(False) + # Do nothing, cancel or end the drag operation if the mouse leaves the + # associated component? + # NOTE: This behavior depends on "mouse_leave" events, which in general + # are not fired when `capture_mouse` is True (default). + on_drag_leave = Enum(None, 'cancel', 'end') + # These keys, if pressed during drag, cause the drag operation to reset. cancel_keys = List(Str, ["Esc"]) @@ -43,7 +53,7 @@ class DragTool(BaseTool): # Whether or not to capture the mouse during the drag operation. In effect, # this routes mouse events back to this tool for dispatching, rather than # allowing the event to be handled by the window. This may have effects - # surrounding "mouse_leave" events: see note on `end_drag_on_leave` flag. + # surrounding "mouse_leave" events: see note on `on_drag_leave` flag. capture_mouse = Bool(True) # ------------------------------------------------------------------------ @@ -98,13 +108,16 @@ def drag_cancel(self, event): """ Called when the drag is cancelled. A drag is usually cancelled by receiving a mouse_leave event when - end_drag_on_leave is True, or by the user pressing any of the - **cancel_keys**. + end_drag_on_leave is True, or on_drag_leave is 'cancel', or by the user + pressing any of the **cancel_keys**. """ pass def drag_end(self, event): """ Called when a mouse event causes the drag operation to end. + + A drag is ended when a user releases the mouse, or by receiving a + mouse_leave event when on_drag_leave is 'end'. """ pass @@ -139,7 +152,15 @@ def _cancel_drag(self, event): self._drag_state = "nondrag" outcome = self.drag_cancel(event) self._mouse_down_received = False - if event.window.mouse_owner == self: + if event.window.mouse_owner is self: + event.window.set_mouse_owner(None) + return outcome + + def _end_drag(self, event): + self._drag_state = "nondrag" + outcome = self.drag_end(event) + self._mouse_down_received = False + if event.window.mouse_owner is self: event.window.set_mouse_owner(None) return outcome @@ -197,8 +218,23 @@ def _drag_button_up(self, event): return False def _drag_mouse_leave(self, event): - if self.end_drag_on_leave and self._drag_state == "dragging": + if self.end_drag_on_leave: + # raise deprecation warning + msg = ("end_drag_on_leave is now deprecated as its name was " + "misleading. It triggers a drag_cancel not drag_end on " + "leave. Use new on_drag_end Enum trait instead.") + warnings.warn( + msg, + category=DeprecationWarning, + ) + if self._drag_state == "dragging": + return self._cancel_drag(event) + return False + + if self.on_drag_leave == "cancel" and self._drag_state == "dragging": return self._cancel_drag(event) + elif self.on_drag_leave == "end" and self._drag_state == "dragging": + return self._end_drag(event) return False def _drag_mouse_enter(self, event):