# -*- coding: iso-8859-1 -*-
import wx
import colorsys
from math import cos, sin, radians, acos, asin, degrees
import time
import profile

class Plotter(object):
    
    def setuptransform(self, gc, transform):
        if transform.getTranslate() != (0,0):
            gc.Translate(*transform.getTranslate())
        (a,b,c,d) = transform.getTransform()
        rads = 0
        if a == 0 and b == -1:
            rads = radians(90)
        elif a == 0 and b == 1:
            rads = radians(270)
        elif a == 1 and b == 0:
            rads = radians(0)
        elif a == -1 and b == 0:
            rads = radians(180)
        else:
            raise Exception()
        if rads:
            gc.Rotate(rads)
            
        
    def drawPath(self, points, gc, transform, method):
        """
        """
        if len(points) < 2:
            raise Exception('At least two points must be specified')
        
        gc.PushState()

        self.setuptransform(gc, transform)
        
        path = gc.CreatePath()

        point = points[0]
        path.MoveToPoint(point[0], point[1])
        
        for point in points[1:]:
            path.AddLineToPoint(point[0], point[1])

        if method == 'DrawPath':
            gc.DrawPath(path)
        elif method == 'FillPath':
            gc.FillPath(path)
        elif method == 'StrokePath':
            gc.StrokePath(path)
        else:
            raise Exception()        

        gc.PopState()

    def drawLine(self, points, gc, transform):
        self.drawPath(points, gc, transform, 'StrokePath')
        
    def drawPolygon(self, points, gc, transform):
        self.drawPath(points, gc, transform, 'DrawPath')
        
    def drawEllipse(self, point, shape, gc, transform):

        gc.PushState()

        self.setuptransform(gc, transform)
        
        gc.DrawEllipse(point[0], point[1], shape[0], shape[1]) 
        
        gc.PopState()
            
    def drawCircle(self, point, diameter, gc, transform):

        gc.PushState()

        self.setuptransform(gc, transform)
        
        gc.DrawEllipse(point[0], point[1], diameter, diameter) 
        
        gc.PopState()

    def drawArc(self, points, gc, transform):
        
        gc.PushState()

        self.setuptransform(gc, transform)
        
        gc.DrawArc(points[0][0], points[1][1], points[1][0], points[1][1], points[2][0], points[2][1]) 
        
        gc.PopState()        
        
    def drawRectangle(self, point, shape, gc, transform):

        gc.PushState()

        self.setuptransform(gc, transform)
        
        gc.DrawRectangle(point[0], point[1], shape[0], shape[1]) 
        
        gc.PopState()        
   
    def drawRoundedRectangle(self, point, shape, radius, gc, transform):

        gc.PushState()
        
        self.setuptransform(gc, transform)

        gc.DrawRoundedRectangle(point[0], point[1], shape[0], shape[1], radius) 
        
        gc.PopState()        

    def drawText(self, text, point, brush, gc, transform):

        gc.PushState()
        
        self.setuptransform(gc, transform)

        gc.DrawText(text, point[0], point[1], gc.CreateBrush(brush)) 
        
        gc.PopState() 
        
class WorldTransformMatix(object):
    def __init__(self, a=1, b=0, c=0, d=-1, x=0,y=0):
        self.a = a
        self.b = b
        self.c = c
        self.d = d 
        self.x = x
        self.y = y
        
    def __str__(self):
        str =  ' -         -    -     -\n'
        str += '|%5.0f %5.0f|  | %5.0f |\n' % (self.a, self.b, self.x)
        str += '|%5.0f %5.0f|  | %5.0f |\n' % (self.c, self.d, self.y)         
        str += ' -         -    -     -'
        return str
    
    def Translate(self, x,y):
        self.x = x
        self.y = y
    
    def getTransform(self):
        return (self.a, self.b, self.c, self.d)
    
    def getTranslate(self):
        return (self.x, self.y)
    
    def PixelToWorld(self, x, y):
        return (x-self.x,-y+self.y)
    
    def WorldToPixel(self, x, y):
        return (x+self.x,-y+self.y)   
    
    def Zoom(self, factor):
        self.a *= factor
        self.b *= factor
        self.c *= factor
        self.d *= factor
        self.x /= factor
        self.y /= factor
        
class Fig(object):
    """
    """
    plotter = Plotter()
    
    def __init__(self, brush=None, pen=None, font=None):
        self.brush = brush
        self.pen = pen
        self.font = font
        
        self.layer = None
        self.resizable = False
        self.locked = False
        self.parent = None
        self.boundingbox = None
        self.selected = False
        self.visible = True
        self.propertychangelistner = None
        self.hitable = False
        self.transform = WorldTransformMatix()

    def getBrush(self):
        return self.__brush
    
    def setBrush(self, brush):
        self.__brush = brush
        
    brush = property(getBrush, setBrush, None)
    
    def getPen(self):
        return self.__pen
    
    def setPen(self, pen):
        self.__pen = pen
        
    pen = property(getPen, setPen, None)
    
    def getLayer(self):
        return self.__layer
    
    def setLayer(self, layer):
        self.__layer = layer
        
    layer = property(getLayer, setLayer, None)
    
    def getVisible(self):
        return self.__visible

    isVisible = getVisible
    
    def setVisible(self, visible):
        self.__visible = visible
    
    visible = property(getVisible, setVisible, None)    

    def getHitable(self):
        return self.__hitable

    def setHitable(self, hitable):
        self.__hitable = hitable

    hitable = property(getHitable, setHitable, None)
    
    def paint(self, context):
        raise Exception('Abstract method')
    
class Grid(Fig):
    def __init__(self, dx,dy, pen):
        Fig.__init__(self, pen=pen)
        self.dx = dx
        self.dy = dy
        
    def paint(self, context):
        
        if self.pen:
            context.SetPen(self.pen)
        for y in range(-1000, 1000, self.dy):
            for x in range(-1000, 1000, self.dx):
                self.transform.Translate(x, y)
                self.plotter.drawLine([(-3,0),(3,0)], context, self.transform) 
                self.plotter.drawLine([(0,-3),(0,3)], context, self.transform)

class Path(Fig):
    def __init__(self, points, pen=None, brush=None, method='DrawPath'):
        Fig.__init__(self, pen=pen, brush=brush)
        self.points = points
        self.method = method
                
    def paint(self, devicecontext):
        if self.pen:
            devicecontext.SetPen(self.pen)
        if self.brush:
            devicecontext.SetBrush(self.brush)
        Fig.plotter.drawPath(self.points, devicecontext, self.transform, self.method)
    
class Line(Path):
    
    def __init__(self, points, pen=None, brush=None):
        Path.__init__(self, points, pen, brush, 'StrokePath')
        
class Polygon(Path):
    
    def __init__(self, points, pen=None, brush=None):
        Path.__init__(self, points, pen, brush, 'DrawPath')

class ShapedFigure(Fig):
    def __init__(self, point, shape, pen=None, brush=None):
        Fig.__init__(self, pen=pen, brush=brush)
        self.point = point
        self.shape = shape

class Rectangle(ShapedFigure):
    def __init__(self, point, shape, pen=None, brush=None):
        ShapedFigure.__init__(self, point=point, shape=shape, pen=pen, brush=brush)
        
    def paint(self, devicecontext):
        if self.pen:
            devicecontext.SetPen(self.pen)
        if self.brush:
            devicecontext.SetBrush(self.brush)
        Fig.plotter.drawRectangle(self.point, self.shape, devicecontext, self.transform)

class RoundedRectangle(ShapedFigure):
    def __init__(self, point, shape, radius, pen=None, brush=None):
        ShapedFigure.__init__(self, point=point, shape=shape, pen=pen, brush=brush)
        self.radius = radius
        
    def paint(self, devicecontext):
        if self.pen:
            devicecontext.SetPen(self.pen)
        if self.brush:
            devicecontext.SetBrush(self.brush)
        Fig.plotter.drawRoundedRectangle(self.point, self.shape, self.radius, devicecontext, self.transform)
            
class Ellipse(ShapedFigure):
    def __init__(self, point, shape, pen=None, brush=None):
        ShapedFigure.__init__(self, point=point, shape=shape, pen=pen, brush=brush)
        
    def paint(self, devicecontext):
        if self.pen:
            devicecontext.SetPen(self.pen)
        if self.brush:
            devicecontext.SetBrush(self.brush)
        Fig.plotter.drawEllipse(self.point, self.shape, devicecontext, self.transform)

class Circle(Ellipse):
    def __init__(self, point, diameter, pen=None, brush=None):
        Ellipse.__init__(self, point, (diameter, diameter), pen, brush)
            

class Arc(Fig):
    def __init__(self, points, pen=None, brush=None):
        Fig.__init__(self, pen = pen, brush = brush)
        self.points = points
        

    def paint(self, devicecontext):
        if self.pen:
            devicecontext.SetPen(self.pen)
        if self.brush:
            devicecontext.SetBrush(self.brush)
        Fig.plotter.drawArc(self.points, devicecontext, self.transform)

        
class Text(Fig):
    def __init__(self, text, point, angle, pen=None, brush=None, font=None):
        Fig.__init__(self, pen=pen, brush=brush, font=font)
        self.text = text
        self.point = point
        self.angle = angle

    def paint(self, devicecontext):
        if self.pen:
            devicecontext.SetPen(self.pen)
        if self.brush:
            devicecontext.SetBrush(self.brush)
        if self.font:
            devicecontext.SetFont(self.font)
        Fig.plotter.drawText(self.text, self.point, self.brush, devicecontext, self.transform)
            
class Layer(object):
    def __init__(self, name):
        self.name = name
        self.paintobjects = []
        self.visible = True
        
    def add(self, object):
        self.paintobjects.append(object)
        object.layer = self
        
    def remove(self, object):
        self.paintobjects.remove(object)
        object.layer = None
        
    def paint(self, context):
        for object in self.paintobjects:
            if object.isVisible():
                object.paint(context)
                       
class LayerManager(object):
    def __init__(self):
        self.layers = []
        
    def addlayer(self, layer, index=None):
        if index is None:
            self.layers.append(layer)
        else:
            self.layers.insert(index, layer)
    
    def moveback(self, object):
        layerindex = self.layers.index(object.layer)
        if layerindex > 0:
            self.layers[layerindex-1].add(object)

    def moveforward(self, object):
        layerindex = self.layers.index(object.layer)
        if layerindex < len(self.layers):
            self.layers[layerindex+1].add(object)
        
    def paint(self, context):
        for layer in self.layers:
            if layer.visible:
                layer.paint(context)
            
class pyGEFPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1, size = (600,600))

        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_MOTION, self.OnMouseMotion)
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_LEFT_DOWN, self.OnMousePressed)
        self.Bind(wx.EVT_RIGHT_DOWN, self.OnMousePressed)

        self.drawobjects = []
        self.layermanager = LayerManager()
        self.transform = WorldTransformMatix(1,0,0,1)

        self.buffer = None
        self.start = (0,0)

    def OnMousePressed(self, event):
        if event.LeftIsDown():
            self.start = self.transform.PixelToWorld(*event.GetPosition())
        if event.RightIsDown():
            self.transform.Zoom(1.5)
            self.Draw(True)
            
    def OnMouseMotion(self, event):
        pos =  event.GetPosition()
        world = self.transform.PixelToWorld(*pos)
        pos = self.transform.WorldToPixel(*world)

        if event.LeftIsDown():
            translate = self.transform.getTranslate()
            dxy = (world[0] - self.start[0], world[1] - self.start[1])
            self.transform.Translate(translate[0]+dxy[0], translate[1]-dxy[1])
            self.start = self.transform.PixelToWorld(*event.GetPosition())
            self.Draw(True)

    def OnSize(self, event):  
        self.transform.Translate(self.GetSize()[0]/2, self.GetSize()[1]/2)
        self.Draw(True)
                   
    def OnPaint(self, evt):
        start = time.clock()        
        dc = wx.PaintDC(self)
        if self.buffer:
            dc.DrawBitmap(self.buffer,0,0)
        print "OnPaint took %f seconds of CPU time"%(time.clock()-start)        

    def Draw(self, force=False):
        start = time.clock()
        ScreenDC =  wx.ClientDC(self)

        self.buffer = wx.EmptyBitmap(*self.GetSize())

        dc = wx.MemoryDC()
        dc.SelectObject(self.buffer)

        gc = wx.GraphicsContext.Create(dc)

        gm = gc.CreateMatrix()
        gm.Set(*(self.transform.getTransform()+(self.transform.getTranslate())))
        gc.SetTransform(gm)
        
        dc.BeginDrawing()
        self.layermanager.paint(gc)        

        ScreenDC.Blit(0, 0, self.GetSize()[0], self.GetSize()[1], dc, 0, 0)
        dc.EndDrawing()
        print "Drawing took %f seconds of CPU time"%(time.clock()-start)
                
class App(wx.App):
    def OnInit(self):
        wx.InitAllImageHandlers()
        self.main = wx.Frame(None,-1,'', size=(800,800))
        self.sz3s = wx.BoxSizer(wx.VERTICAL)
        self.main.SetSizer(self.sz3s);
        panel = pyGEFPanel(self.main)
        self.sz3s.Add(panel, 1, wx.EXPAND)
        
        gridlayer = Layer('grid')
        panel.layermanager.addlayer(gridlayer)
        gridlayer.add(Grid(50,50, wx.Pen("grey", 1)))
        
        crosslayer = Layer('cross')
        panel.layermanager.addlayer(crosslayer)

        l1 = Layer('bottom')
        panel.layermanager.addlayer(l1)
        l2 = Layer('top')
        panel.layermanager.addlayer(l2)

        
        cross1 = Line([(-1000,0),(1000,0)], pen=wx.Pen("grey", 1), brush=wx.Brush(wx.Colour(0,  0,  255, 64)))
        cross2 = Line([(0,-1000),(0,1000)], pen=wx.Pen("white", 1), brush=wx.Brush(wx.Colour(0,  0,  255, 64)))
        
        crosslayer.add(cross1)
        crosslayer.add(cross2)
        
        for x in range(-300, 300, 10):
            line = Line([(-50,-75),(50,-75), (50,25), (0,75), (-50,25), (-50, -75)], pen=wx.Pen("blue", 3), brush=wx.Brush(wx.Colour(0,  0,  255, 128)))
            line.transform.Translate(x,x)
            
            circle = Circle((-100,-100), 200, pen=wx.Pen("red", 1), brush=wx.Brush(wx.Colour(178,  34,  34, 128), style=wx.FDIAGONAL_HATCH))
            circle.transform.Translate(x,x)
    
            poly = Polygon([(-50,-25),(0,25), (50,25), (50,-25), (-50,-25)], pen=wx.Pen("blue", 3), brush=wx.Brush(wx.Colour(0,  0,  255, 128),style=wx.SOLID))
            poly.transform.Translate(200+x,x)
            
            rec = Rectangle((-25,-25), (50,50), pen=wx.Pen("green", 3), brush=wx.Brush(wx.Colour(0,  255,  0, 128), style=wx.FDIAGONAL_HATCH))
            rec.transform.Translate(100+x,100+x)
    
            rrec = RoundedRectangle((-50,-50), (100,100), 10, pen=wx.Pen("yellow", 3), brush=wx.Brush(wx.Colour(255, 255, 0,128), style=wx.CROSSDIAG_HATCH))
            rrec.transform.Translate(-100+x,100+x)
    
            #arc = Arc([(0,0), (200,0), (-200,0)], pen=wx.Pen("purple", 3), brush=wx.Brush(wx.Colour(255, 0, 255, 64)))
    
            font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
            font.SetWeight(wx.BOLD)
            text = Text('Hello', (0,0), 0, pen=wx.Pen("white", 1), brush=wx.Brush(wx.Colour(1,  1,  1, 1)), font=font)
            text.transform.Translate(x,x)
    
            l2.add(line)
            l1.add(circle)
            l2.add(poly)
            l2.add(text)
            l2.add(rec)
            l2.add(rrec)

        self.main.Show(True)
        self.main.SetAutoLayout(1)
        self.main.Layout();

        return 1

def main():
    application = App(0)
    application.MainLoop()

if __name__ == '__main__':
    profile.run('main()')
