PyXR

c:\python24\lib\lib-tk \ Tkdnd.py



0001 """Drag-and-drop support for Tkinter.
0002 
0003 This is very preliminary.  I currently only support dnd *within* one
0004 application, between different windows (or within the same window).
0005 
0006 I an trying to make this as generic as possible -- not dependent on
0007 the use of a particular widget or icon type, etc.  I also hope that
0008 this will work with Pmw.
0009 
0010 To enable an object to be dragged, you must create an event binding
0011 for it that starts the drag-and-drop process. Typically, you should
0012 bind <ButtonPress> to a callback function that you write. The function
0013 should call Tkdnd.dnd_start(source, event), where 'source' is the
0014 object to be dragged, and 'event' is the event that invoked the call
0015 (the argument to your callback function).  Even though this is a class
0016 instantiation, the returned instance should not be stored -- it will
0017 be kept alive automatically for the duration of the drag-and-drop.
0018 
0019 When a drag-and-drop is already in process for the Tk interpreter, the
0020 call is *ignored*; this normally averts starting multiple simultaneous
0021 dnd processes, e.g. because different button callbacks all
0022 dnd_start().
0023 
0024 The object is *not* necessarily a widget -- it can be any
0025 application-specific object that is meaningful to potential
0026 drag-and-drop targets.
0027 
0028 Potential drag-and-drop targets are discovered as follows.  Whenever
0029 the mouse moves, and at the start and end of a drag-and-drop move, the
0030 Tk widget directly under the mouse is inspected.  This is the target
0031 widget (not to be confused with the target object, yet to be
0032 determined).  If there is no target widget, there is no dnd target
0033 object.  If there is a target widget, and it has an attribute
0034 dnd_accept, this should be a function (or any callable object).  The
0035 function is called as dnd_accept(source, event), where 'source' is the
0036 object being dragged (the object passed to dnd_start() above), and
0037 'event' is the most recent event object (generally a <Motion> event;
0038 it can also be <ButtonPress> or <ButtonRelease>).  If the dnd_accept()
0039 function returns something other than None, this is the new dnd target
0040 object.  If dnd_accept() returns None, or if the target widget has no
0041 dnd_accept attribute, the target widget's parent is considered as the
0042 target widget, and the search for a target object is repeated from
0043 there.  If necessary, the search is repeated all the way up to the
0044 root widget.  If none of the target widgets can produce a target
0045 object, there is no target object (the target object is None).
0046 
0047 The target object thus produced, if any, is called the new target
0048 object.  It is compared with the old target object (or None, if there
0049 was no old target widget).  There are several cases ('source' is the
0050 source object, and 'event' is the most recent event object):
0051 
0052 - Both the old and new target objects are None.  Nothing happens.
0053 
0054 - The old and new target objects are the same object.  Its method
0055 dnd_motion(source, event) is called.
0056 
0057 - The old target object was None, and the new target object is not
0058 None.  The new target object's method dnd_enter(source, event) is
0059 called.
0060 
0061 - The new target object is None, and the old target object is not
0062 None.  The old target object's method dnd_leave(source, event) is
0063 called.
0064 
0065 - The old and new target objects differ and neither is None.  The old
0066 target object's method dnd_leave(source, event), and then the new
0067 target object's method dnd_enter(source, event) is called.
0068 
0069 Once this is done, the new target object replaces the old one, and the
0070 Tk mainloop proceeds.  The return value of the methods mentioned above
0071 is ignored; if they raise an exception, the normal exception handling
0072 mechanisms take over.
0073 
0074 The drag-and-drop processes can end in two ways: a final target object
0075 is selected, or no final target object is selected.  When a final
0076 target object is selected, it will always have been notified of the
0077 potential drop by a call to its dnd_enter() method, as described
0078 above, and possibly one or more calls to its dnd_motion() method; its
0079 dnd_leave() method has not been called since the last call to
0080 dnd_enter().  The target is notified of the drop by a call to its
0081 method dnd_commit(source, event).
0082 
0083 If no final target object is selected, and there was an old target
0084 object, its dnd_leave(source, event) method is called to complete the
0085 dnd sequence.
0086 
0087 Finally, the source object is notified that the drag-and-drop process
0088 is over, by a call to source.dnd_end(target, event), specifying either
0089 the selected target object, or None if no target object was selected.
0090 The source object can use this to implement the commit action; this is
0091 sometimes simpler than to do it in the target's dnd_commit().  The
0092 target's dnd_commit() method could then simply be aliased to
0093 dnd_leave().
0094 
0095 At any time during a dnd sequence, the application can cancel the
0096 sequence by calling the cancel() method on the object returned by
0097 dnd_start().  This will call dnd_leave() if a target is currently
0098 active; it will never call dnd_commit().
0099 
0100 """
0101 
0102 
0103 import Tkinter
0104 
0105 
0106 # The factory function
0107 
0108 def dnd_start(source, event):
0109     h = DndHandler(source, event)
0110     if h.root:
0111         return h
0112     else:
0113         return None
0114 
0115 
0116 # The class that does the work
0117 
0118 class DndHandler:
0119 
0120     root = None
0121 
0122     def __init__(self, source, event):
0123         if event.num > 5:
0124             return
0125         root = event.widget._root()
0126         try:
0127             root.__dnd
0128             return # Don't start recursive dnd
0129         except AttributeError:
0130             root.__dnd = self
0131             self.root = root
0132         self.source = source
0133         self.target = None
0134         self.initial_button = button = event.num
0135         self.initial_widget = widget = event.widget
0136         self.release_pattern = "<B%d-ButtonRelease-%d>" % (button, button)
0137         self.save_cursor = widget['cursor'] or ""
0138         widget.bind(self.release_pattern, self.on_release)
0139         widget.bind("<Motion>", self.on_motion)
0140         widget['cursor'] = "hand2"
0141 
0142     def __del__(self):
0143         root = self.root
0144         self.root = None
0145         if root:
0146             try:
0147                 del root.__dnd
0148             except AttributeError:
0149                 pass
0150 
0151     def on_motion(self, event):
0152         x, y = event.x_root, event.y_root
0153         target_widget = self.initial_widget.winfo_containing(x, y)
0154         source = self.source
0155         new_target = None
0156         while target_widget:
0157             try:
0158                 attr = target_widget.dnd_accept
0159             except AttributeError:
0160                 pass
0161             else:
0162                 new_target = attr(source, event)
0163                 if new_target:
0164                     break
0165             target_widget = target_widget.master
0166         old_target = self.target
0167         if old_target is new_target:
0168             if old_target:
0169                 old_target.dnd_motion(source, event)
0170         else:
0171             if old_target:
0172                 self.target = None
0173                 old_target.dnd_leave(source, event)
0174             if new_target:
0175                 new_target.dnd_enter(source, event)
0176                 self.target = new_target
0177 
0178     def on_release(self, event):
0179         self.finish(event, 1)
0180 
0181     def cancel(self, event=None):
0182         self.finish(event, 0)
0183 
0184     def finish(self, event, commit=0):
0185         target = self.target
0186         source = self.source
0187         widget = self.initial_widget
0188         root = self.root
0189         try:
0190             del root.__dnd
0191             self.initial_widget.unbind(self.release_pattern)
0192             self.initial_widget.unbind("<Motion>")
0193             widget['cursor'] = self.save_cursor
0194             self.target = self.source = self.initial_widget = self.root = None
0195             if target:
0196                 if commit:
0197                     target.dnd_commit(source, event)
0198                 else:
0199                     target.dnd_leave(source, event)
0200         finally:
0201             source.dnd_end(target, event)
0202 
0203 
0204 
0205 # ----------------------------------------------------------------------
0206 # The rest is here for testing and demonstration purposes only!
0207 
0208 class Icon:
0209 
0210     def __init__(self, name):
0211         self.name = name
0212         self.canvas = self.label = self.id = None
0213 
0214     def attach(self, canvas, x=10, y=10):
0215         if canvas is self.canvas:
0216             self.canvas.coords(self.id, x, y)
0217             return
0218         if self.canvas:
0219             self.detach()
0220         if not canvas:
0221             return
0222         label = Tkinter.Label(canvas, text=self.name,
0223                               borderwidth=2, relief="raised")
0224         id = canvas.create_window(x, y, window=label, anchor="nw")
0225         self.canvas = canvas
0226         self.label = label
0227         self.id = id
0228         label.bind("<ButtonPress>", self.press)
0229 
0230     def detach(self):
0231         canvas = self.canvas
0232         if not canvas:
0233             return
0234         id = self.id
0235         label = self.label
0236         self.canvas = self.label = self.id = None
0237         canvas.delete(id)
0238         label.destroy()
0239 
0240     def press(self, event):
0241         if dnd_start(self, event):
0242             # where the pointer is relative to the label widget:
0243             self.x_off = event.x
0244             self.y_off = event.y
0245             # where the widget is relative to the canvas:
0246             self.x_orig, self.y_orig = self.canvas.coords(self.id)
0247 
0248     def move(self, event):
0249         x, y = self.where(self.canvas, event)
0250         self.canvas.coords(self.id, x, y)
0251 
0252     def putback(self):
0253         self.canvas.coords(self.id, self.x_orig, self.y_orig)
0254 
0255     def where(self, canvas, event):
0256         # where the corner of the canvas is relative to the screen:
0257         x_org = canvas.winfo_rootx()
0258         y_org = canvas.winfo_rooty()
0259         # where the pointer is relative to the canvas widget:
0260         x = event.x_root - x_org
0261         y = event.y_root - y_org
0262         # compensate for initial pointer offset
0263         return x - self.x_off, y - self.y_off
0264 
0265     def dnd_end(self, target, event):
0266         pass
0267 
0268 class Tester:
0269 
0270     def __init__(self, root):
0271         self.top = Tkinter.Toplevel(root)
0272         self.canvas = Tkinter.Canvas(self.top, width=100, height=100)
0273         self.canvas.pack(fill="both", expand=1)
0274         self.canvas.dnd_accept = self.dnd_accept
0275 
0276     def dnd_accept(self, source, event):
0277         return self
0278 
0279     def dnd_enter(self, source, event):
0280         self.canvas.focus_set() # Show highlight border
0281         x, y = source.where(self.canvas, event)
0282         x1, y1, x2, y2 = source.canvas.bbox(source.id)
0283         dx, dy = x2-x1, y2-y1
0284         self.dndid = self.canvas.create_rectangle(x, y, x+dx, y+dy)
0285         self.dnd_motion(source, event)
0286 
0287     def dnd_motion(self, source, event):
0288         x, y = source.where(self.canvas, event)
0289         x1, y1, x2, y2 = self.canvas.bbox(self.dndid)
0290         self.canvas.move(self.dndid, x-x1, y-y1)
0291 
0292     def dnd_leave(self, source, event):
0293         self.top.focus_set() # Hide highlight border
0294         self.canvas.delete(self.dndid)
0295         self.dndid = None
0296 
0297     def dnd_commit(self, source, event):
0298         self.dnd_leave(source, event)
0299         x, y = source.where(self.canvas, event)
0300         source.attach(self.canvas, x, y)
0301 
0302 def test():
0303     root = Tkinter.Tk()
0304     root.geometry("+1+1")
0305     Tkinter.Button(command=root.quit, text="Quit").pack()
0306     t1 = Tester(root)
0307     t1.top.geometry("+1+60")
0308     t2 = Tester(root)
0309     t2.top.geometry("+120+60")
0310     t3 = Tester(root)
0311     t3.top.geometry("+240+60")
0312     i1 = Icon("ICON1")
0313     i2 = Icon("ICON2")
0314     i3 = Icon("ICON3")
0315     i1.attach(t1.canvas)
0316     i2.attach(t2.canvas)
0317     i3.attach(t3.canvas)
0318     root.mainloop()
0319 
0320 if __name__ == '__main__':
0321     test()
0322 

Generated by PyXR 0.9.4
SourceForge.net Logo