10a8c90248264a8b26970b4473770bcc3df8515fJosh Gao"""Drag-and-drop support for Tkinter. 20a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 30a8c90248264a8b26970b4473770bcc3df8515fJosh GaoThis is very preliminary. I currently only support dnd *within* one 40a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoapplication, between different windows (or within the same window). 50a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 60a8c90248264a8b26970b4473770bcc3df8515fJosh GaoI an trying to make this as generic as possible -- not dependent on 70a8c90248264a8b26970b4473770bcc3df8515fJosh Gaothe use of a particular widget or icon type, etc. I also hope that 80a8c90248264a8b26970b4473770bcc3df8515fJosh Gaothis will work with Pmw. 90a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 100a8c90248264a8b26970b4473770bcc3df8515fJosh GaoTo enable an object to be dragged, you must create an event binding 110a8c90248264a8b26970b4473770bcc3df8515fJosh Gaofor it that starts the drag-and-drop process. Typically, you should 120a8c90248264a8b26970b4473770bcc3df8515fJosh Gaobind <ButtonPress> to a callback function that you write. The function 130a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoshould call Tkdnd.dnd_start(source, event), where 'source' is the 140a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoobject to be dragged, and 'event' is the event that invoked the call 150a8c90248264a8b26970b4473770bcc3df8515fJosh Gao(the argument to your callback function). Even though this is a class 160a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoinstantiation, the returned instance should not be stored -- it will 170a8c90248264a8b26970b4473770bcc3df8515fJosh Gaobe kept alive automatically for the duration of the drag-and-drop. 180a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 190a8c90248264a8b26970b4473770bcc3df8515fJosh GaoWhen a drag-and-drop is already in process for the Tk interpreter, the 200a8c90248264a8b26970b4473770bcc3df8515fJosh Gaocall is *ignored*; this normally averts starting multiple simultaneous 210a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodnd processes, e.g. because different button callbacks all 220a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodnd_start(). 230a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 240a8c90248264a8b26970b4473770bcc3df8515fJosh GaoThe object is *not* necessarily a widget -- it can be any 250a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoapplication-specific object that is meaningful to potential 260a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodrag-and-drop targets. 270a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 280a8c90248264a8b26970b4473770bcc3df8515fJosh GaoPotential drag-and-drop targets are discovered as follows. Whenever 290a8c90248264a8b26970b4473770bcc3df8515fJosh Gaothe mouse moves, and at the start and end of a drag-and-drop move, the 300a8c90248264a8b26970b4473770bcc3df8515fJosh GaoTk widget directly under the mouse is inspected. This is the target 310a8c90248264a8b26970b4473770bcc3df8515fJosh Gaowidget (not to be confused with the target object, yet to be 320a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodetermined). If there is no target widget, there is no dnd target 330a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoobject. If there is a target widget, and it has an attribute 340a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodnd_accept, this should be a function (or any callable object). The 350a8c90248264a8b26970b4473770bcc3df8515fJosh Gaofunction is called as dnd_accept(source, event), where 'source' is the 360a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoobject being dragged (the object passed to dnd_start() above), and 370a8c90248264a8b26970b4473770bcc3df8515fJosh Gao'event' is the most recent event object (generally a <Motion> event; 380a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoit can also be <ButtonPress> or <ButtonRelease>). If the dnd_accept() 390a8c90248264a8b26970b4473770bcc3df8515fJosh Gaofunction returns something other than None, this is the new dnd target 400a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoobject. If dnd_accept() returns None, or if the target widget has no 410a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodnd_accept attribute, the target widget's parent is considered as the 420a8c90248264a8b26970b4473770bcc3df8515fJosh Gaotarget widget, and the search for a target object is repeated from 430a8c90248264a8b26970b4473770bcc3df8515fJosh Gaothere. If necessary, the search is repeated all the way up to the 440a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoroot widget. If none of the target widgets can produce a target 450a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoobject, there is no target object (the target object is None). 460a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 470a8c90248264a8b26970b4473770bcc3df8515fJosh GaoThe target object thus produced, if any, is called the new target 480a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoobject. It is compared with the old target object (or None, if there 490a8c90248264a8b26970b4473770bcc3df8515fJosh Gaowas no old target widget). There are several cases ('source' is the 500a8c90248264a8b26970b4473770bcc3df8515fJosh Gaosource object, and 'event' is the most recent event object): 510a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 520a8c90248264a8b26970b4473770bcc3df8515fJosh Gao- Both the old and new target objects are None. Nothing happens. 530a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 540a8c90248264a8b26970b4473770bcc3df8515fJosh Gao- The old and new target objects are the same object. Its method 550a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodnd_motion(source, event) is called. 560a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 570a8c90248264a8b26970b4473770bcc3df8515fJosh Gao- The old target object was None, and the new target object is not 580a8c90248264a8b26970b4473770bcc3df8515fJosh GaoNone. The new target object's method dnd_enter(source, event) is 590a8c90248264a8b26970b4473770bcc3df8515fJosh Gaocalled. 600a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 610a8c90248264a8b26970b4473770bcc3df8515fJosh Gao- The new target object is None, and the old target object is not 620a8c90248264a8b26970b4473770bcc3df8515fJosh GaoNone. The old target object's method dnd_leave(source, event) is 630a8c90248264a8b26970b4473770bcc3df8515fJosh Gaocalled. 640a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 650a8c90248264a8b26970b4473770bcc3df8515fJosh Gao- The old and new target objects differ and neither is None. The old 660a8c90248264a8b26970b4473770bcc3df8515fJosh Gaotarget object's method dnd_leave(source, event), and then the new 670a8c90248264a8b26970b4473770bcc3df8515fJosh Gaotarget object's method dnd_enter(source, event) is called. 680a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 690a8c90248264a8b26970b4473770bcc3df8515fJosh GaoOnce this is done, the new target object replaces the old one, and the 700a8c90248264a8b26970b4473770bcc3df8515fJosh GaoTk mainloop proceeds. The return value of the methods mentioned above 710a8c90248264a8b26970b4473770bcc3df8515fJosh Gaois ignored; if they raise an exception, the normal exception handling 720a8c90248264a8b26970b4473770bcc3df8515fJosh Gaomechanisms take over. 730a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 740a8c90248264a8b26970b4473770bcc3df8515fJosh GaoThe drag-and-drop processes can end in two ways: a final target object 750a8c90248264a8b26970b4473770bcc3df8515fJosh Gaois selected, or no final target object is selected. When a final 760a8c90248264a8b26970b4473770bcc3df8515fJosh Gaotarget object is selected, it will always have been notified of the 770a8c90248264a8b26970b4473770bcc3df8515fJosh Gaopotential drop by a call to its dnd_enter() method, as described 780a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoabove, and possibly one or more calls to its dnd_motion() method; its 790a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodnd_leave() method has not been called since the last call to 800a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodnd_enter(). The target is notified of the drop by a call to its 810a8c90248264a8b26970b4473770bcc3df8515fJosh Gaomethod dnd_commit(source, event). 820a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 830a8c90248264a8b26970b4473770bcc3df8515fJosh GaoIf no final target object is selected, and there was an old target 840a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoobject, its dnd_leave(source, event) method is called to complete the 850a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodnd sequence. 860a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 870a8c90248264a8b26970b4473770bcc3df8515fJosh GaoFinally, the source object is notified that the drag-and-drop process 880a8c90248264a8b26970b4473770bcc3df8515fJosh Gaois over, by a call to source.dnd_end(target, event), specifying either 890a8c90248264a8b26970b4473770bcc3df8515fJosh Gaothe selected target object, or None if no target object was selected. 900a8c90248264a8b26970b4473770bcc3df8515fJosh GaoThe source object can use this to implement the commit action; this is 910a8c90248264a8b26970b4473770bcc3df8515fJosh Gaosometimes simpler than to do it in the target's dnd_commit(). The 920a8c90248264a8b26970b4473770bcc3df8515fJosh Gaotarget's dnd_commit() method could then simply be aliased to 930a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodnd_leave(). 940a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 950a8c90248264a8b26970b4473770bcc3df8515fJosh GaoAt any time during a dnd sequence, the application can cancel the 960a8c90248264a8b26970b4473770bcc3df8515fJosh Gaosequence by calling the cancel() method on the object returned by 970a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodnd_start(). This will call dnd_leave() if a target is currently 980a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoactive; it will never call dnd_commit(). 990a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1000a8c90248264a8b26970b4473770bcc3df8515fJosh Gao""" 1010a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1020a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1030a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoimport Tkinter 1040a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1050a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1060a8c90248264a8b26970b4473770bcc3df8515fJosh Gao# The factory function 1070a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1080a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodef dnd_start(source, event): 1090a8c90248264a8b26970b4473770bcc3df8515fJosh Gao h = DndHandler(source, event) 1100a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if h.root: 1110a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return h 1120a8c90248264a8b26970b4473770bcc3df8515fJosh Gao else: 1130a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return None 1140a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1150a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1160a8c90248264a8b26970b4473770bcc3df8515fJosh Gao# The class that does the work 1170a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1180a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoclass DndHandler: 1190a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1200a8c90248264a8b26970b4473770bcc3df8515fJosh Gao root = None 1210a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1220a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def __init__(self, source, event): 1230a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if event.num > 5: 1240a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return 1250a8c90248264a8b26970b4473770bcc3df8515fJosh Gao root = event.widget._root() 1260a8c90248264a8b26970b4473770bcc3df8515fJosh Gao try: 1270a8c90248264a8b26970b4473770bcc3df8515fJosh Gao root.__dnd 1280a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return # Don't start recursive dnd 1290a8c90248264a8b26970b4473770bcc3df8515fJosh Gao except AttributeError: 1300a8c90248264a8b26970b4473770bcc3df8515fJosh Gao root.__dnd = self 1310a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.root = root 1320a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.source = source 1330a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.target = None 1340a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.initial_button = button = event.num 1350a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.initial_widget = widget = event.widget 1360a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.release_pattern = "<B%d-ButtonRelease-%d>" % (button, button) 1370a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.save_cursor = widget['cursor'] or "" 1380a8c90248264a8b26970b4473770bcc3df8515fJosh Gao widget.bind(self.release_pattern, self.on_release) 1390a8c90248264a8b26970b4473770bcc3df8515fJosh Gao widget.bind("<Motion>", self.on_motion) 1400a8c90248264a8b26970b4473770bcc3df8515fJosh Gao widget['cursor'] = "hand2" 1410a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1420a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def __del__(self): 1430a8c90248264a8b26970b4473770bcc3df8515fJosh Gao root = self.root 1440a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.root = None 1450a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if root: 1460a8c90248264a8b26970b4473770bcc3df8515fJosh Gao try: 1470a8c90248264a8b26970b4473770bcc3df8515fJosh Gao del root.__dnd 1480a8c90248264a8b26970b4473770bcc3df8515fJosh Gao except AttributeError: 1490a8c90248264a8b26970b4473770bcc3df8515fJosh Gao pass 1500a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1510a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def on_motion(self, event): 1520a8c90248264a8b26970b4473770bcc3df8515fJosh Gao x, y = event.x_root, event.y_root 1530a8c90248264a8b26970b4473770bcc3df8515fJosh Gao target_widget = self.initial_widget.winfo_containing(x, y) 1540a8c90248264a8b26970b4473770bcc3df8515fJosh Gao source = self.source 1550a8c90248264a8b26970b4473770bcc3df8515fJosh Gao new_target = None 1560a8c90248264a8b26970b4473770bcc3df8515fJosh Gao while target_widget: 1570a8c90248264a8b26970b4473770bcc3df8515fJosh Gao try: 1580a8c90248264a8b26970b4473770bcc3df8515fJosh Gao attr = target_widget.dnd_accept 1590a8c90248264a8b26970b4473770bcc3df8515fJosh Gao except AttributeError: 1600a8c90248264a8b26970b4473770bcc3df8515fJosh Gao pass 1610a8c90248264a8b26970b4473770bcc3df8515fJosh Gao else: 1620a8c90248264a8b26970b4473770bcc3df8515fJosh Gao new_target = attr(source, event) 1630a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if new_target: 1640a8c90248264a8b26970b4473770bcc3df8515fJosh Gao break 1650a8c90248264a8b26970b4473770bcc3df8515fJosh Gao target_widget = target_widget.master 1660a8c90248264a8b26970b4473770bcc3df8515fJosh Gao old_target = self.target 1670a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if old_target is new_target: 1680a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if old_target: 1690a8c90248264a8b26970b4473770bcc3df8515fJosh Gao old_target.dnd_motion(source, event) 1700a8c90248264a8b26970b4473770bcc3df8515fJosh Gao else: 1710a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if old_target: 1720a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.target = None 1730a8c90248264a8b26970b4473770bcc3df8515fJosh Gao old_target.dnd_leave(source, event) 1740a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if new_target: 1750a8c90248264a8b26970b4473770bcc3df8515fJosh Gao new_target.dnd_enter(source, event) 1760a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.target = new_target 1770a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1780a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def on_release(self, event): 1790a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.finish(event, 1) 1800a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1810a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def cancel(self, event=None): 1820a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.finish(event, 0) 1830a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1840a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def finish(self, event, commit=0): 1850a8c90248264a8b26970b4473770bcc3df8515fJosh Gao target = self.target 1860a8c90248264a8b26970b4473770bcc3df8515fJosh Gao source = self.source 1870a8c90248264a8b26970b4473770bcc3df8515fJosh Gao widget = self.initial_widget 1880a8c90248264a8b26970b4473770bcc3df8515fJosh Gao root = self.root 1890a8c90248264a8b26970b4473770bcc3df8515fJosh Gao try: 1900a8c90248264a8b26970b4473770bcc3df8515fJosh Gao del root.__dnd 1910a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.initial_widget.unbind(self.release_pattern) 1920a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.initial_widget.unbind("<Motion>") 1930a8c90248264a8b26970b4473770bcc3df8515fJosh Gao widget['cursor'] = self.save_cursor 1940a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.target = self.source = self.initial_widget = self.root = None 1950a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if target: 1960a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if commit: 1970a8c90248264a8b26970b4473770bcc3df8515fJosh Gao target.dnd_commit(source, event) 1980a8c90248264a8b26970b4473770bcc3df8515fJosh Gao else: 1990a8c90248264a8b26970b4473770bcc3df8515fJosh Gao target.dnd_leave(source, event) 2000a8c90248264a8b26970b4473770bcc3df8515fJosh Gao finally: 2010a8c90248264a8b26970b4473770bcc3df8515fJosh Gao source.dnd_end(target, event) 2020a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2030a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2040a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2050a8c90248264a8b26970b4473770bcc3df8515fJosh Gao# ---------------------------------------------------------------------- 2060a8c90248264a8b26970b4473770bcc3df8515fJosh Gao# The rest is here for testing and demonstration purposes only! 2070a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2080a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoclass Icon: 2090a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2100a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def __init__(self, name): 2110a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.name = name 2120a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.canvas = self.label = self.id = None 2130a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2140a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def attach(self, canvas, x=10, y=10): 2150a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if canvas is self.canvas: 2160a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.canvas.coords(self.id, x, y) 2170a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return 2180a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if self.canvas: 2190a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.detach() 2200a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if not canvas: 2210a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return 2220a8c90248264a8b26970b4473770bcc3df8515fJosh Gao label = Tkinter.Label(canvas, text=self.name, 2230a8c90248264a8b26970b4473770bcc3df8515fJosh Gao borderwidth=2, relief="raised") 2240a8c90248264a8b26970b4473770bcc3df8515fJosh Gao id = canvas.create_window(x, y, window=label, anchor="nw") 2250a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.canvas = canvas 2260a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.label = label 2270a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.id = id 2280a8c90248264a8b26970b4473770bcc3df8515fJosh Gao label.bind("<ButtonPress>", self.press) 2290a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2300a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def detach(self): 2310a8c90248264a8b26970b4473770bcc3df8515fJosh Gao canvas = self.canvas 2320a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if not canvas: 2330a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return 2340a8c90248264a8b26970b4473770bcc3df8515fJosh Gao id = self.id 2350a8c90248264a8b26970b4473770bcc3df8515fJosh Gao label = self.label 2360a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.canvas = self.label = self.id = None 2370a8c90248264a8b26970b4473770bcc3df8515fJosh Gao canvas.delete(id) 2380a8c90248264a8b26970b4473770bcc3df8515fJosh Gao label.destroy() 2390a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2400a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def press(self, event): 2410a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if dnd_start(self, event): 2420a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # where the pointer is relative to the label widget: 2430a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.x_off = event.x 2440a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.y_off = event.y 2450a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # where the widget is relative to the canvas: 2460a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.x_orig, self.y_orig = self.canvas.coords(self.id) 2470a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2480a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def move(self, event): 2490a8c90248264a8b26970b4473770bcc3df8515fJosh Gao x, y = self.where(self.canvas, event) 2500a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.canvas.coords(self.id, x, y) 2510a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2520a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def putback(self): 2530a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.canvas.coords(self.id, self.x_orig, self.y_orig) 2540a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2550a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def where(self, canvas, event): 2560a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # where the corner of the canvas is relative to the screen: 2570a8c90248264a8b26970b4473770bcc3df8515fJosh Gao x_org = canvas.winfo_rootx() 2580a8c90248264a8b26970b4473770bcc3df8515fJosh Gao y_org = canvas.winfo_rooty() 2590a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # where the pointer is relative to the canvas widget: 2600a8c90248264a8b26970b4473770bcc3df8515fJosh Gao x = event.x_root - x_org 2610a8c90248264a8b26970b4473770bcc3df8515fJosh Gao y = event.y_root - y_org 2620a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # compensate for initial pointer offset 2630a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return x - self.x_off, y - self.y_off 2640a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2650a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def dnd_end(self, target, event): 2660a8c90248264a8b26970b4473770bcc3df8515fJosh Gao pass 2670a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2680a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoclass Tester: 2690a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2700a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def __init__(self, root): 2710a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.top = Tkinter.Toplevel(root) 2720a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.canvas = Tkinter.Canvas(self.top, width=100, height=100) 2730a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.canvas.pack(fill="both", expand=1) 2740a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.canvas.dnd_accept = self.dnd_accept 2750a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2760a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def dnd_accept(self, source, event): 2770a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return self 2780a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2790a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def dnd_enter(self, source, event): 2800a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.canvas.focus_set() # Show highlight border 2810a8c90248264a8b26970b4473770bcc3df8515fJosh Gao x, y = source.where(self.canvas, event) 2820a8c90248264a8b26970b4473770bcc3df8515fJosh Gao x1, y1, x2, y2 = source.canvas.bbox(source.id) 2830a8c90248264a8b26970b4473770bcc3df8515fJosh Gao dx, dy = x2-x1, y2-y1 2840a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.dndid = self.canvas.create_rectangle(x, y, x+dx, y+dy) 2850a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.dnd_motion(source, event) 2860a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2870a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def dnd_motion(self, source, event): 2880a8c90248264a8b26970b4473770bcc3df8515fJosh Gao x, y = source.where(self.canvas, event) 2890a8c90248264a8b26970b4473770bcc3df8515fJosh Gao x1, y1, x2, y2 = self.canvas.bbox(self.dndid) 2900a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.canvas.move(self.dndid, x-x1, y-y1) 2910a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2920a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def dnd_leave(self, source, event): 2930a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.top.focus_set() # Hide highlight border 2940a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.canvas.delete(self.dndid) 2950a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.dndid = None 2960a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2970a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def dnd_commit(self, source, event): 2980a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.dnd_leave(source, event) 2990a8c90248264a8b26970b4473770bcc3df8515fJosh Gao x, y = source.where(self.canvas, event) 3000a8c90248264a8b26970b4473770bcc3df8515fJosh Gao source.attach(self.canvas, x, y) 3010a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 3020a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodef test(): 3030a8c90248264a8b26970b4473770bcc3df8515fJosh Gao root = Tkinter.Tk() 3040a8c90248264a8b26970b4473770bcc3df8515fJosh Gao root.geometry("+1+1") 3050a8c90248264a8b26970b4473770bcc3df8515fJosh Gao Tkinter.Button(command=root.quit, text="Quit").pack() 3060a8c90248264a8b26970b4473770bcc3df8515fJosh Gao t1 = Tester(root) 3070a8c90248264a8b26970b4473770bcc3df8515fJosh Gao t1.top.geometry("+1+60") 3080a8c90248264a8b26970b4473770bcc3df8515fJosh Gao t2 = Tester(root) 3090a8c90248264a8b26970b4473770bcc3df8515fJosh Gao t2.top.geometry("+120+60") 3100a8c90248264a8b26970b4473770bcc3df8515fJosh Gao t3 = Tester(root) 3110a8c90248264a8b26970b4473770bcc3df8515fJosh Gao t3.top.geometry("+240+60") 3120a8c90248264a8b26970b4473770bcc3df8515fJosh Gao i1 = Icon("ICON1") 3130a8c90248264a8b26970b4473770bcc3df8515fJosh Gao i2 = Icon("ICON2") 3140a8c90248264a8b26970b4473770bcc3df8515fJosh Gao i3 = Icon("ICON3") 3150a8c90248264a8b26970b4473770bcc3df8515fJosh Gao i1.attach(t1.canvas) 3160a8c90248264a8b26970b4473770bcc3df8515fJosh Gao i2.attach(t2.canvas) 3170a8c90248264a8b26970b4473770bcc3df8515fJosh Gao i3.attach(t3.canvas) 3180a8c90248264a8b26970b4473770bcc3df8515fJosh Gao root.mainloop() 3190a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 3200a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoif __name__ == '__main__': 3210a8c90248264a8b26970b4473770bcc3df8515fJosh Gao test() 322