blob: 60f409659bf8e72dea3be7109748e5eb0fa8f870 [file] [log] [blame]
Guido van Rossum9a8cb841997-04-03 00:04:51 +00001#! /usr/bin/env python
2
3from Tkinter import *
4from Canvas import Oval, Group, CanvasText
5
6
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:
9
10class Group(Group):
11 def bind(self, sequence=None, command=None):
12 return self.canvas.tag_bind(self.id, sequence, command)
13
14class Object:
15
16 """Base class for composite graphical objects.
17
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).
21
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.
25
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.
29
30 All instance attributes are public since the derived class may
31 need them.
32
33 """
34
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)
42
43 def __str__(self):
44 return str(self.group)
45
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)
54
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
61
62 def moveto(self, x, y):
63 self.moveby(x - self.x, y - self.y)
64
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)
72
73 def tkraise(self):
74 self.group.tkraise()
75
76
77class Bottom(Object):
78
79 """An object to serve as the bottom of a pile."""
80
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)
86
87
88class Pile:
89
90 """A group of graphical objects."""
91
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()
101
102 def bindhandlers(self):
103 self.group.bind('<1>', self.clickhandler)
104 self.group.bind('<Double-1>', self.doubleclickhandler)
105
106 def add(self, object):
107 self.objects.append(object)
108 self.group.addtag_withtag(object.group)
109 self.position(object)
110
111 def delete(self, object):
112 object.group.dtag(self.group)
113 self.objects.remove(object)
114
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)
119
120 def clickhandler(self, event):
121 pass
122
123 def doubleclickhandler(self, event):
124 pass
125
126
127class MovingPile(Pile):
128
129 def bindhandlers(self):
130 Pile.bindhandlers(self)
131 self.group.bind('<B1-Motion>', self.motionhandler)
132 self.group.bind('<ButtonRelease-1>', self.releasehandler)
133
134 movethis = None
135
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
150
151 doubleclickhandler = clickhandler
152
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)
162
163 def releasehandler(self, event):
164 objects = self.movethis
165 if not objects:
166 return
167 self.movethis = None
168 self.finishmove(objects)
169
170 def finishmove(self, objects):
171 for o in objects:
172 self.position(o)
173
174
175class Pile1(MovingPile):
176
177 x = 50
178 y = 50
179 tag = 'p1'
180
181 def __init__(self, demo):
182 self.demo = demo
183 MovingPile.__init__(self, self.demo.canvas, self.x, self.y, self.tag)
184
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)
192
193 def other(self):
194 return self.demo.p2
195
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)
205
206class Pile2(Pile1):
207
208 x = 150
209 y = 50
210 tag = 'p2'
211
212 def other(self):
213 return self.demo.p1
214
215
216class Demo:
217
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)
233
234
235# Main function, run when invoked as a stand-alone Python program.
236
237def main():
238 root = Tk()
239 demo = Demo(root)
240 root.protocol('WM_DELETE_WINDOW', root.quit)
241 root.mainloop()
242
243if __name__ == '__main__':
244 main()