''' $Revision: 372 $ $Date: 2006-03-05 18:19:39 -0800 (Sun, 05 Mar 2006) $ ''' import copy #import string NUL = 0 # Fill character; ignored on input. ENQ = 5 # Transmit answerback message. BEL = 7 # Ring the bell. BS = 8 # Move cursor left. HT = 9 # Move cursor to next tab stop. LF = 10 # Line feed. VT = 11 # Same as LF. FF = 12 # Same as LF. CR = 13 # Move cursor to left margin or newline. SO = 14 # Invoke G1 character set. SI = 15 # Invoke G0 character set. XON = 17 # Resume transmission. XOFF = 19 # Halt transmission. CAN = 24 # Cancel escape sequence. SUB = 26 # Same as CAN. ESC = 27 # Introduce a control sequence. DEL = 127 # Fill character; ignored on input. SPACE = chr(32) # Space or blank character. def constrain (n, min, max): """This returns n constrained to the min and max bounds. """ if n < min: return min if n > max: return max return n class screen: """This maintains the state of a virtual text screen. This maintains a cursor position and handles scrolling as characters are added. This supports most of the methods needed by an ANSI text screen. Row and column indexes are 1-based (not zero-based, like arrays). """ def __init__ (self, r=24,c=80): self.rows = r self.cols = c self.cur_r = 1 self.cur_c = 1 self.cur_saved_r = 1 self.cur_saved_c = 1 self.scroll_row_start = 1 self.scroll_row_end = self.rows self.w = [ [SPACE] * self.cols for c in range(self.rows)] def __str__ (self): return '\n'.join ([ ''.join(c) for c in self.w ]) def dump (self): """This returns a copy of the screen as a string. This is similar to __str__ except that lines are not terminated with line feeds. """ return ''.join ([ ''.join(c) for c in self.w ]) def fill (self, ch=SPACE): self.fill_region (1,1,self.rows,self.cols, ch) def fill_region (self, rs,cs, re,ce, ch=SPACE): rs = constrain (rs, 1, self.rows) re = constrain (re, 1, self.rows) cs = constrain (cs, 1, self.cols) ce = constrain (ce, 1, self.cols) if rs > re: rs, re = re, rs if cs > ce: cs, ce = ce, cs for r in range (rs, re+1): for c in range (cs, ce + 1): self.put_abs (r,c,ch) def cr (self): '''This moves the cursor to the beginning (col 1) of the current row. ''' self.cursor_home (self.cur_r, 1) def lf (self): '''This moves the cursor down with scrolling. ''' old_r = self.cur_r self.cursor_down() if old_r == self.cur_r: self.scroll_up () self.erase_line() def crlf (self): '''This advances the cursor with CRLF properties. The cursor will line wrap and the screen may scroll. ''' self.cr () self.lf () def newline (self): """This is an alias for crlf(). """ self.crlf() def put_abs (self, r, c, ch): '''Screen array starts at 1 index.''' r = constrain (r, 1, self.rows) c = constrain (c, 1, self.cols) ch = str(ch)[0] self.w[r-1][c-1] = ch def put (self, ch): """This puts a characters at the current cursor position. """ self.put_abs (self.cur_r, self.cur_c, ch) def insert_abs (self, r, c, ch): '''This inserts a character at (r,c). Everything under and to the right is shifted right one character. The last character of the line is lost. ''' r = constrain (r, 1, self.rows) c = constrain (c, 1, self.cols) for ci in range (self.cols, c, -1): self.put_abs (r,ci, self.get_abs(r,ci-1)) self.put_abs (r,c,ch) def insert (self, ch): self.insert_abs (self.cur_r, self.cur_c, ch) def get_abs (self, r, c): r = constrain (r, 1, self.rows) c = constrain (c, 1, self.cols) return self.w[r-1][c-1] def get (self): self.get_abs (self.cur_r, self.cur_c) def get_region (self, rs,cs, re,ce): '''This returns a list of lines representing the region. ''' rs = constrain (rs, 1, self.rows) re = constrain (re, 1, self.rows) cs = constrain (cs, 1, self.cols) ce = constrain (ce, 1, self.cols) if rs > re: rs, re = re, rs if cs > ce: cs, ce = ce, cs sc = [] for r in range (rs, re+1): line = '' for c in range (cs, ce + 1): ch = self.get_abs (r,c) line = line + ch sc.append (line) return sc def cursor_constrain (self): '''This keeps the cursor within the screen area. ''' self.cur_r = constrain (self.cur_r, 1, self.rows) self.cur_c = constrain (self.cur_c, 1, self.cols) def cursor_home (self, r=1, c=1): # [{ROW};{COLUMN}H self.cur_r = r self.cur_c = c self.cursor_constrain () def cursor_back (self,count=1): # [{COUNT}D (not confused with down) self.cur_c = self.cur_c - count self.cursor_constrain () def cursor_down (self,count=1): # [{COUNT}B (not confused with back) self.cur_r = self.cur_r + count self.cursor_constrain () def cursor_forward (self,count=1): # [{COUNT}C self.cur_c = self.cur_c + count self.cursor_constrain () def cursor_up (self,count=1): # [{COUNT}A self.cur_r = self.cur_r - count self.cursor_constrain () def cursor_up_reverse (self): # M (called RI -- Reverse Index) old_r = self.cur_r self.cursor_up() if old_r == self.cur_r: self.scroll_up() def cursor_force_position (self, r, c): # [{ROW};{COLUMN}f '''Identical to Cursor Home.''' self.cursor_home (r, c) def cursor_save (self): # [s '''Save current cursor position.''' self.cursor_save_attrs() def cursor_unsave (self): # [u '''Restores cursor position after a Save Cursor.''' self.cursor_restore_attrs() def cursor_save_attrs (self): # 7 '''Save current cursor position.''' self.cur_saved_r = self.cur_r self.cur_saved_c = self.cur_c def cursor_restore_attrs (self): # 8 '''Restores cursor position after a Save Cursor.''' self.cursor_home (self.cur_saved_r, self.cur_saved_c) def scroll_constrain (self): '''This keeps the scroll region within the screen region.''' if self.scroll_row_start <= 0: self.scroll_row_start = 1 if self.scroll_row_end > self.rows: self.scroll_row_end = self.rows def scroll_screen (self): # [r '''Enable scrolling for entire display.''' self.scroll_row_start = 1 self.scroll_row_end = self.rows def scroll_screen_rows (self, rs, re): # [{start};{end}r '''Enable scrolling from row {start} to row {end}.''' self.scroll_row_start = rs self.scroll_row_end = re self.scroll_constrain() def scroll_down (self): # D '''Scroll display down one line.''' # Screen is indexed from 1, but arrays are indexed from 0. s = self.scroll_row_start - 1 e = self.scroll_row_end - 1 self.w[s+1:e+1] = copy.deepcopy(self.w[s:e]) def scroll_up (self): # M '''Scroll display up one line.''' # Screen is indexed from 1, but arrays are indexed from 0. s = self.scroll_row_start - 1 e = self.scroll_row_end - 1 self.w[s:e] = copy.deepcopy(self.w[s+1:e+1]) def erase_end_of_line (self): # [0K -or- [K '''Erases from the current cursor position to the end of the current line.''' self.fill_region (self.cur_r, self.cur_c, self.cur_r, self.cols) def erase_start_of_line (self): # [1K '''Erases from the current cursor position to the start of the current line.''' self.fill_region (self.cur_r, 1, self.cur_r, self.cur_c) def erase_line (self): # [2K '''Erases the entire current line.''' self.fill_region (self.cur_r, 1, self.cur_r, self.cols) def erase_down (self): # [0J -or- [J '''Erases the screen from the current line down to the bottom of the screen.''' self.erase_end_of_line () self.fill_region (self.cur_r + 1, 1, self.rows, self.cols) def erase_up (self): # [1J '''Erases the screen from the current line up to the top of the screen.''' self.erase_start_of_line () self.fill_region (self.cur_r-1, 1, 1, self.cols) def erase_screen (self): # [2J '''Erases the screen with the background color.''' self.fill () def set_tab (self): # H '''Sets a tab at the current position.''' pass def clear_tab (self): # [g '''Clears tab at the current position.''' pass def clear_all_tabs (self): # [3g '''Clears all tabs.''' pass # Insert line Esc [ Pn L # Delete line Esc [ Pn M # Delete character Esc [ Pn P # Scrolling region Esc [ Pn(top);Pn(bot) r