Add TextBox, text editor demo. Update demobase.
authorRadek Brich <radek.brich@devl.cz>
Fri, 28 Mar 2014 14:58:20 +0100
changeset 96 68c562e0eb1f
parent 95 05392e369ede
child 97 0c2e0c09ba5c
Add TextBox, text editor demo. Update demobase.
demos/04_texteditor.py
demos/demobase.py
tuikit/widgets/textbox.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/demos/04_texteditor.py	Fri Mar 28 14:58:20 2014 +0100
@@ -0,0 +1,33 @@
+#!/usr/bin/env python3
+
+import demobase
+
+from tuikit.core.application import Application
+#from tuikit.scrollview import ScrollView
+from tuikit.widgets.textbox import TextBox
+
+
+class MyApplication(Application):
+    def __init__(self):
+        Application.__init__(self)
+        #self.top.add_handler('keypress', self.on_top_keypress)
+
+        t = open('../tuikit/core/widget.py').read()
+        editbox = TextBox(t)
+
+        #scroll = ScrollView()
+        #scroll.add(editbox)
+
+        self.root_window.add(editbox)
+        #self.root_window.add(scroll, halign='fill', valign='fill')
+
+    def on_top_keypress(self, ev):
+        if ev.keyname == 'escape':
+            self.terminate()
+            return True
+
+
+if __name__ == '__main__':
+    app = MyApplication()
+    app.start()
+
--- a/demos/demobase.py	Fri Mar 28 14:58:12 2014 +0100
+++ b/demos/demobase.py	Fri Mar 28 14:58:20 2014 +0100
@@ -1,9 +1,13 @@
+# Path to root directory containing tuikit package
 import sys
 sys.path.append('..')
 
-import logging
+# Set system locale (needed for ncurses)
+import locale
+locale.setlocale(locale.LC_ALL, '')
 
 # Setup logging
+import logging
 logger = logging.getLogger('tuikit')
 logger.setLevel(logging.DEBUG)
 handler = logging.FileHandler(filename='tuikit.log')
@@ -11,3 +15,9 @@
 formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s %(message)s')
 handler.setFormatter(formatter)
 logger.addHandler(handler)
+
+# Escape key code is also used for escape sequences. After escape code,
+# terminal waits for rest of sequence. This delay is 1 second by default.
+# Let's hope that our terminal is fast enough to handle the sequences in 200ms.
+import os
+os.environ['ESCDELAY'] = '200'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tuikit/widgets/textbox.py	Fri Mar 28 14:58:20 2014 +0100
@@ -0,0 +1,209 @@
+from tuikit.core.widget import Widget
+from tuikit.core.signal import Signal
+
+
+class TextBox(Widget):
+
+    """Multiline text view/edit widget.
+
+    Spot is used for text cursor position.
+
+    """
+
+    def __init__(self, text=''):
+        Widget.__init__(self)
+
+        # Text content, splitted as lines
+        self._lines = []
+        self.text = text
+
+        self.allow_focus = True
+
+        # Cursor position is same as spot.
+        # This variable rememberes horizontal position
+        # for the case when cursor moves to shorter line.
+        self.cursor_column = 0
+        # selection - line and column of selection start
+        self.sel_line = 0
+        self.sel_column = 0
+
+        #self.add_events('scroll', Event)
+
+    @property
+    def text(self):
+        return '\n'.join(self._lines)
+
+    @text.setter
+    def text(self, value):
+        self._lines = value.split('\n')
+        maxlen = max([len(line) for line in self._lines])
+        self.sizereq.update(w=maxlen, h=len(self._lines))
+
+    @property
+    def cur_line(self):
+        return self._lines[self._spot.y]
+
+    @cur_line.setter
+    def cur_line(self, value):
+        self._lines[self._spot.y] = value
+
+    def set_theme(self, theme):
+        Widget.set_theme(self, theme)
+        self.color = theme.normal
+
+    def draw(self, buffer):
+        exposed = self.exposed(buffer)
+        with buffer.attr(self.color):
+            buffer.fill()
+            end_y = min(len(self._lines), exposed.y + exposed.h)
+            for j in range(exposed.y, end_y):
+                line = self._lines[j]
+                buffer.puts(line, 0, j)
+            #self.cursor = (self._spot.x, self._spot.y)
+
+    def on_keypress(self, ev):
+        if ev.keyname:
+            if ev.keyname == 'left':        self.move_left()
+            if ev.keyname == 'right':       self.move_right()
+            if ev.keyname == 'home':        self.move_home()
+            if ev.keyname == 'end':         self.move_end()
+            if ev.keyname == 'up':          self.move_up()
+            if ev.keyname == 'down':        self.move_down()
+            if ev.keyname == 'pageup':      self.move_pageup()
+            if ev.keyname == 'pagedown':    self.move_pagedown()
+            if ev.keyname == 'backspace':   self.backspace()
+            if ev.keyname == 'delete':      self.del_char()
+            if ev.keyname == 'enter':       self.add_newline(move=True)
+            if ev.mod == ev.MOD_CTRL:
+                if ev.keyname == 'home':    self.move_top()
+                if ev.keyname == 'end':     self.move_bottom()
+
+        if ev.char:
+            self.add_char(ev.char)
+            self.move_right()
+
+        self.redraw()
+
+    def on_mousedown(self, ev):
+        y = ev.wy
+        x = min(ev.wx, len(self._lines[y]))
+        self._spot.update(x=x, y=y)
+        self.redraw()
+
+    def on_mousewheel(self, ev):
+        if ev.button == 4:
+            # wheel up
+            self.emit('scrollreq', -5)
+        if ev.button == 5:
+            # wheel down
+            self.emit('scrollreq', +5)
+        self.redraw()
+
+    def move_left(self):
+        if self._spot.x > 0:
+            self._spot.x -= 1
+        else:
+            if self._spot.y > 0:
+                self._spot.y -= 1
+                self._spot.x = len(self.cur_line)
+        self.cursor_column = self._spot.x
+
+    def move_right(self):
+        if self._spot.x < len(self.cur_line):
+            self._spot.x += 1
+        else:
+            if self._spot.y < len(self._lines) - 1:
+                self._spot.y += 1
+                self._spot.x = 0
+        self.cursor_column = self._spot.x
+
+    def move_home(self):
+        self._spot.x = 0
+        self.cursor_column = self._spot.x
+
+    def move_end(self):
+        self._spot.x = len(self.cur_line)
+        self.cursor_column = self._spot.x
+
+    def move_up(self):
+        if self._spot.y > 0:
+            self._spot.y -= 1
+        self._update_spot_x()
+
+    def move_down(self):
+        if self._spot.y < len(self._lines) - 1:
+            self._spot.y += 1
+        self._update_spot_x()
+
+    def move_pageup(self):
+        if self._spot.y >= self.view_height - 1:
+            self.emit('scrollreq', - (self.view_height - 1))
+            self._spot.y -= self.view_height - 1
+        else:
+            self._spot.y = 0
+        self._update_spot_x()
+
+    def move_pagedown(self):
+        if len(self._lines) - self._spot.y > (self.view_height - 1):
+            self.emit('scrollreq', (self.view_height - 1))
+            self._spot.y += self.view_height - 1
+        else:
+            self._spot.y = len(self._lines) - 1
+        self._update_spot_x()
+
+    def move_top(self):
+        self._spot.y = 0
+        self._update_spot_x()
+
+    def move_bottom(self):
+        self._spot.y = len(self._lines) - 1
+        self._update_spot_x()
+
+    def add_char(self, c):
+        ln = self.cur_line
+        sx = self._spot.x
+        self.cur_line = ln[:sx] + c + ln[sx:]
+        self.cursor_column = sx
+
+    def add_newline(self, move=False):
+        ln = self.cur_line
+        sx = self._spot.x
+        self.cur_line = ln[sx:]
+        self._lines.insert(self._spot.y, ln[:sx])
+        self._default_size.update(h=len(self._lines))
+        if move:
+            self.move_right()
+
+    def add_line(self, text):
+        ln = self.cur_line
+        sx = self._spot.x
+        self.cur_line = ln[sx:]
+        self._lines.insert(self._spot.y, ln[:sx] + text)
+        self.cursor_column = 0
+        self._spot.x = 0
+        self._spot.y += 1
+        w = max(self._default_size.w, len(ln[:sx] + text))
+        self._default_size.update(w=w, h=len(self._lines))
+
+    def backspace(self):
+        if self._spot.y > 0 or self._spot.x > 0:
+            self.move_left()
+            self.del_char()
+
+    def del_char(self):
+        ln = self.cur_line
+        sx = self._spot.x
+        if sx == len(self.cur_line):
+            if self._spot.y + 1 < len(self._lines):
+                self.cur_line += self._lines[self._spot.y+1]
+                del self._lines[self._spot.y+1]
+                self._default_size.update(h=len(self._lines))
+        else:
+            self.cur_line = ln[:sx] + ln[sx+1:]
+
+    def _update_spot_x(self):
+        if self.cursor_column > len(self.cur_line):
+            self._spot.x = len(self.cur_line)
+        else:
+            self._spot.x = self.cursor_column
+