from Gui import *

class Item:
    """an Item object represents a canvas item.

    When you create a canvas item, Tkinter returns an integer 'tag'
    that identifies the new item.  To perform an operation on the
    item, you invoke a method on the canvas and pass the tag as
    a parameter.

    The Item class makes this interface more object-oriented:
    each Item object contains a canvas and a tag.  When you
    invoke methods on the Item, it invokes methods on its canvas.
    """
    def __init__(self, canvas, tag):
        self.canvas = canvas
        self.tag = tag
        self.handles = []
        self.bind('<Button-1>', self.select)
        self.bind('<B1-Motion>', self.drag)
        self.bind('<ButtonRelease-1>', self.release)

    def __str__(self):
        return 'Item' + str(self.tag)
        
    def show_handles(self):
        """draw handles for this item (if necessary)"""
        if self.handles: return
        
        coords = self.coords()
        for i in range(len(coords)):
            x, y = coords[i]
            tag = self.canvas.circle(x, y, 5, fill='yellow')
            handle = Handle(self.canvas, tag, self, i)
            self.handles.append(handle)

    # the following are wrappers for canvas methods
    
    def bind(self, event, callback):
        """this method applies bindings to canvas items (not
        the whole canvas)"""
        self.canvas.tag_bind(self.tag, event, callback)

    def config(self, **options):
        """reconfigure this item with the given options"""
        self.canvas.itemconfig(self.tag, **options)

    def coords(self, *args):
        """get or set the canvas coordinates for this item"""
        return self.canvas.coords(self.tag, *args)

    def move(self, dx, dy):
        """move this item by (dx, dy) in canvas coordinates"""
        self.canvas.move(self.tag, dx, dy)

    def move_coord(self, i, dx, dy):
        """move the ith coordinate by (dx, dy) in canvas coordinates """
        coords = self.coords()
        coords[i][0] += dx
        coords[i][1] += dy
        self.coords(coords)

    def replace_coord(self, i, coord):
        """replace the ith coordinate with the given coordinate"""
        coords = self.coords()
        coords[i] = coord
        self.coords(coords)

    # the following event handlers take an event object as a parameter

    def select(self, event):
        print 'Item.select', self
        self.config(fill='red')
        self.set_drag(event)
        self.show_handles()
        
    def drag(self, event):
        """move this item (and its handles) using the pixel
        coordinates in the event object."""

        # see how far we have moved
        dx, dy = self.sub_drag(event, self.drag)

        # save the current drag coordinates
        self.set_drag(event)

        # move the item and its handles
        self.move(dx, dy)
        for handle in self.handles:
            handle.move(dx, dy)

        return dx, dy

    def release(self, event):
        print 'Item.release', self
        self.config(fill='blue')


    # the following methods are for dealing with events and drag
    # coordinates
        
    def get_drag(self, event):
        """get the drag coordinates from this event and translate
        them into canvas coordinates"""
        x, y = self.canvas.trans([event.x, event.y])
        return x, y

    def set_drag(self, event):
        """store the current drag coordinates"""
        self.drag = self.get_drag(event)

    def sub_drag(self, event, d2):
        """subtract d2 from the drag coordinates in event"""
        d1 = self.get_drag(event)
        return d1[0] - d2[0], d1[1] - d2[1]
        

class Handle(Item):
    def __init__(self, canvas, tag, item, index):
        Item.__init__(self, canvas, tag)
        self.item = item
        self.index = index

    def select(self, event):
        self.set_drag(event)
        
    def drag(self, event):
        """this method is relatively elegant, but it is based on
        assumptions about the way the canvas works that turn out
        not to be true.  So it has an annoying behavior for
        ovals and rectangles.

        To do this right, you probably have to customize the
        behavior of different items (this version probably works
        for polylines, but not ovals and rectangles)."""

        # move the handle
        dx, dy = Item.drag(self, event)

        # move the coordinate that corresponds to this handle
        self.item.move_coord(self.index, dx, dy)

    def release(self, event):
        """override Item.release and do nothing"""


class Hello(Gui):
    def __init__(self):
        Gui.__init__(self)
        self.ca_width = 400
        self.ca_height = 400
        self.setup()

    def setup(self):
        
        # frame 1
        self.fr(TOP)
        self.canvas = self.ca(width=self.ca_width, height=self.ca_height,
                              bg='white')
        self.canvas.bind('<Button-1>', self.click)
        self.endfr()

        # frame 2
        self.fr(TOP, fill=BOTH, expand=1)

        self.fr()
        self.bu(LEFT, text='Hello', command=self.hello)
        self.bu(LEFT, text='Circle', command=self.circle)
        self.bu(LEFT, text='Quit', command=self.quit)
        self.endfr()

        self.endfr()

    def hello(self):
        font = ('Helvetica', 36)
        tag = self.canvas.text([0, 0], 'Hello', font=font, fill='blue')
        item = Item(self.canvas, tag)

    def circle(self):
        tag = self.canvas.circle(0, 0, 100, 'blue')
        item = Item(self.canvas, tag)

    def click(self, event):
        """this event handler gets invoked when the user clicks
        on the canvas."""
        print 'Hello.click', event.type, event.num, event.x, event.y


if __name__ == '__main__':
    h = Hello()
    h.mainloop()
