Benjamin Peterson | 90f5ba5 | 2010-03-11 22:53:45 +0000 | [diff] [blame] | 1 | #! /usr/bin/env python3 |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 2 | |
Benjamin Peterson | d6d63f5 | 2009-01-04 18:53:28 +0000 | [diff] [blame] | 3 | from tkinter import * |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 4 | |
| 5 | |
Georg Brandl | 856023a | 2010-10-25 17:50:20 +0000 | [diff] [blame^] | 6 | # Since Canvas.Group is no longer present, the following class reproduces |
| 7 | # a subset of the old Group class that is used by this app. |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 8 | |
Georg Brandl | 856023a | 2010-10-25 17:50:20 +0000 | [diff] [blame^] | 9 | class Group: |
| 10 | def __init__(self, canvas, tag=None): |
| 11 | if tag is None: |
| 12 | tag = 'Group%d' % id(self) |
| 13 | |
| 14 | self.tag = self.id = tag |
| 15 | self.canvas = canvas |
| 16 | self.canvas.dtag(self.tag) |
| 17 | |
| 18 | def __str__(self): |
| 19 | return self.tag |
| 20 | |
| 21 | def _do(self, cmd, *args): |
| 22 | return self.canvas.tk.call(self.canvas._w, cmd, self.tag, *args) |
| 23 | |
| 24 | def addtag_withtag(self, tagOrId): |
| 25 | self._do('addtag', 'withtag', tagOrId) |
| 26 | |
| 27 | def bind(self, sequence=None, command=None, add=None): |
| 28 | return self.canvas.tag_bind(self.id, sequence, command, add) |
| 29 | |
| 30 | def move(self, x_amount, y_amount): |
| 31 | self._do('move', x_amount, y_amount) |
| 32 | |
| 33 | def dtag(self, tagToDelete=None): |
| 34 | self._do('dtag', tagToDelete) |
| 35 | |
| 36 | def tkraise(self, aboveThis=None): |
| 37 | self._do('raise', aboveThis) |
| 38 | |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 39 | |
| 40 | class Object: |
| 41 | |
| 42 | """Base class for composite graphical objects. |
| 43 | |
| 44 | Objects belong to a canvas, and can be moved around on the canvas. |
| 45 | They also belong to at most one ``pile'' of objects, and can be |
| 46 | transferred between piles (or removed from their pile). |
| 47 | |
| 48 | Objects have a canonical ``x, y'' position which is moved when the |
| 49 | object is moved. Where the object is relative to this position |
| 50 | depends on the object; for simple objects, it may be their center. |
| 51 | |
| 52 | Objects have mouse sensitivity. They can be clicked, dragged and |
| 53 | double-clicked. The behavior may actually determined by the pile |
| 54 | they are in. |
| 55 | |
| 56 | All instance attributes are public since the derived class may |
| 57 | need them. |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 58 | """ |
| 59 | |
| 60 | def __init__(self, canvas, x=0, y=0, fill='red', text='object'): |
Tim Peters | 182b5ac | 2004-07-18 06:16:08 +0000 | [diff] [blame] | 61 | self.canvas = canvas |
| 62 | self.x = x |
| 63 | self.y = y |
| 64 | self.pile = None |
| 65 | self.group = Group(self.canvas) |
| 66 | self.createitems(fill, text) |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 67 | |
| 68 | def __str__(self): |
Tim Peters | 182b5ac | 2004-07-18 06:16:08 +0000 | [diff] [blame] | 69 | return str(self.group) |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 70 | |
| 71 | def createitems(self, fill, text): |
Georg Brandl | 856023a | 2010-10-25 17:50:20 +0000 | [diff] [blame^] | 72 | self.__oval = self.canvas.create_oval(self.x - 20, self.y - 10, |
| 73 | self.x + 20, self.y + 20, fill=fill, width=3) |
Tim Peters | 182b5ac | 2004-07-18 06:16:08 +0000 | [diff] [blame] | 74 | self.group.addtag_withtag(self.__oval) |
Georg Brandl | 856023a | 2010-10-25 17:50:20 +0000 | [diff] [blame^] | 75 | self.__text = self.canvas.create_text(self.x, self.y, text=text) |
Tim Peters | 182b5ac | 2004-07-18 06:16:08 +0000 | [diff] [blame] | 76 | self.group.addtag_withtag(self.__text) |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 77 | |
| 78 | def moveby(self, dx, dy): |
Tim Peters | 182b5ac | 2004-07-18 06:16:08 +0000 | [diff] [blame] | 79 | if dx == dy == 0: |
| 80 | return |
| 81 | self.group.move(dx, dy) |
| 82 | self.x = self.x + dx |
| 83 | self.y = self.y + dy |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 84 | |
| 85 | def moveto(self, x, y): |
Tim Peters | 182b5ac | 2004-07-18 06:16:08 +0000 | [diff] [blame] | 86 | self.moveby(x - self.x, y - self.y) |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 87 | |
| 88 | def transfer(self, pile): |
Tim Peters | 182b5ac | 2004-07-18 06:16:08 +0000 | [diff] [blame] | 89 | if self.pile: |
| 90 | self.pile.delete(self) |
| 91 | self.pile = None |
| 92 | self.pile = pile |
| 93 | if self.pile: |
| 94 | self.pile.add(self) |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 95 | |
| 96 | def tkraise(self): |
Tim Peters | 182b5ac | 2004-07-18 06:16:08 +0000 | [diff] [blame] | 97 | self.group.tkraise() |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 98 | |
| 99 | |
| 100 | class Bottom(Object): |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 101 | """An object to serve as the bottom of a pile.""" |
| 102 | |
| 103 | def createitems(self, *args): |
Georg Brandl | 856023a | 2010-10-25 17:50:20 +0000 | [diff] [blame^] | 104 | self.__oval = self.canvas.create_oval(self.x - 20, self.y - 10, |
| 105 | self.x + 20, self.y + 10, fill='gray', outline='') |
Tim Peters | 182b5ac | 2004-07-18 06:16:08 +0000 | [diff] [blame] | 106 | self.group.addtag_withtag(self.__oval) |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 107 | |
| 108 | |
| 109 | class Pile: |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 110 | """A group of graphical objects.""" |
| 111 | |
| 112 | def __init__(self, canvas, x, y, tag=None): |
Tim Peters | 182b5ac | 2004-07-18 06:16:08 +0000 | [diff] [blame] | 113 | self.canvas = canvas |
| 114 | self.x = x |
| 115 | self.y = y |
| 116 | self.objects = [] |
| 117 | self.bottom = Bottom(self.canvas, self.x, self.y) |
| 118 | self.group = Group(self.canvas, tag=tag) |
| 119 | self.group.addtag_withtag(self.bottom.group) |
| 120 | self.bindhandlers() |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 121 | |
| 122 | def bindhandlers(self): |
Tim Peters | 182b5ac | 2004-07-18 06:16:08 +0000 | [diff] [blame] | 123 | self.group.bind('<1>', self.clickhandler) |
| 124 | self.group.bind('<Double-1>', self.doubleclickhandler) |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 125 | |
| 126 | def add(self, object): |
Tim Peters | 182b5ac | 2004-07-18 06:16:08 +0000 | [diff] [blame] | 127 | self.objects.append(object) |
| 128 | self.group.addtag_withtag(object.group) |
| 129 | self.position(object) |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 130 | |
| 131 | def delete(self, object): |
Tim Peters | 182b5ac | 2004-07-18 06:16:08 +0000 | [diff] [blame] | 132 | object.group.dtag(self.group) |
| 133 | self.objects.remove(object) |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 134 | |
| 135 | def position(self, object): |
Tim Peters | 182b5ac | 2004-07-18 06:16:08 +0000 | [diff] [blame] | 136 | object.tkraise() |
| 137 | i = self.objects.index(object) |
| 138 | object.moveto(self.x + i*4, self.y + i*8) |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 139 | |
| 140 | def clickhandler(self, event): |
Tim Peters | 182b5ac | 2004-07-18 06:16:08 +0000 | [diff] [blame] | 141 | pass |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 142 | |
| 143 | def doubleclickhandler(self, event): |
Tim Peters | 182b5ac | 2004-07-18 06:16:08 +0000 | [diff] [blame] | 144 | pass |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 145 | |
| 146 | |
| 147 | class MovingPile(Pile): |
| 148 | |
| 149 | def bindhandlers(self): |
Tim Peters | 182b5ac | 2004-07-18 06:16:08 +0000 | [diff] [blame] | 150 | Pile.bindhandlers(self) |
| 151 | self.group.bind('<B1-Motion>', self.motionhandler) |
| 152 | self.group.bind('<ButtonRelease-1>', self.releasehandler) |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 153 | |
| 154 | movethis = None |
| 155 | |
| 156 | def clickhandler(self, event): |
Tim Peters | 182b5ac | 2004-07-18 06:16:08 +0000 | [diff] [blame] | 157 | tags = self.canvas.gettags('current') |
| 158 | for i in range(len(self.objects)): |
| 159 | o = self.objects[i] |
| 160 | if o.group.tag in tags: |
| 161 | break |
| 162 | else: |
| 163 | self.movethis = None |
| 164 | return |
| 165 | self.movethis = self.objects[i:] |
| 166 | for o in self.movethis: |
| 167 | o.tkraise() |
| 168 | self.lastx = event.x |
| 169 | self.lasty = event.y |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 170 | |
| 171 | doubleclickhandler = clickhandler |
| 172 | |
| 173 | def motionhandler(self, event): |
Tim Peters | 182b5ac | 2004-07-18 06:16:08 +0000 | [diff] [blame] | 174 | if not self.movethis: |
| 175 | return |
| 176 | dx = event.x - self.lastx |
| 177 | dy = event.y - self.lasty |
| 178 | self.lastx = event.x |
| 179 | self.lasty = event.y |
| 180 | for o in self.movethis: |
| 181 | o.moveby(dx, dy) |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 182 | |
| 183 | def releasehandler(self, event): |
Tim Peters | 182b5ac | 2004-07-18 06:16:08 +0000 | [diff] [blame] | 184 | objects = self.movethis |
| 185 | if not objects: |
| 186 | return |
| 187 | self.movethis = None |
| 188 | self.finishmove(objects) |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 189 | |
| 190 | def finishmove(self, objects): |
Tim Peters | 182b5ac | 2004-07-18 06:16:08 +0000 | [diff] [blame] | 191 | for o in objects: |
| 192 | self.position(o) |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 193 | |
| 194 | |
| 195 | class Pile1(MovingPile): |
| 196 | |
| 197 | x = 50 |
| 198 | y = 50 |
| 199 | tag = 'p1' |
| 200 | |
| 201 | def __init__(self, demo): |
Tim Peters | 182b5ac | 2004-07-18 06:16:08 +0000 | [diff] [blame] | 202 | self.demo = demo |
| 203 | MovingPile.__init__(self, self.demo.canvas, self.x, self.y, self.tag) |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 204 | |
| 205 | def doubleclickhandler(self, event): |
Tim Peters | 182b5ac | 2004-07-18 06:16:08 +0000 | [diff] [blame] | 206 | try: |
| 207 | o = self.objects[-1] |
| 208 | except IndexError: |
| 209 | return |
| 210 | o.transfer(self.other()) |
| 211 | MovingPile.doubleclickhandler(self, event) |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 212 | |
| 213 | def other(self): |
Tim Peters | 182b5ac | 2004-07-18 06:16:08 +0000 | [diff] [blame] | 214 | return self.demo.p2 |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 215 | |
| 216 | def finishmove(self, objects): |
Tim Peters | 182b5ac | 2004-07-18 06:16:08 +0000 | [diff] [blame] | 217 | o = objects[0] |
| 218 | p = self.other() |
| 219 | x, y = o.x, o.y |
| 220 | if (x-p.x)**2 + (y-p.y)**2 < (x-self.x)**2 + (y-self.y)**2: |
| 221 | for o in objects: |
| 222 | o.transfer(p) |
| 223 | else: |
| 224 | MovingPile.finishmove(self, objects) |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 225 | |
| 226 | class Pile2(Pile1): |
| 227 | |
| 228 | x = 150 |
| 229 | y = 50 |
| 230 | tag = 'p2' |
| 231 | |
| 232 | def other(self): |
Tim Peters | 182b5ac | 2004-07-18 06:16:08 +0000 | [diff] [blame] | 233 | return self.demo.p1 |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 234 | |
| 235 | |
| 236 | class Demo: |
| 237 | |
| 238 | def __init__(self, master): |
Tim Peters | 182b5ac | 2004-07-18 06:16:08 +0000 | [diff] [blame] | 239 | self.master = master |
| 240 | self.canvas = Canvas(master, |
| 241 | width=200, height=200, |
| 242 | background='yellow', |
| 243 | relief=SUNKEN, borderwidth=2) |
| 244 | self.canvas.pack(expand=1, fill=BOTH) |
| 245 | self.p1 = Pile1(self) |
| 246 | self.p2 = Pile2(self) |
| 247 | o1 = Object(self.canvas, fill='red', text='o1') |
| 248 | o2 = Object(self.canvas, fill='green', text='o2') |
| 249 | o3 = Object(self.canvas, fill='light blue', text='o3') |
| 250 | o1.transfer(self.p1) |
| 251 | o2.transfer(self.p1) |
| 252 | o3.transfer(self.p2) |
Guido van Rossum | 9a8cb84 | 1997-04-03 00:04:51 +0000 | [diff] [blame] | 253 | |
| 254 | |
| 255 | # Main function, run when invoked as a stand-alone Python program. |
| 256 | |
| 257 | def main(): |
| 258 | root = Tk() |
| 259 | demo = Demo(root) |
| 260 | root.protocol('WM_DELETE_WINDOW', root.quit) |
| 261 | root.mainloop() |
| 262 | |
| 263 | if __name__ == '__main__': |
| 264 | main() |