# HG changeset patch # User Goffi # Date 1440088780 -7200 # Node ID 436076392538f5f6992412fd52e52e3c38d263ff # Parent 13d60882450889b2afb294d2b34b2940dbf9723b GenericList + List fixes, better 'change' signal handling, added GenericList.contents property (addind or removing an item there trigger the 'change' signal) diff -r 13d608824508 -r 436076392538 urwid_satext/sat_widgets.py --- a/urwid_satext/sat_widgets.py Thu Aug 20 18:37:52 2015 +0200 +++ b/urwid_satext/sat_widgets.py Thu Aug 20 18:39:40 2015 +0200 @@ -456,6 +456,102 @@ return super(UnselectableListBox, self).render(size, focus) +class SimpleListWalkerWithCb(urwid.SimpleListWalker): + """a SimpleListWalker which call callbacks on items changes""" + + def __init__(self, contents, on_new=None, on_delete=None): + """ + @param contents: list to copy into this object + @param on_new: callback to call when an item is added + @param on_delete: callback to call when an item is deleted + """ + # XXX: we can't use modified signal as it doesn't return the modified item + super(SimpleListWalkerWithCb, self).__init__(contents) + for content in contents: + on_new(content) + self._on_new = on_new + self._on_delete = on_delete + + def __cbSingle(self, item, cb): + try: + cb(item) + except TypeError: + pass + + def __cbMulti(self, items, cb): + if cb is not None: + for item in items: + cb(item) + + def __add__(self, new_list): + self.__cbMulti(new_list, self._on_new) + return super(SimpleListWalkerWithCb, self).__add__(new_list) + + def __delitem__(self, item): + self.__cbSingle(item, self._on_delete) + return super(SimpleListWalkerWithCb, self).__delitem__(item) + + def __delslice__(self, i,j): + items = super(SimpleListWalkerWithCb, self).__getslice__(i,j) + self.__cbMulti(items, self._on_delete) + return super(SimpleListWalkerWithCb, self).__delslice(i,j) + + def __iadd__(self, y): + raise NotImplementedError + + def __imul__(self, y): + raise NotImplementedError + + def __mul__(self, n): + raise NotImplementedError + + def __rmul__(self, n): + raise NotImplementedError + + def __setitem__(self, i, y): + parent = super(SimpleListWalkerWithCb, self) + self.__cbSingle(y, self._on_new) + to_delete = parent.__getitem__(i) + self.__cbSingle(to_delete, self._on_delete) + return parent.__setitem__(self, i, y) + + def __setslice__(self, i, j, y): + parent = super(SimpleListWalkerWithCb, self) + items_to_delete = parent.__getslice__(i,j) + self.__cbMulti(items_to_delete, self._on_delete) + if hasattr(y, '__iter__'): + self.__cbMulti(y, self._on_new) + else: + self.__cbSingle(y, self._on_new) + return parent.__setslice__(i, j, y) + + def append(self, obj): + self.__cbSingle(obj, self._on_new) + return super(SimpleListWalkerWithCb, self).append(obj) + + def extend(self, it): + self.__cbMulti(it, self.__on_new) + return super(SimpleListWalkerWithCb, self).extend(it) + + def insert(self, idx, obj): + self.__cbSingle(obj, self.__on_new) + return super(SimpleListWalkerWithCb, self).insert(idx, obj) + + def pop(self, idx=None): + if idx is None: + idx=len(self)-1 + + parent = super(SimpleListWalkerWithCb, self) + to_remove = parent.__getitem__(idx) + self.__cbSingle(to_remove, self._on_delete) + return parent.pop(idx) + + def remove(self, val): + ret = super(SimpleListWalkerWithCb, self).remove(val) + self.__cbSingle(val, self._on_delete) + return ret + + class GenericList(urwid.ListBox): signals = ['click','change'] @@ -468,7 +564,11 @@ - 'no_first_select' nothing selected when list is first displayed - 'can_select_none' if we can select nothing @param align: alignement of text inside the list + @param option_type: callable (usually a class) which will be called with: + - option as first argument + - align=align as keyword argument @param on_click: method called when click signal is emited + @param on_change: method called when change signal is emited @param user_data: data sent to the callback for click signal """ if style is None: @@ -486,11 +586,22 @@ if on_change: urwid.connect_signal(self, 'change', on_change, user_data) - self.content = urwid.SimpleListWalker([]) + self.content = SimpleListWalkerWithCb([], self._addSignals, lambda widget: self._emit('change')) super(GenericList, self).__init__(self.content) self.changeValues(options) - def _onStateChange(self, widget, selected): + def _addSignals(self, widget): + for signal, callback in (('change', self._onStateChange), ('click', self._onClick)): + try: + urwid.connect_signal(widget, signal, callback) + except NameError: + pass #the widget given doesn't support the signal + + @property + def contents(self): + return self.content + + def _onStateChange(self, widget, selected, *args): if self.single: if not selected and not self.can_select_none: #if in single mode, it's forbidden to unselect a value @@ -499,10 +610,13 @@ if selected: self.unselectAll(invisible=True) widget.setState(True, invisible=True) - self._emit("change") + self._emit("change", widget, selected, *args) - def _onClick(self, widget): - self._emit("click", widget) + def _onClick(self, widget, *args): + if widget not in self.content: + urwid.disconnect_signal(widget, "click", self._onClick) + return + self._emit("click", widget, *args) def unselectAll(self, invisible=False): for widget in self.content: @@ -543,15 +657,10 @@ old_selected = self.getSelectedValues() widgets = [] for option in new_values: - widget = self.option_type(option, self.align) + widget = self.option_type(option, align=self.align) if not self.first_display and option in old_selected: widget.setState(True) widgets.append(widget) - for signal, callback in (('change', self._onStateChange), ('click', self._onClick)): - try: - urwid.connect_signal(widget, signal, callback) - except NameError: - pass #the widget given doesn't support the signal self.content[:] = widgets if self.first_display and self.single and new_values and not self.no_first_select: self.content[0].setState(True) @@ -608,19 +717,19 @@ if style is None: style = [] self.genericList = GenericList(options, style, align, option_type, on_click, on_change, user_data) - urwid.connect_signal(self.genericList, 'change', self._onChange) - urwid.connect_signal(self.genericList, 'click', self._onClick) + urwid.connect_signal(self.genericList, 'change', lambda *args: self._emit('change')) self.max_height = max_height - def _onChange(self, widget): - self._emit('change') - - def _onClick(self, widget): - self._emit('click') + @property + def contents(self): + return self.genericList.content def selectable(self): return True + def get_cursor_coords(self, size): + return self.genericList.get_cursor_coords((size[0], self._getHeight(size, True))) + def keypress(self, size, key): return self.displayWidget(size,True).keypress(size, key) @@ -654,10 +763,13 @@ def rows(self, size, focus=False): return self.displayWidget(size, focus).rows(size, focus) - def displayWidget(self, size, focus): + def _getHeight(self, size, focus): list_size = sum([wid.rows(size, focus) for wid in self.genericList.content]) height = min(list_size,self.max_height) or 1 - return urwid.BoxAdapter(self.genericList, height) + return height + + def displayWidget(self, size, focus): + return urwid.BoxAdapter(self.genericList, self._getHeight(size, focus)) ## MISC ##