1#! /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 be 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() 245