Home | History | Annotate | Download | only in guido
      1 #! /usr/bin/env python
      2 
      3 from Tkinter import *
      4 from 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 
     10 class Group(Group):
     11     def bind(self, sequence=None, command=None):
     12         return self.canvas.tag_bind(self.id, sequence, command)
     13 
     14 class 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 
     77 class 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 
     88 class 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 
    127 class 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 
    175 class 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 
    206 class Pile2(Pile1):
    207 
    208     x = 150
    209     y = 50
    210     tag = 'p2'
    211 
    212     def other(self):
    213         return self.demo.p1
    214 
    215 
    216 class 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 
    237 def main():
    238     root = Tk()
    239     demo = Demo(root)
    240     root.protocol('WM_DELETE_WINDOW', root.quit)
    241     root.mainloop()
    242 
    243 if __name__ == '__main__':
    244     main()
    245