Пример #1
0
class GTKGui (_SketchGUI, gtk.DrawingArea):
    def __init__(self, dims = (WIDTH, HEIGHT) ):
        # Create a new window
        gtk.DrawingArea.__init__(self)
        self.resize(*dims)# BREAKS when X forwarding

        #Semantic board data
        self.board = None # set by resetBoard()
        self.boardProcThread = BoardThread(self.board) # set by resetBoard()

        #GUI data variables
        self.shouldDrawStrokes = True
        self.currentPoints = None # set by resetBoard()
        self.strokeList = None # set by resetBoard()
        self.opQueue = None # set by resetBoard()

        self.isMouseDown1 = False
        self.isMouseDown3 = False
        self.keyCallbacks = {}
        self.strokeQueue = ProcQueue()
        self.strokeLoader = StrokeStorage()
        self.screenImage = None
        self._pixbuf = None
        self._isFullscreen = False
        #Cairo drawing data
        self.renderBuffer = None
        self.context = None 



        #Event hooks
        gobject.idle_add(self.processOps) #Idle event
        gobject.idle_add(self.processQueuedStrokes) #Async stroke processing
        self.set_property("can-focus", True) #So we can capture keyboard events
        self.connect("button_press_event", self.onMouseDown)
        self.connect("motion_notify_event", self.onMouseMove)
        self.connect("button_release_event", self.onMouseUp)
        self.connect("key_press_event", self.onKeyPress)
        self.connect("expose_event", self.onExpose)
        self.set_events(gtk.gdk.BUTTON_RELEASE_MASK | 
                        gtk.gdk.BUTTON_PRESS_MASK |
                        gtk.gdk.KEY_PRESS_MASK |
                        gtk.gdk.EXPOSURE_MASK |
                        gtk.gdk.POINTER_MOTION_MASK )

        #Enable the board processing
        self.resetBoard()


    def getDimensions(self):
        "Return the (width, height) of the visible board area"
        return self.window.get_size()

    def post(self, op):
        self.opQueue.put(op)

    def registerKeyCallback(self, keyVal, function):
        """Register a function to be called when
        a certain keyVal is pressed"""
        log.debug("Registered function for %s" % (keyVal))
        self.keyCallbacks.setdefault(keyVal, []).append(function)

    def onKeyPress(self, widget, event, data=None):
        key = chr(event.keyval % 256)
        #Run the registered callbacks
        for func in self.keyCallbacks.get(key, ()):
            func()
        
        #Run the hard-coded key events
        if key == 'r':
            self.resetBoard()
        elif key == 'i':
            def ok_callback(fileSelector):
                fname = fileSelector.get_filename()
                fileSelector.destroy()
                self.loadStrokesFromImage(filename=fname)

            fileSelector = gtk.FileSelection("Choose a photo")
            fileSelector.set_filename("./photos/")

            fileSelector.ok_button.connect("clicked", 
                                      (lambda w: ok_callback(fileSelector)) )
            fileSelector.cancel_button.connect("clicked", 
                                      (lambda w: fileSelector.destroy()) )
                                      
            fileSelector.show()
        elif key == 'l':
            self.loadStrokes()
        elif key == 's':
            self.saveStrokes()
        elif key == 'f':
            self.setFullscreen(not self._isFullscreen)
        elif key == 'q':
            gtk.main_quit()
        elif key in ('1','0','-','`',' '):
            self.controlTuringMachines(key)
            self.boardChanged()
        elif key == 'D':
            self.shouldDrawStrokes = not self.shouldDrawStrokes
            if self.shouldDrawStrokes:
                print ("Show Strokes")
            else:
                print("Don't Show Strokes")
            self.draw()


    def controlTuringMachines(self, key):
        """Keyboard bindings specifically hard-coded for controlling 
        any present Turing Machine annotations"""
        tmAnnos = self.board.FindAnnotations(anno_type = TuringMachineAnnotation)
        if key in ['1', '0', '-']:
            for anno in tmAnnos:
                tapeString = anno.getTapeString()
                tapeString+= key
                anno.setTapeString(tapeString)
        elif key == '`':
            fp = open("TuringMachines.dot", "a")
            for anno in tmAnnos:
                print >> fp, anno.dotify()
                anno.setTapeString("")
                anno.restartSimulation()
            fp.close()
        elif key == ' ':
            for anno in tmAnnos:
                anno.simulateStep()
            
    def resetBackBuffer(self):
        """Reset the back painting buffer, for example when the screen
        size changes"""
        x,y,w,h = self.allocation
        log.debug("Reset back buffer %sx%s" % (w,h))
        self.renderBuffer = cairo.ImageSurface(cairo.FORMAT_ARGB32, w,h)
        self.context = cairo.Context(self.renderBuffer)
        #self.screenImage = None

        
    def setFullscreen(self, makeFull):
        """Set the application fullscreen according to makeFull(boolean)"""
        windows = gtk.window_list_toplevels()
#        for (i, win) in enumerate(windows):
#            print i, win.name, "Has focus: %s" % (win.has_toplevel_focus())
#        win = windows[0]
        self._pixbuf = None
        if makeFull:
            self.screenImage = None
            self._isFullscreen = True
            log.debug("Fullscreen")
            for win in windows:
                if self in win.children():
                    win.fullscreen()
            self.opQueue.put(lambda : time.sleep(0.1)) # So we don't reset too early
            self.opQueue.put(lambda : self.resetBackBuffer())
        else:
            self.screenImage = None
            self._isFullscreen = False
            log.debug("UNFullscreen")
            for win in windows:
                if self in win.children():
                    win.unfullscreen()
            self.opQueue.put(lambda : time.sleep(0.1))
            self.opQueue.put(lambda : self.resetBackBuffer())

    def resetBoard(self):
        log.debug("Resetting Board")
        self.opQueue = Queue.Queue()
        self.board = Board(gui = self)
        self.strokeList = []
        self.currentPoints = []
        Config.initializeBoard(self.board)
        self.boardProcThread.stop()
        self.boardProcThread = BoardThread(self.board)
        self.boardProcThread.start()
        self.opQueue.put(lambda : time.sleep(0.1)) # So we don't reset too early
        self.opQueue.put(lambda : self.resetBackBuffer())
        self.opQueue.put(lambda : self.boardChanged())

    def boardChanged(self):
        if self.strokeQueue.empty(): #No strokes waiting to be processed
            if self.board.Lock.acquire(False): #Nobody's using the board
                self.board.Lock.release()
                self.draw()
               
    def loadStrokesFromImage(self, filename=None, image=None):
        width, height = self.window.get_size()
        if image is None:
            if filename is None:
                return
            else:
                image = ImageStrokeConverter.loadFile(filename)

        p = Process(target = processImage,
                    args = (image, self.strokeQueue, (width,height) )
                   )
        p.start()
 
    def loadStrokes(self):
        for stroke in self.strokeLoader.loadStrokes():
            self.addStroke(stroke)

    def saveStrokes(self):
        self.strokeLoader.saveStrokes(self.strokeList)
        with self.board.Lock:
            print ElementTree.tostring(self.board.xml(1280, 720))


    def drawCircle(self, *args, **kargs):
        """Draw a circle on the canvas at (x,y) with radius rad. Color should be
        24 bit RGB string #RRGGBB. Empty string is transparent"""
        op = partial(GTKGui._drawCircle, self, *args, **kargs)
        self.opQueue.put(op)

    def drawText(self, *args, **kargs):
        """Draw some text (InText) on the canvas at (x,y). Color as defined by 24
        bit RGB string #RRGGBB"""
        op = partial(GTKGui._drawText, self, *args, **kargs)
        self.opQueue.put(op)

    def drawLine(self, x1, y1, x2, y2, width=2, color="#FFFFFF"):
        """Draw a line on the canvas from (x1,y1) to (x2,y2). Color should be 24
        bit RGB string #RRGGBB"""
        op = partial(GTKGui._drawLine, self, x1, y1, x2, y2, width, color)
        self.opQueue.put(op)

    def drawStroke(self, *args, **kargs):
        """Draw a stroke on the board with width and color as specified."""
        op = partial(GTKGui._drawStroke, self, *args, **kargs)
        self.opQueue.put(op)

    def drawCurve(self, *args, **kargs):
        "Draw a curve on the board with width and color as specified"
        op = partial(GTKGui._drawCurve, self, *args, **kargs)
        self.opQueue.put(op)

    def drawBox(self, *args, **kargs):
        op = partial(GTKGui._drawBox, self, *args, **kargs)
        self.opQueue.put(op)
        
    def drawBitmap(self, x, y, filename=None, pixbuf=None):
        try:
            if pixbuf is None and filename is not None:
                pixbuf = gtk.gdk.pixbuf_new_from_file(filename)
            elif pixbuf is None:
                raise Exception("Must specify filename or pixbuf")
            else:
                op = partial(GTKGui._drawBitmap, self, x, y, pixbuf)
                self.opQueue.put(op)
        except Exception as e:
            log.warn(str(e))

    #________________________________________
    #  Private, actual draw calls
    #________________________________________

    def _drawCircle(self, x, y, radius=1, color="#FFFFFF", fill="", width=1.0):
        """Draw a circle on the canvas at (x,y) with radius rad. Color should be
        24 bit RGB string #RRGGBB. Empty string is transparent"""
        self.context.save()
        #Draw the line
        pt = self.b2c(Point(x,y))
        self.context.arc(pt.X, pt.Y, radius, 0, math.pi * 2)
        if fill != "":
            self.context.set_source_rgb(* hexToTuple(fill) )
            self.context.fill_preserve()
        #c = hexToTuple(color)
        self.context.set_source_rgb(*hexToTuple(color))
        self.context.stroke()
        self.context.restore()
         
    def _drawLine(self, x1, y1, x2, y2, width, color, _context=None):
        """Draw a line on the canvas from (x1,y1) to (x2,y2). Color should be 24
        bit RGB string #RRGGBB"""
        if _context is None:
            context = self.context
        else:
            context = _context

        context.save()
        #Draw the line
        c = hexToTuple(color)
        context.set_source_rgb(*c)
        p1 = self.b2c(Point(x1, y1))
        p2 = self.b2c(Point(x2, y2))
        context.move_to( p1.X, p1.Y )
        context.line_to( p2.X, p2.Y )
        context.stroke()
        context.restore()
         
    def _drawText (self, x, y, InText="", size=10, color="#FFFFFF"):
        """Draw some text (InText) on the canvas at (x,y). Color as defined by 24
        bit RGB string #RRGGBB"""
        self.context.save()
        ctxt = pangocairo.CairoContext(self.context)
        layout = ctxt.create_layout()
        layout.set_text(InText)
        c = hexToTuple(color)
        self.context.set_source_rgb(*c)
        _, (tlx, tly, brx, bry) = layout.get_pixel_extents()
        pt = Point(x, y)
        #pt = self.b2c(Point(x, y - bry))
        pt = self.b2c(pt)
        ctxt.translate(pt.X, pt.Y)
        ctxt.show_layout(layout)
        self.context.restore()

    # ------------------------------------------------
    #      Optional overloads
    def _drawBox(self, tl, br, topright = None, 
                bottomleft = None, color="#FFFFFF", fill = "", width=2):
        self.context.save()
        tl = self.b2c(tl)
        br = self.b2c(br)

        x = tl.X
        y = tl.Y
        w = br.X - tl.X
        h = br.Y - tl.Y

        self.context.set_source_rgb(* hexToTuple(color))
        self.context.set_line_width(width)
        self.context.rectangle(x,y,w,h)
        if fill != "":
            self.context.set_source_rgb(* hexToTuple(fill))
            self.context.fill_preserve()
        self.context.stroke()
        self.context.restore()
    
    def _drawStroke(self, stroke, width = 2, color="#FFFFFF", erasable = False):
        """Draw a stroke on the board with width and color as specified."""
        self.context.save()
        #Draw the lines
        c = hexToTuple(color)
        self.context.set_source_rgb(*c)
        if len(stroke.Points) > 0:
            pt = self.b2c(stroke.Points[0])
            self.context.move_to( pt.X, pt.Y )
            if len(stroke.Points) > 1:
                for pt in stroke.Points[1:]:
                    pt = self.b2c(pt)
                    self.context.line_to( pt.X, pt.Y)
            else:
                pass
        self.context.stroke()
        self.context.restore()
    
    def _drawBitmap(self, x, y, pixbuf):
        """Draw a bitmap on the board"""
        self.context.save()
        pt = self.b2c(Point(x,y))
        ctxt2 = gtk.gdk.CairoContext(self.context)
        ctxt2.set_source_pixbuf(pixbuf, pt.X, pt.Y)
        ctxt2.paint()
        self.context.restore()        
        
    def _getContext(self):
        return self.window.cairo_create()

    def resize(self, w,h):
        """Set the size of the canvas to w x h"""
        self.set_size_request(w,h)

    def processQueuedStrokes(self):
        if not self.strokeQueue.empty():
            log.debug("Adding queued stroke")
            stroke = self.strokeQueue.get()
            self.strokeList.append(stroke)
            self.boardProcThread.addStroke(stroke, callback = self.boardChanged)
        return True

    def processOps(self):
        """Process one operation from the opqueue"""
        if not self.opQueue.empty():
            op = self.opQueue.get() #Call the next operation in the queue
            op()
            self.opQueue.task_done()
        return True #Call me again next idle time

    def onMouseDown(self, widget, e):
        """Respond to a mouse being pressed"""
        if e.button == 1:
            self.isMouseDown1 = True
            self.currentPoints.append(self.b2c(Point(e.x, e.y)))
            return True
        elif e.button == 3:
            self.isMouseDown3 = True
            self.currentPoints.append(self.b2c(Point(e.x, e.y)))
            return True

    def onMouseMove(self, widget, e):
        """Respond to the mouse moving"""
        if self.isMouseDown1:
            p = self.currentPoints[-1]
            curPt = self.b2c(Point(e.x, e.y))
            self.currentPoints.append(curPt)
            liveContext = self._getContext()
            self._drawLine(p.X, p.Y, curPt.X, curPt.Y, 
                            2, "#ffffff", _context= liveContext)
            return True
        elif self.isMouseDown3:
            p = self.currentPoints[-1]
            curPt = self.b2c(Point(e.x, e.y))
            self.currentPoints.append(curPt)
            liveContext = self._getContext()
            self._drawLine(p.X, p.Y, curPt.X, curPt.Y, 
                            2, "#c00c0c", _context = liveContext)
            return True
    
    def onMouseUp(self, widget, e):
        """Respond to the mouse being released"""
        if e.button == 1:
            self.isMouseDown1 = False
            curPt = self.b2c(Point(e.x, e.y))
            self.currentPoints.append(curPt)
            stroke = Stroke( self.currentPoints)
            self.currentPoints = []
            self.addStroke(stroke)
            #self.opQueue.put(partial(GTKGui.addStroke, self, stroke))
            #self.draw()
            return True
        elif e.button == 3:
            self.isMouseDown3 = False
            curPt = self.b2c(Point(e.x, e.y))
            self.currentPoints.append(curPt)
            stroke = Stroke( self.currentPoints)
            self.currentPoints = []
            shouldRedraw = True
            for stk in list(self.strokeList):
                if len(getStrokesIntersection(stroke, stk) ) > 0:
                    self.eraseStroke(stk)
                    shouldRedraw = False
            if shouldRedraw:
                #pass
                self.draw()
            return True

    def addStroke(self, stroke):
        """Add as stroke to the board and our local bookkeeping"""
        self.strokeQueue.put(stroke)
#        self.boardProcThread.addStroke(stroke, callback = self.boardChanged)

    def eraseStroke(self, stroke):
        """Remove a stroke from the board and our local lists"""
        self.strokeList.remove(stroke)
        self.boardProcThread.removeStroke(stroke, callback = self.boardChanged)
        

    def onExpose(self, widget, e):
        """Respond to the window being uncovered"""
        log.debug("Expose")
        if self.screenImage is not None:
            self.window.draw_pixbuf(None, self.screenImage, 0,0, 0,0)
        else:
            self.draw()

    def clearBoard(self, bgColor="#000000"):
        """Erase the contents of the board"""
        log.debug("Clear")
        self.context.save()
        c = hexToTuple(bgColor)
        self.context.set_source_rgb(*c)
        rect = self.get_allocation()
        self.context.rectangle(rect.x, rect.y, rect.width, rect.height)
        self.context.fill()
        self.context.restore()
        
    def draw(self):
        """Draw the board"""
        log.debug("Redraw")
        self.opQueue.put(partial(GTKGui.clearBoard, self))
        with self.board.Lock:
            if self.shouldDrawStrokes:
                for stk in self.board.Strokes:
                    stk.drawMyself()
            for obs in self.board.BoardObservers:
                obs.drawMyself()
        self.drawStroke(Stroke(self.currentPoints))
        self.doPaint()

    def doPaint(self):
        """A method to commit the current queue of draw events to 
        the screen"""
        self.opQueue.put(partial( GTKGui.flipContext, self) )
        self.opQueue.put(partial( GTKGui._updateScreenImage, self) )
        
    def flipContext(self):
        """Render the drawn surface to the screen"""
        log.debug("Flip image to surface")
        bufferToPaint = self.renderBuffer
        (nw, nh) = bufferToPaint.get_width(), bufferToPaint.get_height()
        log.debug(" Current buffer: %sx%s" % (nw, nh) )
        #Set up a new buffer to paint from
        self.resetBackBuffer()
        #Paint the render buffer to the live context
        liveContext = self._getContext()
        liveContext.set_source_surface(bufferToPaint)
        liveContext.paint()

    def getScreenShot(self):
        width, height = self.window.get_size()

        #if self._pixbuf is None:
        log.debug("  pixbuf size: %sx%s" % (width, height))
        _pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8,
                                width, height)

        #BREAKING
        print os.getpid()
        screenImage = _pixbuf.get_from_drawable(self.window, 
                                        self.window.get_colormap(), 
                                        0, 0, 0, 0, width, height)
     
        retImage = cv.CreateImageHeader( (screenImage.get_width(), screenImage.get_height()), cv.IPL_DEPTH_8U, 3)
        cv.SetData(retImage, screenImage.get_pixels())
        retImage = cv.GetMat(retImage)

        return retImage

    def _updateScreenImage(self):
        """Update the image of the screen we're dealing with"""
        width, height = self.window.get_size()        
        log.debug("Update Screenshot")
        _pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8,
                                width, height)
        self.screenImage = _pixbuf.get_from_drawable(self.window, 
                                        self.window.get_colormap(), 
                                        0, 0, 0, 0, width, height)        
        self.screenImage.save('screenshot.png', 'png')
        
#        saveimg(self.getScreenShot())

        
    def b2c(self, pt):
        """Converts bottom-left origin Board coordinates to raw canvas coords
        and back"""
        rect = self.get_allocation()
        return Point(pt.X, rect.height - pt.Y)