# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. # """Module to emulate a VT100 terminal in Tkinter. Maintainer: Paul Swartz """ import Tkinter, tkFont import ansi import string ttyFont = None#tkFont.Font(family = 'Courier', size = 10) fontWidth, fontHeight = None,None#max(map(ttyFont.measure, string.letters+string.digits)), int(ttyFont.metrics()['linespace']) colorKeys = ( 'b', 'r', 'g', 'y', 'l', 'm', 'c', 'w', 'B', 'R', 'G', 'Y', 'L', 'M', 'C', 'W' ) colorMap = { 'b': '#000000', 'r': '#c40000', 'g': '#00c400', 'y': '#c4c400', 'l': '#000080', 'm': '#c400c4', 'c': '#00c4c4', 'w': '#c4c4c4', 'B': '#626262', 'R': '#ff0000', 'G': '#00ff00', 'Y': '#ffff00', 'L': '#0000ff', 'M': '#ff00ff', 'C': '#00ffff', 'W': '#ffffff', } class VT100Frame(Tkinter.Frame): def __init__(self, *args, **kw): global ttyFont, fontHeight, fontWidth ttyFont = tkFont.Font(family = 'Courier', size = 10) fontWidth, fontHeight = max(map(ttyFont.measure, string.letters+string.digits)), int(ttyFont.metrics()['linespace']) self.width = kw.get('width', 80) self.height = kw.get('height', 25) self.callback = kw['callback'] del kw['callback'] kw['width'] = w = fontWidth * self.width kw['height'] = h = fontHeight * self.height Tkinter.Frame.__init__(self, *args, **kw) self.canvas = Tkinter.Canvas(bg='#000000', width=w, height=h) self.canvas.pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=1) self.canvas.bind('', self.keyPressed) self.canvas.bind('<1>', lambda x: 'break') self.canvas.bind('', self.upPressed) self.canvas.bind('', self.downPressed) self.canvas.bind('', self.leftPressed) self.canvas.bind('', self.rightPressed) self.canvas.focus() self.ansiParser = ansi.AnsiParser(ansi.ColorText.WHITE, ansi.ColorText.BLACK) self.ansiParser.writeString = self.writeString self.ansiParser.parseCursor = self.parseCursor self.ansiParser.parseErase = self.parseErase #for (a, b) in colorMap.items(): # self.canvas.tag_config(a, foreground=b) # self.canvas.tag_config('b'+a, background=b) #self.canvas.tag_config('underline', underline=1) self.x = 0 self.y = 0 self.cursor = self.canvas.create_rectangle(0,0,fontWidth-1,fontHeight-1,fill='green',outline='green') def _delete(self, sx, sy, ex, ey): csx = sx*fontWidth + 1 csy = sy*fontHeight + 1 cex = ex*fontWidth + 3 cey = ey*fontHeight + 3 items = self.canvas.find_overlapping(csx,csy, cex,cey) for item in items: self.canvas.delete(item) def _write(self, ch, fg, bg): if self.x == self.width: self.x = 0 self.y+=1 if self.y == self.height: [self.canvas.move(x,0,-fontHeight) for x in self.canvas.find_all()] self.y-=1 canvasX = self.x*fontWidth + 1 canvasY = self.y*fontHeight + 1 items = self.canvas.find_overlapping(canvasX, canvasY, canvasX+2, canvasY+2) if items: [self.canvas.delete(item) for item in items] if bg: self.canvas.create_rectangle(canvasX, canvasY, canvasX+fontWidth-1, canvasY+fontHeight-1, fill=bg, outline=bg) self.canvas.create_text(canvasX, canvasY, anchor=Tkinter.NW, font=ttyFont, text=ch, fill=fg) self.x+=1 def write(self, data): #print self.x,self.y,repr(data) #if len(data)>5: raw_input() self.ansiParser.parseString(data) self.canvas.delete(self.cursor) canvasX = self.x*fontWidth + 1 canvasY = self.y*fontHeight + 1 self.cursor = self.canvas.create_rectangle(canvasX,canvasY,canvasX+fontWidth-1,canvasY+fontHeight-1, fill='green', outline='green') self.canvas.lower(self.cursor) def writeString(self, i): if not i.display: return fg = colorMap[i.fg] bg = i.bg != 'b' and colorMap[i.bg] for ch in i.text: b = ord(ch) if b == 7: # bell self.bell() elif b == 8: # BS if self.x: self.x-=1 elif b == 9: # TAB [self._write(' ',fg,bg) for i in range(8)] elif b == 10: if self.y == self.height-1: self._delete(0,0,self.width,0) [self.canvas.move(x,0,-fontHeight) for x in self.canvas.find_all()] else: self.y+=1 elif b == 13: self.x = 0 elif 32 <= b < 127: self._write(ch, fg, bg) def parseErase(self, erase): if ';' in erase: end = erase[-1] parts = erase[:-1].split(';') [self.parseErase(x+end) for x in parts] return start = 0 x,y = self.x, self.y if len(erase) > 1: start = int(erase[:-1]) if erase[-1] == 'J': if start == 0: self._delete(x,y,self.width,self.height) else: self._delete(0,0,self.width,self.height) self.x = 0 self.y = 0 elif erase[-1] == 'K': if start == 0: self._delete(x,y,self.width,y) elif start == 1: self._delete(0,y,x,y) self.x = 0 else: self._delete(0,y,self.width,y) self.x = 0 elif erase[-1] == 'P': self._delete(x,y,x+start,y) def parseCursor(self, cursor): #if ';' in cursor and cursor[-1]!='H': # end = cursor[-1] # parts = cursor[:-1].split(';') # [self.parseCursor(x+end) for x in parts] # return start = 1 if len(cursor) > 1 and cursor[-1]!='H': start = int(cursor[:-1]) if cursor[-1] == 'C': self.x+=start elif cursor[-1] == 'D': self.x-=start elif cursor[-1]=='d': self.y=start-1 elif cursor[-1]=='G': self.x=start-1 elif cursor[-1]=='H': if len(cursor)>1: y,x = map(int, cursor[:-1].split(';')) y-=1 x-=1 else: x,y=0,0 self.x = x self.y = y def keyPressed(self, event): if self.callback and event.char: self.callback(event.char) return 'break' def upPressed(self, event): self.callback('\x1bOA') def downPressed(self, event): self.callback('\x1bOB') def rightPressed(self, event): self.callback('\x1bOC') def leftPressed(self, event): self.callback('\x1bOD')