changeset 219:9faccd140119

plugin contact list: refactoring: - contacts are now displayed in a grid - they can be filtered thanks to FilterBehaviour - use the new update system of QuickContactList - a new "add a contact" button is visible, but not implemented yet.
author Goffi <goffi@goffi.org>
date Sun, 24 Jun 2018 22:26:15 +0200
parents 30be583dbabc
children 24f8ab7c08be
files cagou/core/utils.py cagou/plugins/plugin_wid_contact_list.kv cagou/plugins/plugin_wid_contact_list.py
diffstat 3 files changed, 112 insertions(+), 57 deletions(-) [+]
line wrap: on
line diff
--- a/cagou/core/utils.py	Sun Jun 24 22:09:49 2018 +0200
+++ b/cagou/core/utils.py	Sun Jun 24 22:26:15 2018 +0200
@@ -34,7 +34,8 @@
                                       opacity = 0,
                                       d = 0.5)
 
-    def do_filter(self, children, text, get_child_text, width_cb, height_cb, continue_tests=None):
+    def do_filter(self, children, text, get_child_text, width_cb, height_cb,
+                  continue_tests=None):
         """filter the children
 
         filtered children will have a animation to set width, height and opacity to 0
--- a/cagou/plugins/plugin_wid_contact_list.kv	Sun Jun 24 22:09:49 2018 +0200
+++ b/cagou/plugins/plugin_wid_contact_list.kv	Sun Jun 24 22:26:15 2018 +0200
@@ -14,22 +14,51 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-<ContactListView>:
-    row_height: dp(50)
+#:import _ sat.core.i18n._
 
 <ContactItem>:
-    padding: dp(10), dp(3)
-    size_hint: 1, None
-    height: dp(50)
+    size_hint: None, None
+    width: self.base_width
+    height: self.minimum_height
+    orientation: 'vertical'
     Avatar:
+        id: avatar
+        size_hint: 1, None
+        height: dp(60)
         source: root.data.get('avatar', app.default_avatar)
-        size_hint: None, 1
-        width: dp(60)
         allow_stretch: True
     Label:
         id: jid_label
-        padding: dp(5), 0
+        size_hint: None, None
+        text_size: root.base_width, None
+        size: self.texture_size
         text: root.jid
-        text_size: self.size
         bold: True
-        valign: "middle"
+        valign: 'middle'
+        halign: 'center'
+
+<ContactList>:
+    float_layout: float_layout
+    layout: layout
+    orientation: 'vertical'
+    BoxLayout:
+        size_hint: 1, None
+        height: dp(30)
+        Widget:
+        SymbolLabel:
+            symbol: 'plus-circled'
+            text: _("add a contact")
+        Widget:
+    FloatLayout:
+        id: float_layout
+        ScrollView:
+            size_hint: 1, 1
+            pos_hint: {'x': 0, 'y': 0}
+            do_scroll_x: False
+            scroll_type: ['bars', 'content']
+            bar_width: dp(6)
+            StackLayout:
+                id: layout
+                size_hint: 1, None
+                height: self.minimum_height
+                spacing: 0
--- a/cagou/plugins/plugin_wid_contact_list.py	Sun Jun 24 22:09:49 2018 +0200
+++ b/cagou/plugins/plugin_wid_contact_list.py	Sun Jun 24 22:26:15 2018 +0200
@@ -20,17 +20,19 @@
 
 from sat.core import log as logging
 log = logging.getLogger(__name__)
+from cagou.core.constants import Const as C
 from sat.core.i18n import _
 from sat_frontends.quick_frontend.quick_contact_list import QuickContactList
 from sat_frontends.tools import jid
 from kivy.uix.boxlayout import BoxLayout
-from kivy.uix.listview import ListView
-from kivy.adapters.listadapter import ListAdapter
+from kivy.uix.behaviors import ButtonBehavior
+from cagou.core.utils import FilterBehavior
 from kivy.metrics import dp
 from kivy import properties
 from cagou.core import cagou_widget
 from cagou.core import image
 from cagou import G
+import bisect
 
 
 PLUGIN_INFO = {
@@ -45,66 +47,89 @@
     pass
 
 
-class ContactItem(BoxLayout):
+class ContactItem(ButtonBehavior, BoxLayout):
+    base_width = dp(150)
     data = properties.DictProperty()
     jid = properties.StringProperty('')
 
     def __init__(self, **kwargs):
         super(ContactItem, self).__init__(**kwargs)
 
-    def on_touch_down(self, touch):
-        if self.collide_point(*touch.pos):
-            # XXX: for now clicking on an item launch the corresponding Chat widget
-            #      behaviour should change in the future
-            try:
-                # FIXME: Q&D way to get chat plugin, should be replaced by a clean method
-                #        in host
-                plg_infos = [p for p in G.host.getPluggedWidgets() if 'chat' in p['import_name']][0]
-            except IndexError:
-                log.warning(u"No plugin widget found to display chat")
-            else:
-                factory = plg_infos['factory']
-                G.host.switchWidget(self, factory(plg_infos, jid.JID(self.jid), profiles=iter(G.host.profiles)))
+    def on_release(self):
+        # XXX: for now clicking on an item launch the corresponding Chat widget
+        #      behaviour should change in the future
+        try:
+            # FIXME: Q&D way to get chat plugin, should be replaced by a clean method
+            #        in host
+            plg_infos = [p for p in G.host.getPluggedWidgets()
+                         if 'chat' in p['import_name']][0]
+        except IndexError:
+            log.warning(u"No plugin widget found to display chat")
+        else:
+            factory = plg_infos['factory']
+            G.host.switchWidget(self, factory(plg_infos,
+                                              jid.JID(self.jid),
+                                              profiles=iter(G.host.profiles)))
 
 
-class ContactListView(ListView):
-    pass
-
-
-class ContactList(QuickContactList, cagou_widget.CagouWidget):
+class ContactList(QuickContactList, cagou_widget.CagouWidget, FilterBehavior):
+    float_layout = properties.ObjectProperty()
+    layout = properties.ObjectProperty()
 
     def __init__(self, host, target, profiles):
         QuickContactList.__init__(self, G.host, profiles)
         cagou_widget.CagouWidget.__init__(self)
-        self.adapter = ListAdapter(data={},
-                                   cls=ContactItem,
-                                   args_converter=self.contactDataConverter,
-                                   selection_mode='multiple',
-                                   allow_empty_selection=True,
-                                  )
-        self.add_widget(ContactListView(adapter=self.adapter))
+        FilterBehavior.__init__(self)
+        self._wid_map = {}  # (profile, bare_jid) to widget map
         self.postInit()
-        self.update()
+        if len(self.profiles) != 1:
+            raise NotImplementedError('multi profiles is not implemented yet')
+        self.update(profile=next(iter(self.profiles)))
 
     def onHeaderInputComplete(self, wid, text):
-        # FIXME: this is implementation dependent, need to be done properly
-        items = self.children[0].children[0].children[0].children
+        self.do_filter(self.layout.children,
+                       text,
+                       lambda c: c.jid,
+                       width_cb=lambda c: c.base_width,
+                       height_cb=lambda c: c.minimum_height,
+                       )
+
+    def _addContactItem(self, bare_jid, profile):
+        """Create a new ContactItem instance, and add it
 
-        for item in items:
-            if text not in item.ids.jid_label.text:
-                item.height = 0
-                item.opacity = 0
-            else:
-                item.height = dp(50)
-                item.opacity = 1
-
-    def contactDataConverter(self, idx, bare_jid):
-        return {"jid": bare_jid, "data": self._items_cache[bare_jid]}
+        item will be added in a sorted position
+        @param bare_jid(jid.JID): entity bare JID
+        @param profile(unicode): profile where the contact is
+        """
+        data = G.host.contact_lists[profile].getItem(bare_jid)
+        wid = ContactItem(data=data, jid=bare_jid)
+        child_jids = [c.jid for c in reversed(self.layout.children)]
+        idx = bisect.bisect_right(child_jids, bare_jid)
+        self.layout.add_widget(wid, -idx)
+        self._wid_map[(profile, bare_jid)] = wid
 
     def update(self, entities=None, type_=None, profile=None):
         log.debug("update: %s %s %s" % (entities, type_, profile))
-        # FIXME: for now we update on each event
-        # if entities is None and type_ is None:
-        self._items_cache = self.items_sorted
-        self.adapter.data = self.items_sorted.keys()
-
+        if type_ == None or type_ == C.UPDATE_STRUCTURE:
+            log.debug("full contact list update")
+            self.layout.clear_widgets()
+            for bare_jid, data in self.items_sorted.iteritems():
+                wid = ContactItem(data=data, jid=bare_jid)
+                self.layout.add_widget(wid)
+                self._wid_map[(profile, bare_jid)] = wid
+        elif type_ == C.UPDATE_MODIFY:
+            for entity in entities:
+                entity_bare = entity.bare
+                wid = self._wid_map[(profile, entity_bare)]
+                wid.data = G.host.contact_lists[profile].getItem(entity_bare)
+        elif type_ == C.UPDATE_ADD:
+            for entity in entities:
+                self._addContactItem(entity.bare, profile)
+        elif type_ == C.UPDATE_DELETE:
+            for entity in entities:
+                try:
+                    self.layout.remove_widget(self._wid_map.pop((profile, entity.bare)))
+                except KeyError:
+                    log.debug("entity not found: {entity}".format(entity=entity.bare))
+        else:
+            log.debug("update type not handled: {update_type}".format(update_type=type_))