Public feed #377553

Mine

public text v1 · immutable
Raw Edit
·published 2007-03-01 21:11 UTC
rendered paste body
"""Simple object oriented graphics library for
Introduction to Programming using Python
by John Zelle, Ph.D."""

# Version 1.6
#     Fixed Entry so StringVar uses _root as master, solve weird
#            interaction with shell in Idle
#     Fixed bug in setCoords. X and Y coordinates can increase in
#           "non-intuitive" direction.
#     Tweaked wm_protocol so window is not resizable and kill box closes.

# Version 1.5
#     Fixed bug in Entry. Can now define entry before creating a
#     GraphWin. All GraphWins are now toplevel windows and share
#     a fixed root (called _root).

# Version 1.4
#     Fix Garbage collection of Tkinter images bug.
#     Add ability to set text atttributes.
#     Add Entry boxes.


import Tkinter
tk = Tkinter

from copy import copy

OBJ_ALREADY_DRAWN = "Graphics Error: Object already visible"
UNSUPPORTED_METHOD = "Graphics Error: Object doesn't support operation"
BAD_OPTION = "Graphics Error: Illegal option value"

# Default values for various item configuration options. Only a subset of
#   keys may be present in the configuration dictionary for a given item
DEFAULT_CONFIG = {"fill":"",
		  "outline":"black",
		  "width":"1",
		  "arrow":"none",
		  "text":"",
		  "justify":"center",
                  "font": ("helvetica", 12, "normal")}

class GraphicsObject:
    
    def __init__(self, options):
        self.canvas = None
        self.id = None
        config = {}
        for option in options:
            config[option] = DEFAULT_CONFIG[option]
        self.config = config
        
    def setFill(self, color): self._reconfig("fill", color)
        
    def setOutline(self, color): self._reconfig("outline", color)
        
    def setWidth(self, width): self._reconfig("width", width)

    def draw(self, canvas):
        if self.canvas: raise OBJ_ALREADY_DRAWN
        self.canvas=canvas
        self.id = self._draw(canvas, self.config)

    def undraw(self):
        if not self.canvas: return
        self.canvas.delete(self.id)
        self.canvas = None
        self.id = None
        
    def move(self, dx, dy):
        self._move(dx,dy)
        canvas = self.canvas
        if canvas:
            trans = canvas.trans
            if trans:
                x = dx/ trans.xscale 
                y = -dy / trans.yscale
            else:
                x = dx
                y = dy
            self.canvas.move(self.id, x, y)
           
    def _reconfig(self, option, setting):
        if not self.config.has_key(option): raise UNSUPPORTED_METHOD
        options = self.config
        options[option] = setting
        if self.canvas: self.canvas.itemconfig(self.id, options)
         
class Point(GraphicsObject):
    def __init__(self, x, y):
        GraphicsObject.__init__(self, ["outline", "fill"])
        self.setFill = self.setOutline
        self.x = x
        self.y = y
        
    def _draw(self, canvas, options):
        x,y = canvas.toScreen(self.x,self.y)
        return canvas.create_rectangle(x,y,x+1,y+1,options)
        
    def _move(self, dx, dy):
        self.x = self.x + dx
        self.y = self.y + dy
        
    def clone(self):
        other = Point(self.x,self.y)
        other.config = self.config
        return other
                
    def getX(self): return self.x
    def getY(self): return self.y

class _BBox(GraphicsObject):
    def __init__(self, p1, p2, options=["outline","width","fill"]):
        GraphicsObject.__init__(self, options)
        self.p1 = p1.clone()
	self.p2 = p2.clone()

    def _move(self, dx, dy):
        self.p1.x = self.p1.x + dx
        self.p1.y = self.p1.y + dy
        self.p2.x = self.p2.x + dx
        self.p2.y = self.p2.y  + dy
                
    def getP1(self): return self.p1

    def getP2(self): return self.p2
    
    def getCenter(self):
        p1 = self.p1
        p2 = self.p2
        return Point((p1.x+p2.x)/2.0, (p1.y+p2.y)/2.0)
    
class Rectangle(_BBox):
    
    def __init__(self, p1, p2):
        _BBox.__init__(self, p1, p2)
    
    def _draw(self, canvas, options):
        p1 = self.p1
        p2 = self.p2
        x1,y1 = canvas.toScreen(p1.x,p1.y)
        x2,y2 = canvas.toScreen(p2.x,p2.y)
        return canvas.create_rectangle(x1,y1,x2,y2,options)
        
    def clone(self):
        other = Rectangle(self.p1, self.p2)
        other.config = self.config
        return other
        
class Oval(_BBox):
    
    def __init__(self, p1, p2):
        _BBox.__init__(self, p1, p2)
        
    def clone(self):
        other = Oval(self.p1, self.p2)
        other.config = self.config
        return other
   
    def _draw(self, canvas, options):
        p1 = self.p1
        p2 = self.p2
        x1,y1 = canvas.toScreen(p1.x,p1.y)
        x2,y2 = canvas.toScreen(p2.x,p2.y)
        return canvas.create_oval(x1,y1,x2,y2,options)

class Polygon(GraphicsObject):
    
    def __init__(self, *points):
        self.points = map(Point.clone, points)
        GraphicsObject.__init__(self, ["outline", "width", "fill"])
        
    def clone(self):
        other = apply(Polygon, self.points)
        other.config = self.config
        return other

    def getPoints(self):
        return map(Point.clone, self.points)

    def _move(self, dx, dy):
        for p in self.points:
            p.move(dx,dy)
   
    def _draw(self, canvas, options):
        args = [canvas]
        for p in self.points:
            x,y = canvas.toScreen(p.x,p.y)
            args.append(x)
            args.append(y)
        args.append(options)
        return apply(GraphWin.create_polygon, args) 
    
class Circle(Oval):
    
    def __init__(self, center, radius):
        p1 = Point(center.x-radius, center.y-radius)
        p2 = Point(center.x+radius, center.y+radius)
        Oval.__init__(self, p1, p2)
        self.radius = radius
        
    def clone(self):
        other = Circle(self.getCenter(), self.radius)
        other.config = self.config
        return other
        
    def getRadius(self):
        return self.radius
              
class Line(_BBox):
    
    def __init__(self, p1, p2):
        _BBox.__init__(self, p1, p2, ["arrow","fill","width"])
        self.setFill(DEFAULT_CONFIG['outline'])
        self.setOutline = self.setFill
   
    def clone(self):
        other = Line(self.p1, self.p2)
        other.config = self.config
        return other
	
    def _draw(self, canvas, options):
        p1 = self.p1
        p2 = self.p2
        x1,y1 = canvas.toScreen(p1.x,p1.y)
        x2,y2 = canvas.toScreen(p2.x,p2.y)
        return canvas.create_line(x1,y1,x2,y2,options)
        
    def setArrow(self, option):
        if not option in ["first","last","both","none"]:
            raise BAD_OPTION
        self._reconfig("arrow", option)
        

class Text(GraphicsObject):
    
    	def __init__(self, p, text):
    		GraphicsObject.__init__(self, ["justify","fill","text","font"])
    		self.setText(text)
    		self.anchor = p.clone()
    		self.setFill(DEFAULT_CONFIG['outline'])
                self.setOutline = self.setFill
    		
    	def _draw(self, canvas, options):
    		p = self.anchor
    		x,y = canvas.toScreen(p.x,p.y)
    		return canvas.create_text(x,y,options)
    		
    	def _move(self, dx, dy):
    		self.anchor.move(dx,dy)
    		
    	def clone(self):
    		other = Text(self.anchor, self.text)
    		other.config = self.config
    		return other

    	def setText(self,text):
    		self._reconfig("text", text)
    		
    	def getText(self):
    		return self.config["text"]
    	    	
    	def getAnchor(self):
    		return self.anchor.clone()

        def setFace(self, face):
            if face in ['helvetica','arial','courier','times roman']:
                f,s,b = self.config['font']
                self._reconfig("font",(face,s,b))
            else:
                raise BAD_OPTION

        def setSize(self, size):
            if 5 <= size <= 36:
                f,s,b = self.config['font']
                self._reconfig("font", (f,size,b))
            else:
                raise BAD_OPTION

        def setStyle(self, style):
            if style in ['bold','normal','italic', 'bold italic']:
                f,s,b = self.config['font']
                self._reconfig("font", (f,s,style))
            else:
                raise BAD_OPTION


class Entry(GraphicsObject):

    def __init__(self, p, width):
        GraphicsObject.__init__(self, [])
        self.anchor = p.clone()
        #print self.anchor
        self.width = width
        self.text = tk.StringVar(_root)
        self.text.set("")
        self.fill = "gray"

    def _draw(self, canvas, options):
        p = self.anchor
        x,y = canvas.toScreen(p.x,p.y)
        frm = tk.Frame(canvas.master)
        self.entry = tk.Entry(frm, width=self.width, textvariable=self.text)
        self.entry.pack()
        self.setFill(self.fill)
        return canvas.create_window(x,y,window=frm)

    def getText(self):
        return self.text.get()

    def _move(self, dx, dy):
        self.anchor.move(dx,dy)

    def getAnchor(self):
        return self.anchor.clone()

    def clone(self):
        other = Entry(self.anchor, self.width)
        other.config = self.config
        other.text = tk.StringVar()
        other.text.set(self.text.get())
        other.fill = self.fill
        return other

    def setText(self, t):
        self.text.set(t)
            
    def setFill(self, color):
        self.fill = color
        self.entry.config(bg=color)

    		
class Image(GraphicsObject):

    idCount = 0
    imageCache = {}  # keep images here to avoid gc
    
    def __init__(self, p, file=None):
        GraphicsObject.__init__(self, [])
        self.file = file
        self.anchor = p.clone()
        self.imageId = Image.idCount
        Image.idCount = Image.idCount + 1
    		
    def _draw(self, canvas, options):
        p = self.anchor
        x,y = canvas.toScreen(p.x,p.y)
        img = tk.PhotoImage(file=self.file, master=canvas)
        self.imageCache[self.imageId] = img  # save in cache to avoid gc 
        return canvas.create_image(x,y,image=img)
    
    def _move(self, dx, dy):
        self.anchor.move(dx,dy)

    def undraw(self):
        del self.imageCache[self.imageId]  # allow gc of tk photoimage
        GraphicsObject.undraw(self)

    def getAnchor(self):
        return self.anchor.clone()
    		
    def clone(self):
        other = Image(self.anchor, self.file)
        other.config = self.config
        return other

class GraphWin(tk.Canvas):
    
    def __init__(self, title="Graphics Window", width=200, height=200):
        master = tk.Toplevel(_root)
        master.protocol("WM_DELETE_WINDOW", self.close)
        tk.Canvas.__init__(self, master, width=width, height=height)
        self.master.title(title)
        self.pack()
        master.resizable(0,0)
        self.foreground = "black"
        self.items = []
        self.mouseX = None
        self.mouseY = None
        self.bind("<Button-1>", self._onClick)
        self.height = height
        self.width = width
        self._mouseCallback = None
        self.trans = None
        
    def setBackground(self, color):
        self.config(bg=color)
        
    def setCoords(self, x1, y1, x2, y2):
        self.trans = Transform(self.width, self.height, x1, y1, x2, y2)
        
    def close(self):
        self.master.destroy()
    
    def plot(self, x, y, color="black"):
        xs,ys = self.toScreen(x,y)
        self.create_line(xs,ys,xs+1,ys, fill=color)
        
    def plotPixel(self, x, y, color="black"):
    	self.create_line(x,y,x+1,y, fill=color)
    	
    def flush(self):
        self.update_idletasks()
        
    def getMouse(self):
        self.mouseX = None
        self.mouseY = None
        while self.mouseX == None or self.mouseY == None:
            self.update()
        x,y = self.toWorld(self.mouseX, self.mouseY)
        return Point(x,y)
    
    def getHeight(self):
        return self.height
        
    def getWidth(self):
        return self.width
    
    def toScreen(self, x, y):
        trans = self.trans
        if trans:
            return self.trans.screen(x,y)
        else:
            return x,y
                      
    def toWorld(self, x, y):
        trans = self.trans
        if trans:
            return self.trans.world(x,y)
        else:
            return x,y
        
    def setMouseHandler(self, func):
        self._mouseCallback = func
        
    def _onClick(self, e):
        self.mouseX = e.x
        self.mouseY = e.y
        if self._mouseCallback:
            self._mouseCallback(Point(e.x, e.y)) 
                      
class Transform:
    def __init__(self, w, h, xlow, ylow, xhigh, yhigh):
        xspan = (xhigh-xlow)
        yspan = (yhigh-ylow)
        self.xbase = xlow
        self.ybase = yhigh
        self.xscale = xspan/float(w-1)
        self.yscale = yspan/float(h-1)
        
    def screen(self,x,y):
        xs = (x-self.xbase) / self.xscale
        ys = (self.ybase-y) / self.yscale
        return int(xs+0.5),int(ys+0.5)
        
    def world(self,xs,ys):
        x = xs*self.xscale + self.xbase
        y = self.ybase - ys*self.yscale
        return x,y
        
from math import sin, cos, pi

RADFACTOR = pi/180
TWOPI = 2 * pi

class Turtle:
    
    def __init__(self, window):
        self.window = window
        self.angle = 0
        self.image = None
        x,y = window.toWorld(window.width/2, window.height/2)
        if window.trans:
            self.__size = window.trans.xscale*10
        else:
            self.__size = 10
        self.location = Point(x,y)
        self.color = "black"
        self.width = 1
        self.visible = 0
        self.penIsDown = 1
        
    def _redraw(self):
        if self.image and self.visible:
            self.image.undraw()
        self.image = None
        if self.visible:
            tip=self._nextPoint(self.__size)
            image = self.image = Line(self.location, tip)
            image.setArrow("last")
            image.setFill(self.color)
            image.setWidth(self.width)
            image.draw(self.window)
        self.window.update()
    
    def hide(self):
        if self.image and self.visible:
            self.image.undraw()
        self.visible = 0
    
    def show(self):
        self.visible = 1
        if self.image:
            self.image.draw(self.window)
        else:
            self._redraw()
        
    def turn(self, degrees):
        rads = degrees * RADFACTOR
        self.angle = self.angle + rads
        while self.angle > TWOPI:
            self.angle = self.angle - TWOPI
        while self.angle < 0:
            self.angle = self.angle + TWOPI
        self._redraw()
        
    def turnTo(self, degrees):
        rads = degrees * RADFACTOR
        self.angle = rads
        self._redraw()
        
    def setLocation(self, p):
        self.location = p
        self._redraw()
        
    def getLocation(self):
        return self.location
        
    def setWidth(self, width):
        self.width = width
        self._redraw()
        
    def setColor(self, color):
        self.color = color
        self._redraw()
        
    def move(self, units):
        next = self._nextPoint(units)
        if self.penIsDown:
            line = Line(self.location, next)
            line.setOutline(self.color)
            line.setWidth(self.width)
            line.draw(self.window)
        self.location=next
        self._redraw()
        
    def moveTo(self, next):
        if self.penIsDown:
            line = Line(self.location, next)
            line.setOutline(self.color)
            line.setWidth(self.width)
            line.draw(self.window)
        self.location=next
        self._redraw()
        
    def penUp(self):
        self.penIsDown = 0
        
    def penDown(self):
        self.penIsDown = 1     
 
    def _nextPoint(self, dist):
        curr = self.location
        dx = dist*cos(self.angle)
        dy = dist*sin(self.angle)
        return Point(curr.x+dx, curr.y+dy)
            
def color_rgb(r,g,b):
    return "#%02x%02x%02x" % (r,g,b)

# create an invisible main root for all windows
_root = tk.Tk()
_root.withdraw()

def test():
    win = GraphWin()
    win.setCoords(0,0,10,10)
    t = Text(Point(5,5), "Centered Text")
    t.draw(win)
    p = Polygon(Point(1,1), Point(5,3), Point(2,7))
    p.draw(win)
    e = Entry(Point(5,6), 10)
    e.draw(win)
    win.getMouse()
    p.setFill("red")
    p.setOutline("blue")
    p.setWidth(2)
    s = ""
    for pt in p.getPoints():
        s = s + "(%0.1f,%0.1f) " % (pt.getX(), pt.getY())
    t.setText(e.getText())
    e.setFill("green")
    e.setText("Spam!")
    e.move(2,0)
    win.getMouse()
    p.move(2,3)
    s = ""
    for pt in p.getPoints():
        s = s + "(%0.1f,%0.1f) " % (pt.getX(), pt.getY())
    t.setText(s)
    win.getMouse()
    p.undraw()
    e.undraw()
    t.setStyle("bold")
    win.getMouse()
    t.setStyle("normal")
    win.getMouse()
    t.setStyle("italic")
    win.getMouse()
    t.setStyle("bold italic")
    win.getMouse()
    t.setSize(14)
    win.getMouse()
    t.setFace("arial")
    t.setSize(20)
    win.getMouse()
    win.close()

if __name__ == "__main__":
    test()