Updated for 2.1b2 distribution.
[python/dscho.git] / Demo / tkinter / guido / canvasevents.py
blob60f409659bf8e72dea3be7109748e5eb0fa8f870
1 #! /usr/bin/env python
3 from Tkinter import *
4 from Canvas import Oval, Group, CanvasText
7 # Fix a bug in Canvas.Group as distributed in Python 1.4. The
8 # distributed bind() method is broken. This is what should be used:
10 class Group(Group):
11 def bind(self, sequence=None, command=None):
12 return self.canvas.tag_bind(self.id, sequence, command)
14 class Object:
16 """Base class for composite graphical objects.
18 Objects belong to a canvas, and can be moved around on the canvas.
19 They also belong to at most one ``pile'' of objects, and can be
20 transferred between piles (or removed from their pile).
22 Objects have a canonical ``x, y'' position which is moved when the
23 object is moved. Where the object is relative to this position
24 depends on the object; for simple objects, it may be their center.
26 Objects have mouse sensitivity. They can be clicked, dragged and
27 double-clicked. The behavior may actually determined by the pile
28 they are in.
30 All instance attributes are public since the derived class may
31 need them.
33 """
35 def __init__(self, canvas, x=0, y=0, fill='red', text='object'):
36 self.canvas = canvas
37 self.x = x
38 self.y = y
39 self.pile = None
40 self.group = Group(self.canvas)
41 self.createitems(fill, text)
43 def __str__(self):
44 return str(self.group)
46 def createitems(self, fill, text):
47 self.__oval = Oval(self.canvas,
48 self.x-20, self.y-10, self.x+20, self.y+10,
49 fill=fill, width=3)
50 self.group.addtag_withtag(self.__oval)
51 self.__text = CanvasText(self.canvas,
52 self.x, self.y, text=text)
53 self.group.addtag_withtag(self.__text)
55 def moveby(self, dx, dy):
56 if dx == dy == 0:
57 return
58 self.group.move(dx, dy)
59 self.x = self.x + dx
60 self.y = self.y + dy
62 def moveto(self, x, y):
63 self.moveby(x - self.x, y - self.y)
65 def transfer(self, pile):
66 if self.pile:
67 self.pile.delete(self)
68 self.pile = None
69 self.pile = pile
70 if self.pile:
71 self.pile.add(self)
73 def tkraise(self):
74 self.group.tkraise()
77 class Bottom(Object):
79 """An object to serve as the bottom of a pile."""
81 def createitems(self, *args):
82 self.__oval = Oval(self.canvas,
83 self.x-20, self.y-10, self.x+20, self.y+10,
84 fill='gray', outline='')
85 self.group.addtag_withtag(self.__oval)
88 class Pile:
90 """A group of graphical objects."""
92 def __init__(self, canvas, x, y, tag=None):
93 self.canvas = canvas
94 self.x = x
95 self.y = y
96 self.objects = []
97 self.bottom = Bottom(self.canvas, self.x, self.y)
98 self.group = Group(self.canvas, tag=tag)
99 self.group.addtag_withtag(self.bottom.group)
100 self.bindhandlers()
102 def bindhandlers(self):
103 self.group.bind('<1>', self.clickhandler)
104 self.group.bind('<Double-1>', self.doubleclickhandler)
106 def add(self, object):
107 self.objects.append(object)
108 self.group.addtag_withtag(object.group)
109 self.position(object)
111 def delete(self, object):
112 object.group.dtag(self.group)
113 self.objects.remove(object)
115 def position(self, object):
116 object.tkraise()
117 i = self.objects.index(object)
118 object.moveto(self.x + i*4, self.y + i*8)
120 def clickhandler(self, event):
121 pass
123 def doubleclickhandler(self, event):
124 pass
127 class MovingPile(Pile):
129 def bindhandlers(self):
130 Pile.bindhandlers(self)
131 self.group.bind('<B1-Motion>', self.motionhandler)
132 self.group.bind('<ButtonRelease-1>', self.releasehandler)
134 movethis = None
136 def clickhandler(self, event):
137 tags = self.canvas.gettags('current')
138 for i in range(len(self.objects)):
139 o = self.objects[i]
140 if o.group.tag in tags:
141 break
142 else:
143 self.movethis = None
144 return
145 self.movethis = self.objects[i:]
146 for o in self.movethis:
147 o.tkraise()
148 self.lastx = event.x
149 self.lasty = event.y
151 doubleclickhandler = clickhandler
153 def motionhandler(self, event):
154 if not self.movethis:
155 return
156 dx = event.x - self.lastx
157 dy = event.y - self.lasty
158 self.lastx = event.x
159 self.lasty = event.y
160 for o in self.movethis:
161 o.moveby(dx, dy)
163 def releasehandler(self, event):
164 objects = self.movethis
165 if not objects:
166 return
167 self.movethis = None
168 self.finishmove(objects)
170 def finishmove(self, objects):
171 for o in objects:
172 self.position(o)
175 class Pile1(MovingPile):
177 x = 50
178 y = 50
179 tag = 'p1'
181 def __init__(self, demo):
182 self.demo = demo
183 MovingPile.__init__(self, self.demo.canvas, self.x, self.y, self.tag)
185 def doubleclickhandler(self, event):
186 try:
187 o = self.objects[-1]
188 except IndexError:
189 return
190 o.transfer(self.other())
191 MovingPile.doubleclickhandler(self, event)
193 def other(self):
194 return self.demo.p2
196 def finishmove(self, objects):
197 o = objects[0]
198 p = self.other()
199 x, y = o.x, o.y
200 if (x-p.x)**2 + (y-p.y)**2 < (x-self.x)**2 + (y-self.y)**2:
201 for o in objects:
202 o.transfer(p)
203 else:
204 MovingPile.finishmove(self, objects)
206 class Pile2(Pile1):
208 x = 150
209 y = 50
210 tag = 'p2'
212 def other(self):
213 return self.demo.p1
216 class Demo:
218 def __init__(self, master):
219 self.master = master
220 self.canvas = Canvas(master,
221 width=200, height=200,
222 background='yellow',
223 relief=SUNKEN, borderwidth=2)
224 self.canvas.pack(expand=1, fill=BOTH)
225 self.p1 = Pile1(self)
226 self.p2 = Pile2(self)
227 o1 = Object(self.canvas, fill='red', text='o1')
228 o2 = Object(self.canvas, fill='green', text='o2')
229 o3 = Object(self.canvas, fill='light blue', text='o3')
230 o1.transfer(self.p1)
231 o2.transfer(self.p1)
232 o3.transfer(self.p2)
235 # Main function, run when invoked as a stand-alone Python program.
237 def main():
238 root = Tk()
239 demo = Demo(root)
240 root.protocol('WM_DELETE_WINDOW', root.quit)
241 root.mainloop()
243 if __name__ == '__main__':
244 main()