diff urwid_satext/sat_widgets.py @ 110:436076392538

GenericList + List fixes, better 'change' signal handling, added GenericList.contents property (addind or removing an item there trigger the 'change' signal)
author Goffi <goffi@goffi.org>
date Thu, 20 Aug 2015 18:39:40 +0200
parents 13d608824508
children 1cdf4a00b68d
line wrap: on
line diff
--- 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 ##