Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions Lib/test/test_tkinter/test_dnd.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,12 @@ def dnd_end(self, target, event):


class FakeEvent:
def __init__(self, widget, num=1):
def __init__(self, widget, num=1, x_root=0, y_root=0):
self.num = num
self.widget = widget
self.x = self.y = self.x_root = self.y_root = 0
self.x = self.y = 0
self.x_root = x_root
self.y_root = y_root


class DndTest(AbstractTkTest, unittest.TestCase):
Expand Down Expand Up @@ -111,10 +113,16 @@ def test_restart_after_finish(self):
handler.cancel()

def test_drag_cursor(self):
# The drag cursor is not shown on the initial press, only once the
# pointer moves past the threshold, so a plain click does not flash
# it (gh-43699). The original cursor is restored afterwards.
self.canvas['cursor'] = 'watch'
handler = dnd.dnd_start(self.source, FakeEvent(self.canvas))
# The drag cursor is shown while dragging, the original restored after.
self.assertEqual(handler.save_cursor, 'watch')
self.assertEqual(str(self.canvas['cursor']), 'watch')
handler.on_motion(FakeEvent(self.canvas, x_root=2)) # below threshold
self.assertEqual(str(self.canvas['cursor']), 'watch')
handler.on_motion(FakeEvent(self.canvas, x_root=20)) # past threshold
self.assertEqual(str(self.canvas['cursor']), 'hand2')
handler.cancel()
self.assertEqual(str(self.canvas['cursor']), 'watch')
Expand Down
18 changes: 17 additions & 1 deletion Lib/tkinter/dnd.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ class DndHandler:

root = None

# The drag cursor is shown only once the pointer has moved this many
# pixels from the initial press, so that a plain click does not flash it.
threshold = 3

def __init__(self, source, event):
if event.num > 5:
return
Expand All @@ -134,11 +138,12 @@ def __init__(self, source, event):
self.target = None
self.initial_button = button = event.num
self.initial_widget = widget = event.widget
self.dragging = False
self.x_origin, self.y_origin = event.x_root, event.y_root
self.release_pattern = "<B%d-ButtonRelease-%d>" % (button, button)
self.save_cursor = widget['cursor'] or ""
widget.bind(self.release_pattern, self.on_release)
widget.bind("<Motion>", self.on_motion)
widget['cursor'] = "hand2"

def __del__(self):
root = self.root
Expand Down Expand Up @@ -175,6 +180,17 @@ def on_motion(self, event):
if new_target is not None:
new_target.dnd_enter(source, event)
self.target = new_target
self.update_cursor(x, y)

def update_cursor(self, x, y):
# Show the drag cursor only once the pointer has actually started
# moving past the threshold, so that a plain click does not flash it.
if not self.dragging:
if (abs(x - self.x_origin) <= self.threshold and
abs(y - self.y_origin) <= self.threshold):
return
self.dragging = True
self.initial_widget['cursor'] = "hand2"

def on_release(self, event):
self.finish(event, 1)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
The drag cursor in :mod:`tkinter.dnd` is now changed only once the pointer
starts moving rather than on the initial button press, so that a plain
click no longer flashes it.
Loading