tuikit/editbox.py
author Radek Brich <radek.brich@devl.cz>
Wed, 03 Sep 2014 19:13:37 +0200
changeset 110 cf3d49cdd6e2
parent 77 fc1989059e19
permissions -rw-r--r--
Add cursesw driver, using curses get_wch() for unicode input. It alse has enabled keypad() to let curses interpret control keys and mouse input.

# -*- coding: utf-8 -*-

from tuikit.widget import Widget
from tuikit.events import Event


class EditBox(Widget):

    """Multiline text edit widget.

    Spot is used for text cursor position.

    """

    def __init__(self, text=''):
        Widget.__init__(self)
        self._default_size.update(40, 40)
        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)

        self.text = text

    @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._default_size.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 on_draw(self, ev):
        ev.driver.pushcolor('normal')
        ev.driver.fill_clip()
        end_y = min(len(self.lines), ev.exposed.y + ev.exposed.h)
        for j in range(ev.exposed.y, end_y):
            line = self.lines[j]
            ev.driver.puts(ev.x, ev.y + j, line)
        self.cursor = (self._spot.x, self._spot.y)
        ev.driver.popcolor()

    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