# HG changeset patch # User Radek Brich # Date 1396033139 -3600 # Node ID 0c2e0c09ba5cb2dcb4e436a99f4376d1405d5679 # Parent 68c562e0eb1f26b800741028e5dc6a7a9f3fdf3c Add TextField widget, keypress event, cursor. diff -r 68c562e0eb1f -r 0c2e0c09ba5c demos/03_application.py --- a/demos/03_application.py Fri Mar 28 14:58:20 2014 +0100 +++ b/demos/03_application.py Fri Mar 28 19:58:59 2014 +0100 @@ -5,6 +5,7 @@ from tuikit.core.application import Application from tuikit.widgets.label import Label from tuikit.widgets.button import Button +from tuikit.widgets.textfield import TextField label = Label('Hello there!') label.pos.update(20, 10) @@ -12,7 +13,12 @@ button = Button() button.pos.update(20, 20) +field = TextField('text field') +field.pos.update(20, 30) + app = Application() app.root_window.add(label) app.root_window.add(button) +app.root_window.add(field) +app.root_window.focus_child = field app.start() diff -r 68c562e0eb1f -r 0c2e0c09ba5c tuikit/core/application.py --- a/tuikit/core/application.py Fri Mar 28 14:58:20 2014 +0100 +++ b/tuikit/core/application.py Fri Mar 28 19:58:59 2014 +0100 @@ -61,6 +61,7 @@ screen = ProxyBuffer(self.driver) while not self._quit: self.window_manager.draw(screen) + self.driver.cursor = self.window_manager.cursor self.driver.flush() timeout = self.timer.nearest_timeout() diff -r 68c562e0eb1f -r 0c2e0c09ba5c tuikit/core/container.py --- a/tuikit/core/container.py Fri Mar 28 14:58:20 2014 +0100 +++ b/tuikit/core/container.py Fri Mar 28 19:58:59 2014 +0100 @@ -1,4 +1,5 @@ from tuikit.core.widget import Widget +from tuikit.core.coords import Point from tuikit.layouts.fixed import FixedLayout @@ -14,6 +15,7 @@ Widget.__init__(self) #: List of child widgets. self.children = [] + self.focus_child = None self.layout = layout_class() def add(self, widget): @@ -23,6 +25,8 @@ widget.window = self.window widget.set_theme(self.theme) self.layout.add(widget) + if self.focus_child is None: + self.focus_child = widget def resize(self, w, h): Widget.resize(self, w, h) @@ -41,3 +45,19 @@ Widget.set_theme(self, theme) for child in self.children: child.set_theme(theme) + + @property + def cursor(self): + if self.focus_child: + cursor = self.focus_child.cursor + if cursor is not None: + return cursor.moved(*self.focus_child.pos) + else: + if self._cursor is not None: + return Point(self._cursor) + + ## input events ## + + def keypress(self, keyname, char, mod=0): + if self.focus_child: + self.focus_child.keypress(keyname, char, mod) diff -r 68c562e0eb1f -r 0c2e0c09ba5c tuikit/core/coords.py --- a/tuikit/core/coords.py Fri Mar 28 14:58:20 2014 +0100 +++ b/tuikit/core/coords.py Fri Mar 28 19:58:59 2014 +0100 @@ -6,9 +6,10 @@ """ - def __init__(self, x=0, y=0): - self.x = x - self.y = y + def __init__(self, *args, **kwargs): + self.x = 0 + self.y = 0 + self.update(*args, **kwargs) def __getitem__(self, key): return (self.x, self.y)[key] diff -r 68c562e0eb1f -r 0c2e0c09ba5c tuikit/core/widget.py --- a/tuikit/core/widget.py Fri Mar 28 14:58:20 2014 +0100 +++ b/tuikit/core/widget.py Fri Mar 28 19:58:59 2014 +0100 @@ -39,6 +39,11 @@ #: None means no maximum size (infinite). self.sizemax = Size(None, None) + #: Cursor is position where text input will occur. + #: It is displayed on screen if widget is active. + #: None means no cursor (hidden). + self._cursor = None + ## position and size ## @property @@ -86,6 +91,17 @@ """ return buffer.clip_rect.moved(-buffer.origin.x, -buffer.origin.y) + @property + def cursor(self): + if self._cursor is not None: + return Point(self._cursor) + + ## input events ## + + def keypress(self, keyname, char, mod): + self._log.debug('%s keypress(%r, %r, %r)', + self.name, keyname, char, mod) + ## timeouts ## def add_timeout(self, delay, callback, *args): diff -r 68c562e0eb1f -r 0c2e0c09ba5c tuikit/core/window.py --- a/tuikit/core/window.py Fri Mar 28 14:58:20 2014 +0100 +++ b/tuikit/core/window.py Fri Mar 28 19:58:59 2014 +0100 @@ -9,6 +9,8 @@ Widgets are drawn into window, events are routed to widgets through window. + Parent of Window is always WindowManager. + """ def __init__(self, buffer=None): @@ -45,6 +47,7 @@ def draw(self, buffer): """Draw this window into `buffer`.""" + self.redraw() buffer.draw(self.buffer) @@ -57,8 +60,6 @@ def resize(self, w, h): self.children[0].resize(w, h) -# def keypress(self, keyname, char, mod=0): - def handle_event(self, event_name, *args): """Handle input event to managed windows.""" handler = getattr(self, event_name, None) diff -r 68c562e0eb1f -r 0c2e0c09ba5c tuikit/driver/curses.py --- a/tuikit/driver/curses.py Fri Mar 28 14:58:20 2014 +0100 +++ b/tuikit/driver/curses.py Fri Mar 28 19:58:59 2014 +0100 @@ -139,7 +139,7 @@ def flush(self): if self.cursor: - self.stdscr.move(*self.cursor) + self.stdscr.move(self.cursor.y, self.cursor.x) curses.curs_set(True) else: curses.curs_set(False) @@ -175,18 +175,6 @@ res = res | self.attr_map[a] return res - ## cursor ## - - def showcursor(self, x, y): - if self.clipstack.test(x, y): - self.cursor = (y, x) - else: - self.cursor = None - - def hidecursor(self): - curses.curs_set(False) - self.cursor = None - ## input, events ## def getevents(self, timeout=None): @@ -224,7 +212,7 @@ res += self._process_utf8_chars() elif curses.ascii.isprint(c): - res += [('keypress', None, str(chr(c)))] + res += [('keypress', None, str(chr(c)), set())] else: self._inputqueue_unget(c) @@ -337,7 +325,7 @@ utf.append(c) try: uni = str(bytes(utf), 'utf-8') - return [('keypress', None, uni)] + return [('keypress', None, uni, set())] except UnicodeDecodeError: continue raise Exception('Invalid UTF-8 sequence: %r' % utf) @@ -392,7 +380,7 @@ if match is None: self.log.debug('Unknown control sequence: %s', ','.join(['0x%x' % x for x in consumed])) - return [('keypress', 'Unknown', None)] + return [('keypress', 'Unknown', None, set())] if keyname == 'mouse': return self._process_xterm_mouse() @@ -400,7 +388,7 @@ if keyname == 'CSI': return self._process_control_sequence() - return [('keypress', keyname, None)] + return [('keypress', keyname, None, set())] def _process_xterm_mouse(self): t = self._inputqueue_get_wait() @@ -473,7 +461,7 @@ # no match -> unknown code seq = ','.join(['0x%x' % x for x in debug_seq]) self.log.debug('Unknown control sequence: %s', seq) - return [('keypress', 'Unknown:' + seq, None)] + return [('keypress', 'Unknown:' + seq, None, set())] elif len(codes) == 1: # one match -> we got the winner break @@ -496,7 +484,7 @@ # no match -> unknown code seq = ','.join(['0x%x' % x for x in debug_seq]) self.log.debug('Unknown control sequence: %s', seq) - return [('keypress', 'Unknown:' + seq, None)] + return [('keypress', 'Unknown:' + seq, None, set())] if len(matching_codes) > 1: raise Exception('Internal error: invalid csi_codes, more than one matching') @@ -504,11 +492,17 @@ keyname = matching_codes[0][1] # modifiers - mod = 0 + mod_bits = 0 if len(params) > 1: - mod = params[1] - 1 + mod_bits = params[1] - 1 - return [('keypress', keyname, None, mod)] + # convert modifiers from bit-map to set + mod_set = set() + for bit, name in enumerate(('shift', 'alt', 'ctrl', 'meta')): + if mod_bits & 1< 0: + c = '<' + buffer.putch(c) + + c = ' ' + if len(self.value[self.ofs:]) > self.tw: + c = '>' + buffer.putch(c, self.width-1, 0) + + self._cursor = (1 + self.curspos - self.ofs, 0) + + def keypress(self, keyname, char, mod=0): + Widget.keypress(self, keyname, char, mod) + accepted = True + if keyname == 'left': + self.move_left() + elif keyname == 'right': + self.move_right() + elif keyname == 'backspace': + if self.curspos > 0: + self.move_left() + self.del_char() + elif keyname == 'delete': + self.del_char() + else: + accepted = False + + if char: + self.add_char(char) + self.move_right() + accepted = True + + #if accepted: + #self.redraw() + return accepted + + def move_left(self): + if self.curspos - self.ofs > 1 or (self.ofs == 0 and self.curspos == 1): + # move cursor + self.curspos -= 1 + else: + # move content in field + if self.ofs > 0: + self.curspos -= 1 + self.ofs -= 1 + + def move_right(self): + if self.curspos < len(self.value): + if self.curspos - self.ofs < self.tw - 2 \ + or (self.curspos - self.ofs == self.tw - 2 and self.curspos == len(self.value)-1): + # move cursor + self.curspos += 1 + else: + # move content in field + self.curspos += 1 + self.ofs += 1 + + def add_char(self, c): + self.value = self.value[:self.curspos] + c + self.value[self.curspos:] + + def del_char(self): + self.value = self.value[:self.curspos] + self.value[self.curspos+1:] +