# HG changeset patch # User Radek Brich # Date 1409774180 -7200 # Node ID 6796adfdc7eb95ac61b4816c0da7613530fdb67f # Parent ce2e67e7bbb8673d4a918e2f226e16c00e0f469c# Parent 11dac45bfba423627684dda2ce91b7179a43444a Merge. Due to my schizophrenia, I've accidentally forked my own code. The other set of changes were found in another computer. diff -r 11dac45bfba4 -r 6796adfdc7eb demos/03_application.py --- a/demos/03_application.py Wed Sep 03 08:57:24 2014 +0200 +++ b/demos/03_application.py Wed Sep 03 21:56:20 2014 +0200 @@ -10,15 +10,26 @@ label = Label('Hello there!') label.pos.update(20, 10) -button = Button() -button.pos.update(20, 20) +button1 = Button() +button1.pos.update(20, 20) +button2 = Button() +button2.pos.update(30, 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(button1) +app.root_window.add(button2) app.root_window.add(field) -app.root_window.focus_child = field +app.root_window.focus_widget = field + +def on_keypress(ev): + if ev.keyname == 'escape': + app.stop() + +app.window_manager.sig_keypress.connect(on_keypress) + app.start() + diff -r 11dac45bfba4 -r 6796adfdc7eb tests/curses_get_wch.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/curses_get_wch.py Wed Sep 03 21:56:20 2014 +0200 @@ -0,0 +1,18 @@ +#!/usr/bin/python3 + +import curses +import locale + +locale.setlocale(locale.LC_ALL, "") + +def doStuff(stdscr): + stdscr.keypad(1) + message = "Press 'q' to quit.\n" + stdscr.addstr(0, 0, message, 0) + while True: + c = stdscr.get_wch() # pauses until a key's hit + if c == 'q': + break + stdscr.addstr('%s %r\n' % (c, c)) + +curses.wrapper(doStuff) diff -r 11dac45bfba4 -r 6796adfdc7eb tests/curses_getkey.py --- a/tests/curses_getkey.py Wed Sep 03 08:57:24 2014 +0200 +++ b/tests/curses_getkey.py Wed Sep 03 21:56:20 2014 +0200 @@ -1,5 +1,4 @@ -#!/usr/bin/python -# coding=UTF-8 +#!/usr/bin/python3 import curses import locale diff -r 11dac45bfba4 -r 6796adfdc7eb tests/curses_keycodes.py --- a/tests/curses_keycodes.py Wed Sep 03 08:57:24 2014 +0200 +++ b/tests/curses_keycodes.py Wed Sep 03 21:56:20 2014 +0200 @@ -1,5 +1,4 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- +#!/usr/bin/python3 import curses import locale diff -r 11dac45bfba4 -r 6796adfdc7eb tests/curses_mouse.py --- a/tests/curses_mouse.py Wed Sep 03 08:57:24 2014 +0200 +++ b/tests/curses_mouse.py Wed Sep 03 21:56:20 2014 +0200 @@ -1,5 +1,4 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- +#!/usr/bin/python3 import curses import locale @@ -21,8 +20,8 @@ screen.addstr('key: %x %s\n' % (c, char)) if c == curses.KEY_MOUSE: - m = curses.getmouse() - screen.addstr('(%d %d %d %d %x)\n' % m) + id_, x, y, z, bstate = curses.getmouse() + screen.addstr('(%d %d %d %d %s)\n' % (id_, x, y, z, bin(bstate))) screen.refresh() diff -r 11dac45bfba4 -r 6796adfdc7eb tests/curses_unicode.py --- a/tests/curses_unicode.py Wed Sep 03 08:57:24 2014 +0200 +++ b/tests/curses_unicode.py Wed Sep 03 21:56:20 2014 +0200 @@ -1,5 +1,4 @@ -#!/usr/bin/python -# coding=UTF-8 +#!/usr/bin/python3 import curses import locale diff -r 11dac45bfba4 -r 6796adfdc7eb tests/test_event_class.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test_event_class.py Wed Sep 03 21:56:20 2014 +0200 @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 + +import sys +sys.path.append('..') + +from tuikit.core.events import KeypressEvent + +x = KeypressEvent('tab', '\t', set(['shift', 'ctrl'])) +print(repr(x)) +print(x.mod_key()) + +x = KeypressEvent(None, 'c', set(['shift', 'ctrl'])) +print(x.mod_key()) diff -r 11dac45bfba4 -r 6796adfdc7eb tuikit/core/application.py --- a/tuikit/core/application.py Wed Sep 03 08:57:24 2014 +0200 +++ b/tuikit/core/application.py Wed Sep 03 21:56:20 2014 +0200 @@ -2,6 +2,7 @@ from tuikit.core.theme import default_theme from tuikit.core.timer import Timer from tuikit.core.buffer import ProxyBuffer +from tuikit.core.events import ResizeEvent import logging @@ -15,7 +16,7 @@ """ - def __init__(self, driver='curses'): + def __init__(self, driver='cursesw'): self.log = logging.getLogger(__name__) self.driver = None self.timer = Timer() @@ -56,12 +57,12 @@ def main_loop(self): """The main loop.""" self._started = True - self.window_manager.handle_event('resize', *self.driver.size) + self.window_manager.handle_event(ResizeEvent(*self.driver.size)) screen = ProxyBuffer(self.driver) while not self._quit: self.window_manager.draw(screen) - self.driver.cursor = self.window_manager.cursor + self.driver.cursor = self.window_manager.get_cursor_if_visible() self.driver.flush() timeout = self.timer.nearest_timeout() @@ -69,7 +70,7 @@ self.timer.process_timeouts() for event in events: - self.window_manager.handle_event(event[0], *event[1:]) + self.window_manager.handle_event(event) self._started = False self.log.info('=== End ===') diff -r 11dac45bfba4 -r 6796adfdc7eb tuikit/core/container.py --- a/tuikit/core/container.py Wed Sep 03 08:57:24 2014 +0200 +++ b/tuikit/core/container.py Wed Sep 03 21:56:20 2014 +0200 @@ -14,20 +14,24 @@ def __init__(self, layout_class=FixedLayout): Widget.__init__(self) #: List of child widgets. - self.children = [] - self.focus_child = None - self.mouse_child = None + self._widgets = [] + #: Widget with keyboard focus + self.focus_widget = None + #: Widget on last mouse position + self.mouse_widget = None + #: If True, tab cycles inside container + self.trap_focus = False self.layout = layout_class() def add(self, widget): """Add widget into container.""" - self.children.append(widget) + self._widgets.append(widget) widget.parent = self widget.window = self.window widget.set_theme(self.theme) self.layout.add(widget) - if self.focus_child is None: - self.focus_child = widget + if self.focus_widget is None and widget.can_focus(): + self.focus_widget = widget def resize(self, w, h): Widget.resize(self, w, h) @@ -36,7 +40,7 @@ def draw(self, buffer): """Draw child widgets.""" Widget.draw(self, buffer) - for child in self.children: + for child in self._widgets: with buffer.moved_origin(child.x, child.y): with buffer.clip(buffer.origin.x, buffer.origin.y, child.width, child.height): @@ -44,7 +48,7 @@ def set_theme(self, theme): Widget.set_theme(self, theme) - for child in self.children: + for child in self._widgets: child.set_theme(theme) @property @@ -55,21 +59,37 @@ If this container has child with focus, return its cursor position instead. """ - if self.focus_child: - cursor = self.focus_child.cursor + if self.focus_widget: + cursor = self.focus_widget.cursor if not cursor: return None - cursor = cursor.moved(*self.focus_child.pos) + cursor = cursor.moved(*self.focus_widget.pos) else: cursor = self._cursor.immutable() if cursor in Rect._make((0, 0), self._size): return cursor + @property + def cursor_visible(self): + if self.focus_widget: + return self.focus_widget.cursor_visible + else: + return self._cursor_visible + ## input events ## - def keypress(self, keyname, char, mod=0): - if self.focus_child: - self.focus_child.keypress(keyname, char, mod) + def keypress_event(self, ev): + # First, handle the keypress event to focused child widget + if self.focus_widget is not None: + if self.focus_widget.keypress_event(ev): + return True + # Next, handle default key behaviour by Container + if ev.keyname == 'tab': + return self.focus_next(-1 if 'shift' in ev.mods else 1) + # Finally, handle default keys by Widget + # and send keypress signal + if Widget.keypress_event(self, ev): + return True def mousedown(self, button, pos): self.mouse_child = None @@ -87,3 +107,47 @@ self.mouse_child.mousemove(button, pos - self.mouse_child.pos, relpos) + ## focus ## + + def focus_next(self, step=1): + """Focus next child. + + Sets focus to next child, if there is one + which can be focused. Cycles from last child + to first when needed. Return value depends on + this cycling: + + * False means there wasn't any child to focus + before end of list. Focus was either not changed + or first child was focused. + + * True when focus is set to next child in normal + way or when self.trap_focus is set. + + Return value is supposed to be returned from keypress + event - in that case, True stops event propagation. + + """ + if self.focus_widget is None: + idx_current = 0 + else: + idx_current = self._widgets.index(self.focus_widget) + idx_new = idx_current + cycled = False + while True: + idx_new += step + if idx_new >= len(self._widgets): + idx_new = 0 + cycled = True + if idx_new < 0: # for focus_previous + idx_new = len(self._widgets) - 1 + cycled = True + if idx_current == idx_new: + return False + if self._widgets[idx_new].can_focus(): + self.focus_widget = self._widgets[idx_new] + return self.trap_focus or not cycled + + def focus_previous(self): + """Focus previous child.""" + self.focus_next(-1) diff -r 11dac45bfba4 -r 6796adfdc7eb tuikit/core/events.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tuikit/core/events.py Wed Sep 03 21:56:20 2014 +0200 @@ -0,0 +1,53 @@ +from collections import OrderedDict + + +class Event: + + """Base class for events.""" + + def __init__(self, event_name=None, arg_names=(), arg_values=()): + self.name = event_name + self.args = OrderedDict(zip(arg_names, arg_values)) + + def __getattr__(self, item): + if item in self.args: + return self.args[item] + else: + raise AttributeError(item) + + def __getitem__(self, key): + return self.args[key] + + def __repr__(self): + return '{}({})'.format(self.__class__.__name__, + ', '.join("%s=%r" % (k,v) for k,v + in self.args.items())) + + +class ResizeEvent(Event): + + def __init__(self, w, h): + Event.__init__(self, 'resize', ('w', 'h'), (w, h)) + + +class KeypressEvent(Event): + + def __init__(self, keyname, char, mods): + Event.__init__(self, 'keypress', + ('keyname', 'char', 'mods'), + (keyname, char, mods)) + + def mod_key(self, sep='+'): + """Return combined key with modifiers. + + E.g. "shift+tab" + + Order of modifiers is fixed: ctrl, alt, meta, shift + + """ + res = [] + for mod in ('ctrl', 'alt', 'meta', 'shift'): + if mod in self.mods: + res.append(mod) + res.append(self.keyname or self.char) + return sep.join(res) diff -r 11dac45bfba4 -r 6796adfdc7eb tuikit/core/signal.py --- a/tuikit/core/signal.py Wed Sep 03 08:57:24 2014 +0200 +++ b/tuikit/core/signal.py Wed Sep 03 21:56:20 2014 +0200 @@ -15,13 +15,19 @@ """ - def __init__(self): + def __init__(self, allow_stop=False): self._handlers = [] + #: Allow one of the handlers to stop processing signal + #: The handler should return True value, + #: then other handlers will not be called + self.allow_stop = allow_stop def __call__(self, *args, **kwargs): """Emit the signal to all connected handlers.""" for handler in self._handlers: - handler(*args, **kwargs) + res = handler(*args, **kwargs) + if self.allow_stop and res: + return True def connect(self, handler): if not handler in self._handlers: diff -r 11dac45bfba4 -r 6796adfdc7eb tuikit/core/theme.py --- a/tuikit/core/theme.py Wed Sep 03 08:57:24 2014 +0200 +++ b/tuikit/core/theme.py Wed Sep 03 21:56:20 2014 +0200 @@ -6,6 +6,7 @@ """Default color style""" normal = 'lightgray' + active = 'black on cyan' button = 'black on lightgray' button_active = 'black on cyan' diff -r 11dac45bfba4 -r 6796adfdc7eb tuikit/core/widget.py --- a/tuikit/core/widget.py Wed Sep 03 08:57:24 2014 +0200 +++ b/tuikit/core/widget.py Wed Sep 03 21:56:20 2014 +0200 @@ -1,5 +1,6 @@ from tuikit.core.coords import Point, Size, Rect from tuikit.core.theme import default_theme +from tuikit.core.signal import Signal import logging @@ -37,13 +38,17 @@ self.sizemax = Size(None, None) #: Cursor is position where text input will occur. - #: It is displayed on screen if widget is active. #: The cursor coordinates are relative to widget. - #: Position outside of widget boundaries means no cursor (hidden). - self._cursor = Point(-1, -1) + self._cursor = Point() + #: Cursor is displayed on screen only when the widget is focused. + self._cursor_visible = False - #: Logger name contains full module name, class name and instance number - self._log = logging.getLogger('%s.%s' % (self.__module__, self.name)) + #: Hidden widget does not affect layout. + self.hidden = False + #: Allow keyboard focus for this widget. + self.allow_focus = False + + self.sig_keypress = Signal(allow_stop=True) ## position and size ## @@ -78,8 +83,8 @@ def draw(self, buffer): """Draw self into buffer.""" - self._log.debug('draw into %r at %s (exposed %s)', - buffer, buffer.origin, self.exposed(buffer)) + self.log.debug('Draw into %r at %s (exposed %s)', + buffer, buffer.origin, self.exposed(buffer)) def set_theme(self, theme): self.theme = theme @@ -98,16 +103,37 @@ @property def cursor(self): - """Return cursor coordinates or None if cursor is not set - or is set outside of widget boundaries.""" + """Return cursor coordinates. + + Returns None if cursor is set outside of widget boundaries. + + """ if self._cursor in Rect._make((0, 0), self._size): return self._cursor - ## input events ## + @property + def cursor_visible(self): + return self._cursor_visible + + ## events ## + + def resize_event(self, ev): + self.resize(ev.w, ev.h) + + def keypress_event(self, ev): + """Keypress event handler. - def keypress(self, keyname, char, mod): - self._log.debug('keypress(keyname=%r, char=%r, mod=%r)', - keyname, char, mod) + Override to accept keyboard input. + + Returns True if event was consumed. + + Call this implementation from inherited classes + if it does not consume the event. + + """ + if self.sig_keypress(ev): + return True + self.log.debug('Not consumed: %s', ev) def mousedown(self, button, pos): self._log.debug('mousedown(btn=%r, pos=%r)', @@ -134,3 +160,25 @@ """ self.parent.remove_timeout(self, callback, *args) + + ## focus ## + + def can_focus(self): + return not self.hidden and self.allow_focus + + def has_focus(self): + if self.parent is None: + return True + return (self.parent.has_focus() + and self.parent.focus_widget == self) + + ## utilities ## + + @property + def log(self): + """Logger for widget debugging. + + Logger name contains full module name, class name and instance number. + + """ + return logging.getLogger('%s.%s' % (self.__module__, self.name)) diff -r 11dac45bfba4 -r 6796adfdc7eb tuikit/core/window.py --- a/tuikit/core/window.py Wed Sep 03 08:57:24 2014 +0200 +++ b/tuikit/core/window.py Wed Sep 03 21:56:20 2014 +0200 @@ -17,6 +17,8 @@ """New buffer for the window will be created unless given existing `buffer` as parameter.""" Container.__init__(self) + self.allow_focus = True + self.trap_focus = True self._buffer = None self.buffer = buffer or Buffer() @@ -57,15 +59,35 @@ Container.__init__(self) self.timer = timer + def draw(self, buffer): + Container.draw(self, buffer) + self.log.debug('%s has focus.', self.get_focused_widget().name) + def resize(self, w, h): Container.resize(self, w, h) - self.children[0].resize(w, h) + self._widgets[0].resize(w, h) - def handle_event(self, event_name, *args): + def keypress_event(self, ev): + self.log.debug('%s', ev) + return Container.keypress_event(self, ev) + + def handle_event(self, event): """Handle input event to managed windows.""" - self._log.debug('Handle event: %s %r', event_name, args) - handler = getattr(self, event_name, None) + self.log.debug('Handle event: %r', event) + handler = getattr(self, event.name + '_event', None) if handler: - handler(*args) + handler(event) else: - raise Exception('Unknown event: %r %r' % (event_name, args)) + raise Exception('Unknown event: %r' % event) + + def get_focused_widget(self): + """Traverse the widget hierarchy to bottom + and return actually focused Widget.""" + node = self + while isinstance(node, Container) and node.focus_widget: + node = node.focus_widget + return node + + def get_cursor_if_visible(self): + if self.cursor_visible: + return self.cursor diff -r 11dac45bfba4 -r 6796adfdc7eb tuikit/driver/curses.py --- a/tuikit/driver/curses.py Wed Sep 03 08:57:24 2014 +0200 +++ b/tuikit/driver/curses.py Wed Sep 03 21:56:20 2014 +0200 @@ -44,6 +44,7 @@ (0x44, 1, 'left' ), (0x46, 1, 'end' ), # xterm (0x48, 1, 'home' ), # xterm + (0x5a, 1, 'shift+tab' ), # xterm (0x5b, 0x41, 1, 'f1' ), # linux (0x5b, 0x42, 1, 'f2' ), # linux (0x5b, 0x43, 1, 'f3' ), # linux @@ -80,7 +81,7 @@ def __init__(self): Driver.__init__(self) - self._log = logging.getLogger('tuikit') + self._log = logging.getLogger(__name__) self.stdscr = None self.cursor = None self.colors = {} # maps names to curses attributes @@ -379,7 +380,7 @@ keyname = match[-1] if match is None: - self.log.debug('Unknown control sequence: %s', + self._log.debug('Unknown control sequence: %s', ','.join(['0x%x' % x for x in consumed])) return [('keypress', 'Unknown', None, set())] @@ -461,7 +462,7 @@ if len(codes) == 0: # no match -> unknown code seq = ','.join(['0x%x' % x for x in debug_seq]) - self.log.debug('Unknown control sequence: %s', seq) + self._log.debug('Unknown control sequence: %s', seq) return [('keypress', 'Unknown:' + seq, None, set())] elif len(codes) == 1: # one match -> we got the winner @@ -484,7 +485,7 @@ if len(matching_codes) == 0: # no match -> unknown code seq = ','.join(['0x%x' % x for x in debug_seq]) - self.log.debug('Unknown control sequence: %s', seq) + self._log.debug('Unknown control sequence: %s', seq) return [('keypress', 'Unknown:' + seq, None, set())] if len(matching_codes) > 1: @@ -503,6 +504,14 @@ if mod_bits & 1< 0: + # wait + timeout_tenths = math.ceil(timeout * 10) + curses.halfdelay(timeout_tenths) + else: + # timeout = 0 -> no wait + self.stdscr.nodelay(1) + + # Get key or char + c = self.stdscr.get_wch() + + res = [] + + if c == -1: + # Timeout + return res + elif c == curses.KEY_MOUSE: + res += self._process_mouse() + elif c == curses.KEY_RESIZE: + self.size.h, self.size.w = self.stdscr.getmaxyx() + res.append(ResizeEvent(self.size.w, self.size.h)) + elif isinstance(c, int): + keyname, mods = self._split_keyname_mods(self.key_map[c]) + res.append(KeypressEvent(keyname, None, mods)) + else: + keyname = self.key_names.get(c) + res.append(KeypressEvent(keyname, c, set())) + + return res + + def _process_mouse(self): + out = [] + try: + _id, x, y, _z, bstate = curses.getmouse() + except curses.error: + return out + + pos = Point(x, y) + if bstate & curses.REPORT_MOUSE_POSITION: + if self._mouse_last_pos != pos: + if self._mouse_last_pos: + relpos = pos - self._mouse_last_pos + out += [('mousemove', 0, pos, relpos)] + self._mouse_last_pos = pos + + # we are interested only in changes, not buttons already pressed before event + if self._mouse_last_bstate is not None: + old = self._mouse_last_bstate + new = bstate + bstate = ~old & new + self._mouse_last_bstate = new + else: + self._mouse_last_bstate = bstate + + if bstate & curses.BUTTON1_PRESSED: + out += [('mousedown', 1, pos)] + if bstate & curses.BUTTON2_PRESSED: + out += [('mousedown', 2, pos)] + if bstate & curses.BUTTON3_PRESSED: + out += [('mousedown', 3, pos)] + if bstate & curses.BUTTON1_RELEASED: + out += [('mouseup', 1, pos)] + if bstate & curses.BUTTON2_RELEASED: + out += [('mouseup', 2, pos)] + if bstate & curses.BUTTON3_RELEASED: + out += [('mouseup', 3, pos)] + + # reset last pos when pressed/released + if len(out) > 0 and out[-1][0] in ('mousedown', 'mouseup'): + self._mouse_last_pos = None + + return out + + def _split_keyname_mods(self, keyname): + """Parse keynames in form "shift+tab", return (keyname, mod).""" + mod_set = set() + if '+' in keyname: + parts = keyname.split('+') + for mod in parts[:-1]: + assert(mod in ('shift', 'alt', 'ctrl', 'meta')) + mod_set.add(mod) + keyname = parts[-1] + + return keyname, mod_set + + +driver_class = CursesWDriver diff -r 11dac45bfba4 -r 6796adfdc7eb tuikit/widgets/button.py --- a/tuikit/widgets/button.py Wed Sep 03 08:57:24 2014 +0200 +++ b/tuikit/widgets/button.py Wed Sep 03 21:56:20 2014 +0200 @@ -9,6 +9,7 @@ def __init__(self, label='btn'): """Create button with given label, size according to label.""" Widget.__init__(self) + self.allow_focus = True #: Button label. self._label = '' @@ -21,10 +22,8 @@ #: Padding between prefix/suffix and label self.padding = 1 - self.allow_focus = True - self.color = 'default' - self.color_highlighted = 'default on red' + self.color_active = 'default on red' self.highlight = False self.sig_clicked = Signal() @@ -45,11 +44,11 @@ def set_theme(self, theme): Widget.set_theme(self, theme) self.color = theme.button - self.color_highlighted = theme.button_active + self.color_active = theme.button_active def _get_color(self): - if self.highlight: # or self.has_focus(): - return self.color_highlighted + if self.has_focus(): + return self.color_active return self.color def draw(self, buffer): diff -r 11dac45bfba4 -r 6796adfdc7eb tuikit/widgets/textbox.py --- a/tuikit/widgets/textbox.py Wed Sep 03 08:57:24 2014 +0200 +++ b/tuikit/widgets/textbox.py Wed Sep 03 21:56:20 2014 +0200 @@ -4,25 +4,20 @@ class TextBox(Widget): - """Multiline text view/edit widget. - - Cursor is used for text cursor position. - - """ + """Multiline text view/edit widget.""" def __init__(self, text=''): Widget.__init__(self) + self.allow_focus = True # Text content, splitted as lines self._lines = [] self.text = text - self.allow_focus = True - - # This variable rememberes horizontal position of cursor + self._cursor_visible = True + # This variable remembers horizontal cursor position # for the case when cursor moves to shorter line. self.cursor_column = 0 - self._cursor.update(0, 0) # selection - line and column of selection start self.sel_line = 0 self.sel_column = 0 @@ -47,41 +42,47 @@ def cur_line(self, value): self._lines[self._cursor.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): + with buffer.attr(self.theme.normal): 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) - def keypress(self, keyname, char, mod=0): - if keyname: - if keyname == 'left': self.move_left() - if keyname == 'right': self.move_right() - if keyname == 'home': self.move_home() - if keyname == 'end': self.move_end() - if keyname == 'up': self.move_up() - if keyname == 'down': self.move_down() - if keyname == 'pageup': self.move_pageup() - if keyname == 'pagedown': self.move_pagedown() - if keyname == 'backspace': self.backspace() - if keyname == 'delete': self.del_char() - if keyname == 'enter': self.add_newline(move=True) - if 'ctrl' in mod: - if keyname == 'home': self.move_top() - if keyname == 'end': self.move_bottom() + def keypress_event(self, ev): + if ev.keyname and not ev.mods: + consumed = True + if ev.keyname == 'left': self.move_left() + elif ev.keyname == 'right': self.move_right() + elif ev.keyname == 'home': self.move_home() + elif ev.keyname == 'end': self.move_end() + elif ev.keyname == 'up': self.move_up() + elif ev.keyname == 'down': self.move_down() + elif ev.keyname == 'pageup': self.move_pageup() + elif ev.keyname == 'pagedown': self.move_pagedown() + elif ev.keyname == 'backspace': self.backspace() + elif ev.keyname == 'delete': self.del_char() + elif ev.keyname == 'enter': self.add_newline(move=True) + else: + consumed = False + if consumed: + return True + if ev.mods: + consumed = True + mk = ev.mod_key() + if mk == 'ctrl+home': self.move_top() + elif mk == 'ctrl+end': self.move_bottom() + else: + consumed = False + if consumed: + return True - if char: - self.add_char(char) + if ev.char and not ev.keyname: + self.add_char(ev.char) self.move_right() - - #self.redraw() + return True def on_mousedown(self, ev): y = ev.wy @@ -169,7 +170,7 @@ sx = self._cursor.x self.cur_line = ln[sx:] self._lines.insert(self._cursor.y, ln[:sx]) - self._default_size.update(h=len(self._lines)) + self.sizereq.update(h=len(self._lines)) if move: self.move_right() @@ -181,8 +182,8 @@ self.cursor_column = 0 self._cursor.x = 0 self._cursor.y += 1 - w = max(self._default_size.w, len(ln[:sx] + text)) - self._default_size.update(w=w, h=len(self._lines)) + w = max(self.sizereq.w, len(ln[:sx] + text)) + self.sizereq.update(w=w, h=len(self._lines)) def backspace(self): if self._cursor.y > 0 or self._cursor.x > 0: @@ -196,7 +197,7 @@ if self._cursor.y + 1 < len(self._lines): self.cur_line += self._lines[self._cursor.y+1] del self._lines[self._cursor.y+1] - self._default_size.update(h=len(self._lines)) + self.sizereq.update(h=len(self._lines)) else: self.cur_line = ln[:sx] + ln[sx+1:] diff -r 11dac45bfba4 -r 6796adfdc7eb tuikit/widgets/textfield.py --- a/tuikit/widgets/textfield.py Wed Sep 03 08:57:24 2014 +0200 +++ b/tuikit/widgets/textfield.py Wed Sep 03 21:56:20 2014 +0200 @@ -7,10 +7,9 @@ def __init__(self, value=''): Widget.__init__(self) + self.allow_focus = True self.sizereq.update(10, 1) - self.allow_focus = True - self.code = locale.getpreferredencoding() if not isinstance(value, str): value = str(value, self.code) @@ -22,6 +21,7 @@ self.curspos = 0 # position of cursor in value self.ofs = 0 # position of value beginning on screen + self._cursor_visible = True self.move_end() def resize(self, w, h): @@ -29,11 +29,9 @@ if self.curspos >= self.tw: self.ofs = self.curspos - self.tw - def set_theme(self, theme): - self.color = theme.normal - def draw(self, buffer): - with buffer.attr(self.color): + color = self.theme.active if self.has_focus() else self.theme.normal + with buffer.attr(color): # draw value val = self.value + ' ' * self.tw # add spaces to fill rest of field val = val[self.ofs : self.ofs + self.tw] # cut value - begin from ofs, limit to tw chars @@ -52,8 +50,7 @@ self._cursor.update(1 + self.curspos - self.ofs, 0) - def keypress(self, keyname, char, mod=0): - Widget.keypress(self, keyname, char, mod) + def keypress_event(self, ev): map_keyname_to_func = { 'left': self.move_left, 'right': self.move_right, @@ -62,17 +59,18 @@ 'backspace': self.move_left_and_del, 'delete': self.del_char, } - accepted = False - if keyname in map_keyname_to_func: - map_keyname_to_func[keyname]() - accepted = True - if char: - self.add_char(char) + consumed = False + if ev.keyname in map_keyname_to_func: + map_keyname_to_func[ev.keyname]() + consumed = True + if not ev.keyname and ev.char: + self.add_char(ev.char) self.move_right() - accepted = True - #if accepted: + consumed = True + if consumed: #self.redraw() - return accepted + return True + Widget.keypress_event(self, ev) def move_left(self): if self.curspos - self.ofs > 1 or (self.ofs == 0 and self.curspos == 1):