# HG changeset patch # User Radek Brich # Date 1396015100 -3600 # Node ID 68c562e0eb1f26b800741028e5dc6a7a9f3fdf3c # Parent 05392e369ede17f0f13002cb406474f1766000e9 Add TextBox, text editor demo. Update demobase. diff -r 05392e369ede -r 68c562e0eb1f demos/04_texteditor.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() + diff -r 05392e369ede -r 68c562e0eb1f demos/demobase.py --- 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' diff -r 05392e369ede -r 68c562e0eb1f tuikit/widgets/textbox.py --- /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 +