# HG changeset patch # User Radek Brich # Date 1394891027 -3600 # Node ID 04dfb5ddf0313908c7ae62e99f595496a1da11a9 # Parent ebe732b9ef1968a9cd3188e04b97c4bf6b35c510 Refactor Driver, CursesDriver: draw, setattr, input. diff -r ebe732b9ef19 -r 04dfb5ddf031 demos/02_curses.py --- a/demos/02_curses.py Sat Mar 15 11:05:12 2014 +0100 +++ b/demos/02_curses.py Sat Mar 15 14:43:47 2014 +0100 @@ -18,7 +18,9 @@ driver = CursesDriver() with driver: driver.draw(buffer) + buffer.setattr('red on blue, bold') + buffer.puts(8, 4, 'Hello!') driver.draw(buffer, 20, 10) - driver.stdscr.refresh() - driver.stdscr.getch() + driver.flush() + driver.getevents() diff -r ebe732b9ef19 -r 04dfb5ddf031 tuikit/driver/curses.py --- a/tuikit/driver/curses.py Sat Mar 15 11:05:12 2014 +0100 +++ b/tuikit/driver/curses.py Sat Mar 15 14:43:47 2014 +0100 @@ -8,17 +8,18 @@ class CursesDriver(Driver): + key_codes = ( (0x09, 'tab' ), (0x0a, 'enter' ), (0x7f, 'backspace' ), (0x1b, 'escape' ), - (0x1b,0x4f,0x50, 'f1' ), # xterm - (0x1b,0x4f,0x51, 'f2' ), # xterm - (0x1b,0x4f,0x52, 'f3' ), # xterm - (0x1b,0x4f,0x53, 'f4' ), # xterm - (0x1b,0x5b, 'CSI' ), # see csi_codes - (0x1b,0x5b,0x4d, 'mouse' ), + (0x1b, 0x4f, 0x50, 'f1' ), # xterm + (0x1b, 0x4f, 0x51, 'f2' ), # xterm + (0x1b, 0x4f, 0x52, 'f3' ), # xterm + (0x1b, 0x4f, 0x53, 'f4' ), # xterm + (0x1b, 0x5b, 'CSI' ), # see csi_codes + (0x1b, 0x5b, 0x4d, 'mouse' ), ) # http://en.wikipedia.org/wiki/ANSI_escape_code @@ -44,41 +45,41 @@ (0x44, 1, 'left' ), (0x46, 1, 'end' ), # xterm (0x48, 1, 'home' ), # xterm - (0x5b,0x41, 1, 'f1' ), # linux - (0x5b,0x42, 1, 'f2' ), # linux - (0x5b,0x43, 1, 'f3' ), # linux - (0x5b,0x44, 1, 'f4' ), # linux - (0x5b,0x45, 1, 'f5' ), # linux + (0x5b, 0x41, 1, 'f1' ), # linux + (0x5b, 0x42, 1, 'f2' ), # linux + (0x5b, 0x43, 1, 'f3' ), # linux + (0x5b, 0x44, 1, 'f4' ), # linux + (0x5b, 0x45, 1, 'f5' ), # linux ) color_map = { - 'black' : (curses.COLOR_BLACK, 0), - 'blue' : (curses.COLOR_BLUE, 0), - 'green' : (curses.COLOR_GREEN, 0), - 'cyan' : (curses.COLOR_CYAN, 0), - 'red' : (curses.COLOR_RED, 0), - 'magenta' : (curses.COLOR_MAGENTA,0), - 'brown' : (curses.COLOR_YELLOW, 0), - 'lightgray' : (curses.COLOR_WHITE, 0), - 'gray' : (curses.COLOR_BLACK, curses.A_BOLD), - 'lightblue' : (curses.COLOR_BLUE, curses.A_BOLD), - 'lightgreen' : (curses.COLOR_GREEN, curses.A_BOLD), - 'lightcyan' : (curses.COLOR_CYAN, curses.A_BOLD), - 'lightred' : (curses.COLOR_RED, curses.A_BOLD), - 'lightmagenta' : (curses.COLOR_MAGENTA,curses.A_BOLD), - 'yellow' : (curses.COLOR_YELLOW, curses.A_BOLD), - 'white' : (curses.COLOR_WHITE, curses.A_BOLD), + 'default': (-1, 0), + 'black': (curses.COLOR_BLACK, 0), + 'blue': (curses.COLOR_BLUE, 0), + 'green': (curses.COLOR_GREEN, 0), + 'cyan': (curses.COLOR_CYAN, 0), + 'red': (curses.COLOR_RED, 0), + 'magenta': (curses.COLOR_MAGENTA, 0), + 'brown': (curses.COLOR_YELLOW, 0), + 'lightgray': (curses.COLOR_WHITE, 0), + 'gray': (curses.COLOR_BLACK, curses.A_BOLD), + 'lightblue': (curses.COLOR_BLUE, curses.A_BOLD), + 'lightgreen': (curses.COLOR_GREEN, curses.A_BOLD), + 'lightcyan': (curses.COLOR_CYAN, curses.A_BOLD), + 'lightred': (curses.COLOR_RED, curses.A_BOLD), + 'lightmagenta': (curses.COLOR_MAGENTA, curses.A_BOLD), + 'yellow': (curses.COLOR_YELLOW, curses.A_BOLD), + 'white': (curses.COLOR_WHITE, curses.A_BOLD), } attr_map = { - 'bold' : curses.A_BOLD, - 'underline' : curses.A_UNDERLINE, - 'standout' : curses.A_STANDOUT, # inverse bg/fg - 'blink' : curses.A_BLINK, + 'bold': curses.A_BOLD, + 'underline': curses.A_UNDERLINE, + 'standout': curses.A_STANDOUT, # inverse bg/fg + 'blink': curses.A_BLINK, } def __init__(self): - '''Set driver attributes to default values.''' Driver.__init__(self) self.log = logging.getLogger('tuikit') self.stdscr = None @@ -97,6 +98,7 @@ """Initialize curses""" self.stdscr = curses.initscr() curses.start_color() + curses.use_default_colors() curses.noecho() curses.cbreak() self.stdscr.keypad(0) @@ -114,80 +116,10 @@ curses.nocbreak() curses.endwin() - def __enter__(self): - self.init() - - def __exit__(self, exc_type, exc_val, exc_tb): - self.close() - ## drawing ## - def draw(self, buffer, x=0, y=0): - for bufy in range(buffer.size.h): - for bufx in range(buffer.size.w): - self.putch(x + bufx, y + bufy, - buffer.get(bufx, bufy)[0]) - - - ## colors, attributes ## - - def _parsecolor(self, name): - name = name.lower().strip() - return self.color_map[name] - - def _getcolorpair(self, fg, bg): - pair = (fg, bg) - if pair in self.colorpairs: - return self.colorpairs[pair] - num = len(self.colorpairs) + 1 - curses.init_pair(num, fg, bg) - self.colorpairs[pair] = num - return num - - def _parseattrs(self, attrs): - res = 0 - for a in attrs: - a = a.lower().strip() - res = res | self.attr_map[a] - return res - - def defcolor(self, name, desc): - """Define color name.""" - parts = desc.split(',') - fgbg = parts[0].split(' on ', 1) - fg = fgbg[0] - bg = fgbg[1:] and fgbg[1] or 'black' - attrs = parts[1:] - fg, fgattr = self._parsecolor(fg) - bg, _bgattr = self._parsecolor(bg) - col = self._getcolorpair(fg, bg) - attr = self._parseattrs(attrs) - self.colors[name] = curses.color_pair(col) | fgattr | attr - - def setcolor(self, name): - """Set defined color. Previous color is forgotten.""" - self.stdscr.attrset(self.colors[name]) - - def pushcolor(self, name): - # add prefix if such color is available - if len(self.colorprefix): - prefixname = self.colorprefix[-1] + name - if prefixname in self.colors: - name = prefixname - attr = self.colors[name] - self.stdscr.attrset(attr) - self.colorstack.append(attr) - - def popcolor(self): - self.colorstack.pop() - if len(self.colorstack): - attr = self.colorstack[-1] - else: - attr = 0 - self.stdscr.attrset(attr) - - - ## drawing ## + def erase(self): + self.stdscr.erase() def putch(self, x, y, c): if not self.clipstack.test(x, y): @@ -200,10 +132,14 @@ except curses.error: pass - def erase(self): - self.stdscr.erase() + def draw(self, buffer, x=0, y=0): + for bufy in range(buffer.size.h): + for bufx in range(buffer.size.w): + char, attr_desc = buffer.get(bufx, bufy) + self.setattr(attr_desc) + self.putch(x + bufx, y + bufy, char) - def commit(self): + def flush(self): if self.cursor: self.stdscr.move(*self.cursor) curses.curs_set(True) @@ -211,6 +147,35 @@ curses.curs_set(False) self.stdscr.refresh() + ## colors, attributes ## + + def setattr(self, attr_desc): + """Set attribute to be used for subsequent draw operations.""" + attr = self.colors.get(attr_desc, None) + if attr is None: + # first encountered `attr_desc`, initialize + fg, bg, attrs = self._parse_attr_desc(attr_desc) + fgcol, fgattr = self.color_map[fg] + bgcol, _bgattr = self.color_map[bg] + colpair = self._getcolorpair(fgcol, bgcol) + attr = curses.color_pair(colpair) | self._parseattrs(attrs) | fgattr + self.colors[attr_desc] = attr + self.stdscr.attrset(attr) + + def _getcolorpair(self, fgcol, bgcol): + pair = (fgcol, bgcol) + if pair in self.colorpairs: + return self.colorpairs[pair] + num = len(self.colorpairs) + 1 + curses.init_pair(num, fgcol, bgcol) + self.colorpairs[pair] = num + return num + + def _parseattrs(self, attrs): + res = 0 + for a in attrs: + res = res | self.attr_map[a] + return res ## cursor ## @@ -224,10 +189,52 @@ curses.curs_set(False) self.cursor = None + ## input, events ## - ## input ## + def getevents(self, timeout=None): + """Process input, return list of events. + + timeout -- float, in seconds (None=infinite) + + Returns: + [('event', param1, ...), ...] + + """ + # empty queue -> fill + if len(self.inputqueue) == 0: + if timeout is not None: + timeout = math.ceil(timeout * 10) + self._inputqueue_fill(timeout) + + res = [] + while len(self.inputqueue): + c = self._inputqueue_get() + + if c == curses.KEY_MOUSE: + res += self._process_mouse() - def inputqueue_fill(self, timeout=None): + elif c == curses.KEY_RESIZE: + self.size.h, self.size.w = self.stdscr.getmaxyx() + res.append(('resize',)) + + elif curses.ascii.isctrl(c): + self._inputqueue_unget(c) + res += self._process_control_chars() + + elif 192 <= c <= 255: + self._inputqueue_unget(c) + res += self._process_utf8_chars() + + elif curses.ascii.isprint(c): + res += [('keypress', None, str(chr(c)))] + + else: + self._inputqueue_unget(c) + res += self._process_control_chars() + + return res + + def _inputqueue_fill(self, timeout=None): """Wait for curses input, add it to inputqueue. timeout -- int, tenths of second (None=infinite) @@ -259,12 +266,7 @@ self.stdscr.nodelay(0) - - def inputqueue_top(self, num=0): - return self.inputqueue[-1-num] - - - def inputqueue_get(self): + def _inputqueue_get(self): c = None try: c = self.inputqueue.pop() @@ -272,64 +274,20 @@ pass return c - - def inputqueue_get_wait(self): + def _inputqueue_get_wait(self): c = None while c is None: try: c = self.inputqueue.pop() except IndexError: curses.napms(25) - self.inputqueue_fill(0) + self._inputqueue_fill(0) return c - - def inputqueue_unget(self, c): + def _inputqueue_unget(self, c): self.inputqueue.append(c) - - def getevents(self, timeout=None): - '''Process input, return list of events. - - timeout -- float, in seconds - - ''' - # empty queue -> fill - if len(self.inputqueue) == 0: - if timeout is not None: - timeout = math.ceil(timeout * 10) - self.inputqueue_fill(timeout) - - res = [] - while len(self.inputqueue): - c = self.inputqueue_get() - - if c == curses.KEY_MOUSE: - res += self.process_mouse() - - elif c == curses.KEY_RESIZE: - self.size.h, self.size.w = self.stdscr.getmaxyx() - res.append(('resize',)) - - elif curses.ascii.isctrl(c): - self.inputqueue_unget(c) - res += self.process_control_chars() - - elif c >= 192 and c <= 255: - self.inputqueue_unget(c) - res += self.process_utf8_chars() - - elif curses.ascii.isprint(c): - res += [('keypress', None, str(chr(c)))] - - else: - self.inputqueue_unget(c) - res += self.process_control_chars() - - return res - - - def process_mouse(self): + def _process_mouse(self): try: _id, x, y, _z, bstate = curses.getmouse() except curses.error: @@ -373,12 +331,11 @@ return out - - def process_utf8_chars(self): + def _process_utf8_chars(self): #FIXME read exact number of chars as defined by utf-8 utf = [] while len(utf) <= 6: - c = self.inputqueue_get_wait() + c = self._inputqueue_get_wait() utf.append(c) try: uni = str(bytes(utf), 'utf-8') @@ -387,15 +344,14 @@ continue raise Exception('Invalid UTF-8 sequence: %r' % utf) - - def process_control_chars(self): + def _process_control_chars(self): codes = self.key_codes matchingcodes = [] match = None consumed = [] # consume next char, filter out matching codes - c = self.inputqueue_get_wait() + c = self._inputqueue_get_wait() consumed.append(c) while True: @@ -416,9 +372,9 @@ # match found and some sequencies still match -> continue if len(matchingcodes) > 0: if len(self.inputqueue) == 0: - self.inputqueue_fill(1) + self._inputqueue_fill(1) - c = self.inputqueue_get() + c = self._inputqueue_get() if c: consumed.append(c) codes = matchingcodes @@ -431,28 +387,27 @@ # compare match to consumed, return unused chars l = len(match) - 1 while len(consumed) > l: - self.inputqueue_unget(consumed[-1]) + self._inputqueue_unget(consumed[-1]) del consumed[-1] keyname = match[-1] if match is None: self.log.debug('Unknown control sequence: %s', - ','.join(['0x%x'%x for x in consumed])) + ','.join(['0x%x' % x for x in consumed])) return [('keypress', 'Unknown', None)] if keyname == 'mouse': - return self.process_xterm_mouse() + return self._process_xterm_mouse() if keyname == 'CSI': - return self.process_control_sequence() + return self._process_control_sequence() return [('keypress', keyname, None)] - - def process_xterm_mouse(self): - t = self.inputqueue_get_wait() - x = self.inputqueue_get_wait() - 0x21 - y = self.inputqueue_get_wait() - 0x21 + def _process_xterm_mouse(self): + t = self._inputqueue_get_wait() + x = self._inputqueue_get_wait() - 0x21 + y = self._inputqueue_get_wait() - 0x21 out = [] @@ -485,10 +440,10 @@ return out - def process_control_sequence(self): + def _process_control_sequence(self): codes = self.csi_codes debug_seq = [0x1b, 0x5b] - c = self.inputqueue_get_wait() + c = self._inputqueue_get_wait() debug_seq.append(c) # numeric parameters? @@ -496,7 +451,7 @@ if chr(c).isdigit(): params.append(chr(c)) while True: - c = self.inputqueue_get_wait() + c = self._inputqueue_get_wait() debug_seq.append(c) if chr(c).isdigit(): params[-1] += chr(c) @@ -530,7 +485,7 @@ break else: # more than one matching -> continue loop - c = self.inputqueue_get_wait() + c = self._inputqueue_get_wait() debug_seq.append(c) # filter codes using first parameter @@ -556,4 +511,3 @@ mod = params[1] - 1 return [('keypress', keyname, None, mod)] - diff -r ebe732b9ef19 -r 04dfb5ddf031 tuikit/driver/driver.py --- a/tuikit/driver/driver.py Sat Mar 15 11:05:12 2014 +0100 +++ b/tuikit/driver/driver.py Sat Mar 15 14:43:47 2014 +0100 @@ -16,8 +16,8 @@ self.size = Size() #: Clipping region stack. self.clipstack = ClipStack() - #: Stack of color prefixes. - self.colorprefix = [] + + ## initialization, finalization ## def init(self): """Initialize the driver and screen.""" @@ -27,26 +27,60 @@ """Clean up the screen etc.""" pass + def __enter__(self): + self.init() + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + + ## drawing ## + + def erase(self): + pass + + def putch(self, x, y, c): + pass + def draw(self, buffer, x=0, y=0): for bufy in range(buffer.size.h): for bufx in range(buffer.size.w): print(buffer.get(bufx, bufy)[0], end='') print() + def flush(self): + pass - ## drawing ## + ## colors, attributes ## + + def setattr(self, attr_desc): + """Set attribute to be used for subsequent draw operations.""" + pass - def fill_clip(self, c=' '): - """Fill current clip region.""" - rect = self.clipstack.top() - self.fill(rect.x, rect.y, rect.w, rect.h, c) + def _parse_attr_desc(self, attr_desc): + parts = attr_desc.split(',') + fgbg = parts[0].split(' on ', 1) + fg = fgbg[0].strip().lower() + bg = fgbg[1:] and fgbg[1].strip().lower() or 'default' + attrs = (part.strip().lower() for part in parts[1:]) + return fg, bg, attrs + ## cursor ## - ## colors ## + def showcursor(self, x, y): + pass + + def hidecursor(self): + pass + + ## input, events ## - def pushcolorprefix(self, name): - self.colorprefix.append(name) + def getevents(self, timeout=None): + """Process input, return list of events. + + timeout -- float, in seconds (None=infinite) - def popcolorprefix(self): - self.colorprefix.pop() + Returns: + [('event', param1, ...), ...] + """ + return []