Add DriverPygame (incomplete). Move unicode graphics constants to UnicodeGraphics class. Move shared parts of drivers to Driver base class.
authorRadek Brich <radek.brich@devl.cz>
Sun, 09 Oct 2011 19:30:25 +0200
changeset 24 b248ef500557
parent 23 4e72fd2a0e14
child 25 f69a1f0382ce
Add DriverPygame (incomplete). Move unicode graphics constants to UnicodeGraphics class. Move shared parts of drivers to Driver base class.
tuikit/application.py
tuikit/common.py
tuikit/driver.py
tuikit/driver_curses.py
tuikit/driver_dummy.py
tuikit/driver_pygame.py
tuikit/scrollbar.py
tuikit/treeview.py
tuikit/window.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
--- 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]