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