tuikit/editbox.py
changeset 74 23767a33a781
parent 62 2f61931520c9
child 76 fa5301e58eca
--- a/tuikit/editbox.py	Wed Jan 30 20:21:08 2013 +0100
+++ b/tuikit/editbox.py	Fri Feb 01 00:24:02 2013 +0100
@@ -5,80 +5,73 @@
 
 
 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(20, 20)
+        self._default_size.update(40, 40)
         self.allow_focus = True
 
-        self.xofs = 0
-        self.yofs = 0
+        # 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
 
-        # cursor
-        self.cline = 0
-        self.cpos = 0
+        self.add_events('scroll', Event)
+
+        self.text = text
 
-        # selection
-        self.sline = 0
-        self.spos = 0
+    @property
+    def text(self):
+        return '\n'.join(self.lines)
 
-        self.add_events(
-            'scroll', Event,
-            'areasize', Event)
+    @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))
 
-        self.set_text(text)
+    @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):
-        for j in range(self.height):
-            if self.yofs + j >= len(self.lines):
-                break
-            line = self.lines[self.yofs + j]
-            #if len(line) < self.width:
-                #line += ' ' * (self.width - len(line))
-            #else:
-                #line = line[:self.width]
+        ev.driver.pushcolor('normal')
+        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.get_cpos() - self.xofs, self.cline - self.yofs)
-
+        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':
-                if self.cline > 0 or self.cpos > 0:
-                    self.move_left()
-                    self.del_char()
-
-            if ev.keyname == 'delete':
-                self.del_char()
-
-            if ev.keyname == 'enter':
-                self.add_newline()
-                self.move_right()
+            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)
@@ -86,139 +79,114 @@
 
         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)
 
     def on_mousewheel(self, ev):
-        # up
         if ev.button == 4:
-            self.move_up()
-        # down
+            # wheel up
+            self.emit('scrollreq', -5)
         if ev.button == 5:
-            self.move_down()
+            # wheel down
+            self.emit('scrollreq', +5)
         self.redraw()
 
-
-    def set_text(self, text):
-        self.lines = text.split('\n')
-        self.emit('areasize')
-
-
-    def get_text(self):
-        return '\n'.join(self.lines)
-
-
-    def get_linelen(self):
-        return len(self.lines[self.cline])
-
-
-    def get_cpos(self):
-        if self.cpos > self.get_linelen():
-            return self.get_linelen()
-        return self.cpos
-
-
-    def set_yofs(self, yofs):
-        if yofs > len(self.lines) - self.height:
-            yofs = len(self.lines) - self.height
-        if yofs < 0:
-            yofs = 0
-        if self.yofs != yofs:
-            self.yofs = yofs
-            self.emit('scroll')
-
-
     def move_left(self):
-        if self.cpos > 0:
-            self.cpos = self.get_cpos() - 1
+        if self._spot.x > 0:
+            self._spot.x -= 1
         else:
-            if self.move_up():
-                self.cpos = self.get_linelen()
-
+            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.cpos < self.get_linelen():
-            self.cpos += 1
+        if self._spot.x < len(self.cur_line):
+            self._spot.x += 1
         else:
-            if self.move_down():
-                self.cpos = 0
-
+            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.cpos = 0
-
+        self._spot.x = 0
+        self.cursor_column = self._spot.x
 
     def move_end(self):
-        self.cpos = self.get_linelen()
-
+        self._spot.x = len(self.cur_line)
+        self.cursor_column = self._spot.x
 
     def move_up(self):
-        if self.cline > 0:
-            self.cline -= 1
-            if self.cline < self.yofs:
-                self.set_yofs(self.cline)
-            return True
-        return False
-
+        if self._spot.y > 0:
+            self._spot.y -= 1
+        self._update_spot_x()
 
     def move_down(self):
-        if self.cline < len(self.lines) - 1:
-            self.cline += 1
-            if self.cline > self.yofs + self.height - 1:
-                self.set_yofs(self.cline - (self.height - 1))
-            return True
-        return False
-
+        if self._spot.y < len(self.lines) - 1:
+            self._spot.y += 1
+        self._update_spot_x()
 
     def move_pageup(self):
-        if self.cline >= self.height - 1:
-            self.cline -= self.height - 1
-            self.set_yofs(self.yofs - (self.height - 1))
+        if self._spot.y >= self.view_height - 1:
+            self.emit('scrollreq', - (self.view_height - 1))
+            self._spot.y -= self.view_height - 1
         else:
-            self.cline = 0
-            self.set_yofs(0)
-
+            self._spot.y = 0
+        self._update_spot_x()
 
     def move_pagedown(self):
-        if self.cline <= len(self.lines) - (self.height - 1):
-            self.cline += self.height - 1
-            self.set_yofs(self.yofs + (self.height - 1))
+        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.cline = len(self.lines) - 1
-            self.set_yofs(self.cline)
-
+            self._spot.y = len(self.lines) - 1
+        self._update_spot_x()
 
-    def move_pagefirst(self):
-        self.cline = 0
-        self.set_yofs(0)
-
+    def move_top(self):
+        self._spot.y = 0
+        self._update_spot_x()
 
-    def move_pagelast(self):
-        self.cline = len(self.lines) - 1
-        self.set_yofs(self.cline)
-
+    def move_bottom(self):
+        self._spot.y = len(self.lines) - 1
+        self._update_spot_x()
 
     def add_char(self, c):
-        ln = self.lines[self.cline]
-        cpos = self.get_cpos()
-        self.lines[self.cline] = ln[:cpos] + c + ln[cpos:]
-        self.cpos = cpos
-
+        ln = self.cur_line
+        sx = self._spot.x
+        self.cur_line = ln[:sx] + c + ln[sx:]
+        self.cursor_column = sx
 
-    def add_newline(self):
-        ln = self.lines[self.cline]
-        cpos = self.get_cpos()
-        self.lines[self.cline] = ln[cpos:]
-        self.lines.insert(self.cline, ln[:cpos])
-        self.emit('areasize')
+    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 backspace(self):
+        if self._spot.y > 0 or self._spot.x > 0:
+            self.move_left()
+            self.del_char()
 
     def del_char(self):
-        ln = self.lines[self.cline]
-        cpos = self.get_cpos()
-        if cpos == self.get_linelen():
-            if self.cline + 1 < len(self.lines):
-                self.lines[self.cline] = self.lines[self.cline] + self.lines[self.cline+1]
-                del self.lines[self.cline+1]
-                self.emit('areasize')
+        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.lines[self.cline] = ln[:cpos] + ln[cpos+1:]
+            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
+