Fixed escape sequence handling.
authorRadek Brich <radek.brich@devl.cz>
Wed, 16 Mar 2011 15:19:05 +0100
changeset 3 33ec838dc021
parent 2 684cdc352562
child 4 d197ca00496f
Fixed escape sequence handling.
test_input.py
tuikit/backend_curses.py
tuikit/editbox.py
tuikit/scrollbar.py
tuikit/textedit.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test_input.py	Wed Mar 16 15:19:05 2011 +0100
@@ -0,0 +1,33 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import locale
+locale.setlocale(locale.LC_ALL, '')
+
+from tuikit import *
+
+
+class MyApplication(Application):
+    def __init__(self):
+        Application.__init__(self)
+        self.top.connect('keypress', self.globalkeypress)
+
+        self.text = ''
+        textedit = TextEdit(100, 40, self.text)
+        self.top.add(textedit)
+        textedit.x = 2
+        self.textedit = textedit
+
+
+    def globalkeypress(self, keyname, char):
+        if char == 'q':
+            self.terminate()
+        self.text += 'keyname: %s  char: %s\n' % (keyname, char)
+        self.textedit.settext(self.text)
+        self.textedit.scrolltoend()
+
+
+if __name__ == '__main__':
+    app = MyApplication()
+    app.start()
+
--- a/tuikit/backend_curses.py	Tue Mar 15 17:27:09 2011 +0100
+++ b/tuikit/backend_curses.py	Wed Mar 16 15:19:05 2011 +0100
@@ -255,7 +255,23 @@
 
     ## input ##
 
-    def inputqueue_fill(self):
+    def inputqueue_fill(self, timeout=None):
+        if timeout is None:
+            # wait indefinitely
+            c = self.screen.getch()
+            self.inputqueue.insert(0, c)
+
+        elif timeout > 0:
+            # wait
+            curses.halfdelay(timeout)
+            c = self.screen.getch()
+            curses.cbreak()
+            if c == -1:
+                return
+            self.inputqueue.insert(0, c)
+
+        # timeout = 0 -> no wait
+
         self.screen.nodelay(1)
 
         while True:
@@ -267,51 +283,63 @@
         self.screen.nodelay(0)
 
 
-    def inputqueue_next(self):
+    def inputqueue_top(self, num=0):
+        return self.inputqueue[-1-num]
+
+
+    def inputqueue_get(self):
+        c = None
+        try:
+            c = self.inputqueue.pop()
+        except IndexError:
+            pass
+        return c
+
+
+    def inputqueue_get_wait(self):
         c = None
         while c is None:
             try:
                 c = self.inputqueue.pop()
             except IndexError:
                 curses.napms(25)
-                self.inputqueue_fill()
+                self.inputqueue_fill(0)
         return c
 
 
+    def inputqueue_unget(self, c):
+        self.inputqueue.append(c)
+
+
     def process_input(self, timeout=None):
-        if len(self.inputqueue) > 0:
-            c = self.inputqueue_next()
-        else:
-            if not timeout is None:
-                curses.halfdelay(timeout)
-                c = self.screen.getch()
-                curses.cbreak()
-                if c == -1:
-                    return []
-            else:
-                c = self.screen.getch()
+        # empty queue -> fill
+        if len(self.inputqueue) == 0:
+            self.inputqueue_fill(timeout)
 
-        if c == curses.KEY_MOUSE:
-            return self.process_mouse()
+        res = []
+        while len(self.inputqueue):
+            c = self.inputqueue_get()
+
+            if c == curses.KEY_MOUSE:
+                res += self.process_mouse()
 
-        elif curses.ascii.isctrl(c):
-            self.inputqueue.append(c)
-            self.inputqueue_fill()
-            return self.process_control_chars()
+            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 c >= 192 and c <= 255:
-            self.inputqueue.append(c)
-            self.inputqueue_fill()
-            return self.process_utf8_chars()
+            elif curses.ascii.isprint(c):
+                res += [('keypress', None, str(chr(c)))]
 
-        elif curses.ascii.isprint(c):
-            return [('keypress', None, str(chr(c)))]
+            else:
+                #self.top.keypress(None, unicode(chr(c)))
+                self.inputqueue_unget(c)
+                res += self.process_control_chars()
 
-        else:
-            #self.top.keypress(None, unicode(chr(c)))
-            self.inputqueue.append(c)
-            self.inputqueue_fill()
-            return self.process_control_chars()
+        return res
 
 
     def process_mouse(self):
@@ -346,7 +374,7 @@
         #FIXME read exact number of chars as defined by utf-8
         utf = ''
         while len(utf) <= 6:
-            c = self.inputqueue_next()
+            c = self.inputqueue_get_wait()
             utf += chr(c)
             try:
                 uni = str(utf, 'utf-8')
@@ -357,25 +385,56 @@
 
 
     def process_control_chars(self):
-        keyname = None
-        for code in self.xterm_codes:
-            ok = False
-            if len(self.inputqueue) >= len(code) - 1:
-                ok = True
-                for i in range(len(code)-1):
-                    if self.inputqueue[-i-1] != code[i]:
-                        ok = False
-                        break
+        codes = self.xterm_codes
+        matchingcodes = []
+        match = None
+        consumed = []
+
+        # consume next char, filter out matching codes
+        c = self.inputqueue_get_wait()
+        consumed.append(c)
+
+        while True:
+            self.log.debug('c=%s len=%s', c, len(codes))
+            for code in codes:
+                if c == code[len(consumed)-1]:
+                    if len(code) - 1 == len(consumed):
+                        match = code
+                    else:
+                        matchingcodes += [code]
+
+            self.log.debug('matching=%s', len(matchingcodes))
+
+            # match found, or no matching code found -> stop
+            if len(matchingcodes) == 0:
+                break
 
-            if ok:
-                keyname = code[-1]
-                self.inputqueue = self.inputqueue[:-len(code)+1]
+            # match found and some sequencies still match -> continue
+            if len(matchingcodes) > 0:
+                if len(self.inputqueue) == 0:
+                    self.inputqueue_fill(1)
+
+            c = self.inputqueue_get()
+            if c:
+                consumed.append(c)
+                codes = matchingcodes
+                matchingcodes = []
+            else:
+                break
 
-        if keyname is None:
+        keyname = None
+        if match:
+            # compare match to consumed, return unused chars
+            l = len(match) - 1
+            while len(consumed) > l:
+                self.inputqueue_unget(consumed[-1])
+                del consumed[-1]
+            keyname = match[-1]
+
+        if match is None:
             self.log.debug('Unknown control sequence: %s',
-                ','.join(reversed(['0x%x'%x for x in self.inputqueue])))
-            c = self.inputqueue_next()
-            return [('keypress', 'Unknown%x' % c, None)]
+                ','.join(['0x%x'%x for x in consumed]))
+            return [('keypress', 'Unknown', None)]
 
         if keyname == 'mouse':
            return self.process_xterm_mouse()
@@ -384,9 +443,9 @@
 
 
     def process_xterm_mouse(self):
-        t = self.inputqueue_next()
-        x = self.inputqueue_next() - 0x21
-        y = self.inputqueue_next() - 0x21
+        t = self.inputqueue_get_wait()
+        x = self.inputqueue_get_wait() - 0x21
+        y = self.inputqueue_get_wait() - 0x21
 
         ev = MouseEvent(x, y)
         out = []
--- a/tuikit/editbox.py	Tue Mar 15 17:27:09 2011 +0100
+++ b/tuikit/editbox.py	Wed Mar 16 15:19:05 2011 +0100
@@ -6,8 +6,6 @@
     def __init__(self, width=20, height=20, text=''):
         Widget.__init__(self, width, height)
 
-        self.set_text(text)
-
         self.xofs = 0
         self.yofs = 0
 
@@ -26,6 +24,8 @@
         self.newevent('scroll')
         self.newevent('areasize')
 
+        self.set_text(text)
+
 
     def on_draw(self, screen, x, y):
         for j in range(self.height):
@@ -98,6 +98,7 @@
 
     def set_text(self, text):
         self.lines = text.split('\n')
+        self.handle('areasize')
 
 
     def get_text(self):
@@ -115,10 +116,10 @@
 
 
     def set_yofs(self, yofs):
+        if yofs > len(self.lines) - self.height:
+            yofs = len(self.lines) - self.height
         if yofs < 0:
             yofs = 0
-        if yofs > len(self.lines) - self.height:
-            yofs = len(self.lines) - self.height
         self.yofs = yofs
         self.handle('scroll')
 
@@ -183,6 +184,16 @@
             self.set_yofs(self.cline)
 
 
+    def move_pagefirst(self):
+        self.cline = 0
+        self.set_yofs(0)
+
+
+    def move_pagelast(self):
+        self.cline = len(self.lines) - 1
+        self.set_yofs(self.cline)
+
+
     def add_char(self, c):
         ln = self.lines[self.cline]
         cpos = self.get_cpos()
--- a/tuikit/scrollbar.py	Tue Mar 15 17:27:09 2011 +0100
+++ b/tuikit/scrollbar.py	Wed Mar 16 15:19:05 2011 +0100
@@ -26,7 +26,9 @@
 
     def setpos(self, pos):
         self.pos = pos
-        self.thumbpos = int(round(self.pos / self.max * (self.height - 3)))
+        self.thumbpos = 0
+        if self.max and self.pos <= self.max:
+            self.thumbpos = int(round(self.pos / self.max * (self.height - 3)))
 
 
     def on_draw(self, screen, x, y):
--- a/tuikit/textedit.py	Tue Mar 15 17:27:09 2011 +0100
+++ b/tuikit/textedit.py	Wed Mar 16 15:19:05 2011 +0100
@@ -31,6 +31,10 @@
         self.editbox.set_text(text)
 
 
+    def scrolltoend(self):
+        self.editbox.move_pagelast()
+
+
     def on_draw(self, screen, x, y):
         screen.frame(x, y, self.width, self.height)
 
@@ -40,9 +44,13 @@
 
 
     def on_editbox_areasize(self):
-        self.vscroll.max = len(self.editbox.lines) - self.editbox.height
+        smax = len(self.editbox.lines) - self.editbox.height
+        if smax < 0:
+            smax = 0
+        self.vscroll.max = smax
 
 
     def on_vscroll_change(self):
         self.editbox.yofs = self.vscroll.pos
         self.editbox.redraw()
+