Add DriverPygame (incomplete). Move unicode graphics constants to UnicodeGraphics class. Move shared parts of drivers to Driver base class.
--- 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
--- 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 = '┤'
+
--- /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
--- 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)
--- 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 <radek.brich@devl.cz>
-
'''
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()')
--- /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.'''
+
--- 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):
--- 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
--- 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]