# HG changeset patch # User Radek Brich # Date 1318181425 -7200 # Node ID b248ef500557396a0e14d80252ac3a0b4f72590d # Parent 4e72fd2a0e146bc4c2f841ae098fd6947b8fbd5e Add DriverPygame (incomplete). Move unicode graphics constants to UnicodeGraphics class. Move shared parts of drivers to Driver base class. diff -r 4e72fd2a0e14 -r b248ef500557 tuikit/application.py --- a/tuikit/application.py Sun Oct 09 13:06:58 2011 +0200 +++ b/tuikit/application.py Sun Oct 09 19:30:25 2011 +0200 @@ -6,6 +6,7 @@ from tuikit.container import Container from tuikit.driver_curses import DriverCurses +from tuikit.driver_pygame import DriverPygame from tuikit.driver_dummy import DriverDummy @@ -76,7 +77,7 @@ '''Application class. Defines main loop.''' - def __init__(self): + def __init__(self, driver = 'curses'): '''Create application.''' self.top = TopWindow() @@ -84,9 +85,10 @@ self.quit = False - #self.driver = DriverDummy() - self.driver = DriverCurses() - '''Driver class (render + input), i.e. DriverCurses.''' + #FIXME: import only selected driver, not all + driver_dict = {'dummy': DriverDummy, 'curses': DriverCurses, 'pygame': DriverPygame} + self.driver = driver_dict[driver]() + '''Driver class (render + input), e.g. DriverCurses.''' self.log = logging.getLogger('tuikit') self.log.setLevel(logging.DEBUG) @@ -118,13 +120,16 @@ if self.top.has_timeout(): timeout = int(math.ceil(self.top.nearest_timeout() * 10)) - events = self.driver.process_input(timeout) + events = self.driver.getevents(timeout) if self.top.has_timeout(): self.top.process_timeout() for event in events: - self.top.emit(event[0], *event[1:]) + if event[0] == 'quit': + self.quit = True + else: + self.top.emit(event[0], *event[1:]) if self.quit: break diff -r 4e72fd2a0e14 -r b248ef500557 tuikit/common.py --- a/tuikit/common.py Sun Oct 09 13:06:58 2011 +0200 +++ b/tuikit/common.py Sun Oct 09 19:30:25 2011 +0200 @@ -145,3 +145,34 @@ h = max(r1.y + r1.h, r2.y + r2.h) - y return Rect(x, y, w, h) + +class UnicodeGraphics: + + '''Unicode graphics bank. + + This class can be overriden to change graphics style (round corners etc.).''' + + # http://en.wikipedia.org/wiki/List_of_Unicode_characters#Geometric_shapes + UP_ARROW = '▲' #curses.ACS_UARROW + DOWN_ARROW = '▼' #curses.ACS_DARROW + + # http://en.wikipedia.org/wiki/Box-drawing_characters + LIGHT_SHADE = '░' #curses.ACS_BOARD + MEDIUM_SHADE = '▒' + DARK_SHADE = '▓' + BLOCK = '█' + + COLUMN = '▁▂▃▄▅▆▇█' + CORNER_ROUND = '╭╮╰╯' + CORNER = '┌┐└┘' + LINE = '─━│┃┄┅┆┇┈┉┊┋' + + HLINE = '─' # curses.ACS_HLINE + VLINE = '│' # curses.ACS_VLINE + ULCORNER = '┌' # curses.ACS_ULCORNER + URCORNER = '┐' # curses.ACS_URCORNER + LLCORNER = '└' # curses.ACS_LLCORNER + LRCORNER = '┘' # curses.ACS_LRCORNER + LTEE = '├' + RTEE = '┤' + diff -r 4e72fd2a0e14 -r b248ef500557 tuikit/driver.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tuikit/driver.py Sun Oct 09 19:30:25 2011 +0200 @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +'''Tuikit driver base.''' + +from tuikit.common import Size, ClipStack, UnicodeGraphics + + +class Driver: + + '''Abstract driver interface. Use as base for drivers.''' + + def __init__(self): + '''Initialize instance attributes.''' + self.size = Size() + '''Screen size.''' + self.clipstack = ClipStack() + '''Clipping region stack.''' + self.unigraph = UnicodeGraphics() + '''Unicode graphics characters.''' + + + # drawing + + def puts(self, x, y, s): + '''Output string of characters.''' + for c in s: + self.putch(x, y, c) + x += 1 + + def hline(self, x, y, w, c=' '): + '''Draw horizontal line.''' + if isinstance(c, str): + s = c*w + else: + s = [c]*w + self.puts(x, y, s) + + def vline(self, x, y, h, c=' '): + '''Draw vertical line.''' + for i in range(h): + self.putch(x, y+i, c) + + def fill(self, x, y, w, h, c=' '): + '''Fill rectangular area.''' + for i in range(h): + self.hline(x, y + i, w, c) + + def frame(self, x, y, w, h): + '''Draw rectangular frame using line-drawing characters.''' + self.putch(x, y, self.unigraph.ULCORNER) + self.putch(x+w-1, y, self.unigraph.URCORNER) + self.putch(x, y+h-1, self.unigraph.LLCORNER) + self.putch(x+w-1, y+h-1, self.unigraph.LRCORNER) + self.hline(x+1, y, w-2, self.unigraph.HLINE) + self.hline(x+1, y+h-1, w-2, self.unigraph.HLINE) + self.vline(x, y+1, h-2, self.unigraph.VLINE) + self.vline(x+w-1, y+1, h-2, self.unigraph.VLINE) + + \ No newline at end of file diff -r 4e72fd2a0e14 -r b248ef500557 tuikit/driver_curses.py --- a/tuikit/driver_curses.py Sun Oct 09 13:06:58 2011 +0200 +++ b/tuikit/driver_curses.py Sun Oct 09 19:30:25 2011 +0200 @@ -1,13 +1,12 @@ # -*- coding: utf-8 -*- -import curses +import curses.wrapper import curses.ascii -import curses.wrapper import locale import logging -from tuikit.common import Size, Rect, ClipStack - +from tuikit.driver import Driver + class MouseEvent: def __init__(self, x=0, y=0): @@ -32,7 +31,7 @@ return ev -class DriverCurses: +class DriverCurses(Driver): xterm_codes = ( (0x09, 'tab' ), (0x0a, 'enter' ), @@ -83,10 +82,10 @@ def __init__(self): '''Set driver attributes to default values.''' + Driver.__init__(self) + self.log = logging.getLogger('tuikit') self.screen = None - self.size = Size() self.cursor = None - self.clipstack = ClipStack() self.colors = {} # maps names to curses attributes self.colorpairs = {} # maps tuple (fg,bg) to curses color_pair self.colorstack = [] # pushcolor/popcolor puts or gets attributes from this @@ -94,33 +93,7 @@ self.inputqueue = [] self.mbtnstack = [] - self.log = logging.getLogger('tuikit') - - # http://en.wikipedia.org/wiki/List_of_Unicode_characters#Geometric_shapes - self.UP_ARROW = '▲' #curses.ACS_UARROW - self.DOWN_ARROW = '▼' #curses.ACS_DARROW - - # http://en.wikipedia.org/wiki/Box-drawing_characters - self.LIGHT_SHADE = '░' #curses.ACS_BOARD - self.MEDIUM_SHADE = '▒' - self.DARK_SHADE = '▓' - self.BLOCK = '█' - - self.COLUMN = '▁▂▃▄▅▆▇█' - self.CORNER_ROUND = '╭╮╰╯' - self.CORNER = '┌┐└┘' - self.LINE = '─━│┃┄┅┆┇┈┉┊┋' - - self.HLINE = '─' # curses.ACS_HLINE - self.VLINE = '│' # curses.ACS_VLINE - self.ULCORNER = '┌' # curses.ACS_ULCORNER - self.URCORNER = '┐' # curses.ACS_URCORNER - self.LLCORNER = '└' # curses.ACS_LLCORNER - self.LRCORNER = '┘' # curses.ACS_LRCORNER - self.LTEE = '├' - self.RTEE = '┤' - - def init(self): + def init_curses(self): '''Initialize curses''' self.size.h, self.size.w = self.screen.getmaxyx() self.screen.immedok(0) @@ -132,7 +105,7 @@ def start(self, mainfunc): def main(screen): self.screen = screen - self.init() + self.init_curses() mainfunc() curses.wrapper(main) @@ -143,7 +116,6 @@ name = name.lower().strip() return self.color_names[name] - def _getcolorpair(self, fg, bg): pair = (fg, bg) if pair in self.colorpairs: @@ -153,7 +125,6 @@ self.colorpairs[pair] = num return num - def _parseattrs(self, attrs): res = 0 for a in attrs: @@ -168,7 +139,6 @@ res = res | trans[a] return res - def setcolor(self, name, desc): parts = desc.split(',') fg, bg = parts[0].split(' on ') @@ -179,7 +149,6 @@ attr = self._parseattrs(attrs) self.colors[name] = curses.color_pair(col) | attr - def pushcolor(self, name): # add prefix if available if len(self.colorprefix): @@ -190,7 +159,6 @@ self.screen.attrset(attr) self.colorstack.append(attr) - def popcolor(self): self.colorstack.pop() if len(self.colorstack): @@ -199,11 +167,9 @@ attr = 0 self.screen.attrset(attr) - def pushcolorprefix(self, name): self.colorprefix.append(name) - def popcolorprefix(self): self.colorprefix.pop() @@ -221,46 +187,9 @@ except curses.error: pass - - def puts(self, x, y, s): - for c in s: - self.putch(x, y, c) - x += 1 - - - def hline(self, x, y, w, c=' '): - if isinstance(c, str): - s = c*w - else: - s = [c]*w - self.puts(x, y, s) - - - def vline(self, x, y, h, c=' '): - for i in range(h): - self.putch(x, y+i, c) - - - def frame(self, x, y, w, h): - self.putch(x, y, self.ULCORNER) - self.putch(x+w-1, y, self.URCORNER) - self.putch(x, y+h-1, self.LLCORNER) - self.putch(x+w-1, y+h-1, self.LRCORNER) - self.hline(x+1, y, w-2, self.HLINE) - self.hline(x+1, y+h-1, w-2, self.HLINE) - self.vline(x, y+1, h-2, self.VLINE) - self.vline(x+w-1, y+1, h-2, self.VLINE) - - - def fill(self, x, y, w, h, c=' '): - for i in range(h): - self.hline(x, y + i, w, c) - - def erase(self): self.screen.erase() - def commit(self): if self.cursor: self.screen.move(*self.cursor) @@ -277,7 +206,6 @@ return self.cursor = (y, x) - def hidecursor(self): curses.curs_set(False) self.cursor = None @@ -341,7 +269,7 @@ self.inputqueue.append(c) - def process_input(self, timeout=None): + def getevents(self, timeout=None): # empty queue -> fill if len(self.inputqueue) == 0: self.inputqueue_fill(timeout) diff -r 4e72fd2a0e14 -r b248ef500557 tuikit/driver_dummy.py --- a/tuikit/driver_dummy.py Sun Oct 09 13:06:58 2011 +0200 +++ b/tuikit/driver_dummy.py Sun Oct 09 19:30:25 2011 +0200 @@ -4,32 +4,56 @@ Implements basic driver interface. This is useful for debugging or when writing new driver. -@author: Radek Brich - ''' import logging -from tuikit.common import Size, ClipStack +from tuikit.driver import Driver -class DriverDummy: +class DriverDummy(Driver): '''Dummy driver class''' def __init__(self): '''Initialize instance attributes''' + Driver.__init__(self) self.log = logging.getLogger('tuikit') - self.size = Size() - self.clipstack = ClipStack() + self.size.w, self.size.h = 80, 25 def start(self, mainfunc): '''Start driver and run mainfunc.''' - self.size.w, self.size.h = 80, 25 mainfunc() - # colors + ## input ## + + def getevents(self, timeout=None): + '''Process input, return list of events. + + This dummy implementation just returns 'q' and Escape key presses. + + ''' + events = [('keypress', None, 'q'), ('keypress', 'escape', None)] + return events + + + ## drawing ## + + def erase(self): + '''Clear screen.''' + self.log.info('DummyDriver.erase()') + + def putch(self, x, y, c): + '''Output one unicode character to specified coordinates.''' + self.log.info('DummyDriver.putch(x=%r, y=%r, c=%r)', x, y, c) + + def commit(self): + '''Commit changes to the screen.''' + self.log.info('DummyDriver.commit()') + + + ## colors ## def setcolor(self, name, desc): '''Define color name. @@ -41,35 +65,21 @@ self.log.info('DummyDriver.setcolor(name=%r, desc=%r)', name, desc) def pushcolor(self, name): + '''Add color on top of stack and use this color for following output.''' self.log.info('DummyDriver.pushcolor(name=%r)', name) def popcolor(self): + '''Remove color from top of stack and use new top color for following output.''' self.log.info('DummyDriver.popcolor()') - # drawing - - def erase(self): - '''Clear screen.''' - self.log.info('DummyDriver.erase()') - - def puts(self, x, y, s): - '''Output string to specified coordinates.''' - self.log.info('DummyDriver.puts(x=%r, y=%r, s=%r)', x, y, s) + ## cursor ## - def commit(self): - '''Commit changes to the screen.''' - self.log.info('DummyDriver.commit()') - + def showcursor(self, x, y): + '''Set cursor to be shown at x, y coordinates.''' + self.log.info('DummyDriver.showcursor(x=%r, y=%r)', x, y) - # input - - def process_input(self, timeout=None): - '''Process input, return list of events. - - This dummy implementation just returns 'q' and Escape key presses. - - ''' - events = [('keypress', None, 'q'), ('keypress', 'escape', None)] - return events + def hidecursor(self): + '''Hide cursor.''' + self.log.info('DummyDriver.hidecursor()') diff -r 4e72fd2a0e14 -r b248ef500557 tuikit/driver_pygame.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tuikit/driver_pygame.py Sun Oct 09 19:30:25 2011 +0200 @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +'''PyGame driver.''' + +import pygame +import logging + +from tuikit.driver import Driver +from tuikit.common import Size + + +class DriverPygame(Driver): + + '''PyGame driver class.''' + + key_map = { + pygame.K_ESCAPE : 'escape', + } + + color_map = { + 'black' : (0,0,0), + 'blue' : (0,0,0), + 'cyan' : (0,0,0), + 'green' : (0,0,0), + 'magenta' : (0,0,0), + 'red' : (0,0,0), + 'white' : (0,0,0), + 'yellow' : (0,0,0), + } + + def __init__(self): + '''Initialize instance attributes''' + Driver.__init__(self) + self.log = logging.getLogger('tuikit') + self.screen = None + self.size.w, self.size.h = 80, 25 # screen size in characters + self.charsize = Size(8, 16) # character size in pixels + + def start(self, mainfunc): + pygame.init() + mode = self.size.w * self.charsize.w, self.size.h * self.charsize.h + self.screen = pygame.display.set_mode(mode) + mainfunc() + + + ## input ## + + def getevents(self, timeout=None): + '''Process input, return list of events.''' + events = [] + for ev in pygame.event.get(): + if ev.type == pygame.MOUSEMOTION: + pass + elif ev.type == pygame.MOUSEBUTTONUP: + pass + elif ev.type == pygame.MOUSEBUTTONDOWN: + pass + elif ev.type == pygame.KEYDOWN: + if ev.unicode: + events.append(('keypress', None, ev.unicode)) + elif ev.key in self.key_map: + events.append(('keypress', self.key_map[ev.key], None)) + elif ev.type == pygame.VIDEORESIZE: + #self.size.h, self.size.w = self.screen.getmaxyx() + events.append(('resize',)) + elif ev.type == pygame.QUIT: + events.append(('quit',)) + else: + self.log.warning('Unknown PyGame event: %r', ev.type) + return events + + + ## drawing ## + + def erase(self): + '''Clear screen.''' + self.screen.fill(self.color_map['black']) + + def putch(self, x, y, c): + if not self.clipstack.test(x, y): + return +# try: +# if isinstance(c, str) and len(c) == 1: +# self.screen.addstr(y, x, c) +# else: +# self.screen.addch(y, x, c) +# except curses.error: +# pass + + def commit(self): + '''Commit changes to the screen.''' + pygame.display.flip() + + + ## colors ## + + def setcolor(self, name, desc): + '''Define color name. + + name - name of color (e.g. 'normal', 'active') + desc - color description - foreground, background, attributes (e.g. 'black on white, bold') + + ''' + + def pushcolor(self, name): + '''Add color on top of stack and use this color for following output.''' + + def popcolor(self): + '''Remove color from top of stack and use new top color for following output.''' + + + ## cursor ## + + def showcursor(self, x, y): + '''Set cursor to be shown at x, y coordinates.''' + + def hidecursor(self): + '''Hide cursor.''' + diff -r 4e72fd2a0e14 -r b248ef500557 tuikit/scrollbar.py --- a/tuikit/scrollbar.py Sun Oct 09 13:06:58 2011 +0200 +++ b/tuikit/scrollbar.py Sun Oct 09 19:30:25 2011 +0200 @@ -32,11 +32,11 @@ def on_draw(self, screen, x, y): - screen.putch(x, y, screen.UP_ARROW) + screen.putch(x, y, screen.unigraph.UP_ARROW) for i in range(y + 1, y + self.height - 1): - screen.putch(x, i, screen.LIGHT_SHADE) - screen.putch(x, y + 1 + self.thumbpos, screen.BLOCK) - screen.putch(x, y + self.height - 1, screen.DOWN_ARROW) + screen.putch(x, i, screen.unigraph.LIGHT_SHADE) + screen.putch(x, y + 1 + self.thumbpos, screen.unigraph.BLOCK) + screen.putch(x, y + self.height - 1, screen.unigraph.DOWN_ARROW) def on_mousedown(self, ev): diff -r 4e72fd2a0e14 -r b248ef500557 tuikit/treeview.py --- a/tuikit/treeview.py Sun Oct 09 13:06:58 2011 +0200 +++ b/tuikit/treeview.py Sun Oct 09 19:30:25 2011 +0200 @@ -156,15 +156,15 @@ head = [] for l in range(level-1): if lines & (1 << l): - head.append(screen.VLINE + ' ') + head.append(screen.unigraph.VLINE + ' ') else: head.append(' ') # add vertical line if needed if index < count: - head.append(screen.LTEE) + head.append(screen.unigraph.LTEE) lines |= 1 << level-1 else: - head.append(screen.LLCORNER) + head.append(screen.unigraph.LLCORNER) lines &= ~(1 << level-1) # draw lines and name head = ''.join(head) @@ -229,9 +229,9 @@ return False def move_down(self): - next = self.next_node(self.cnode) - if next is not None: - self.cnode = next + node = self.next_node(self.cnode) + if node is not None: + self.cnode = node return True return False diff -r 4e72fd2a0e14 -r b248ef500557 tuikit/window.py --- a/tuikit/window.py Sun Oct 09 13:06:58 2011 +0200 +++ b/tuikit/window.py Sun Oct 09 19:30:25 2011 +0200 @@ -109,7 +109,7 @@ return #if x > self.parent.width-self.width: - # return + # return if self.resizing: self.width = self.origsize[0] + ev.wx - self.dragstart[0]