diff cagou/core/common.py @ 404:f7476818f9fb

core (common): JidSelector + behaviors various improvments: - renamed *Behaviour => *Behavior to be consistent with Kivy + moved to new "core.behaviors" modules - use a dedicated property in ContactItem for notification counter (which is now named "badge") - in JidSelector, well-known strings now create use a dedicated layout, add separator (except if new `add_separators` property is set to False), and are added to attribute of the same name - a new `item_class` property is now used to indicate the class to instanciate for items (by default it's a ContactItem) - FilterBahavior.do_filter now expect the parent layout instead of directly the children, this is to allow a FilterBahavior to manage several children layout at once (used with JidSelector) - core.utils has been removed, as the behavior there has been moved to core.behaviors
author Goffi <goffi@goffi.org>
date Wed, 12 Feb 2020 20:02:58 +0100
parents 54f6a47cc60a
children 84ff5c917064
line wrap: on
line diff
--- a/cagou/core/common.py	Wed Feb 12 20:02:58 2020 +0100
+++ b/cagou/core/common.py	Wed Feb 12 20:02:58 2020 +0100
@@ -27,13 +27,15 @@
 from kivy.uix.label import Label
 from kivy.uix.behaviors import ButtonBehavior
 from kivy.uix.behaviors import ToggleButtonBehavior
+from kivy.uix.stacklayout import StackLayout
 from kivy.uix.boxlayout import BoxLayout
 from kivy.uix.scrollview import ScrollView
 from kivy.event import EventDispatcher
 from kivy.metrics import dp
 from kivy import properties
 from sat_frontends.quick_frontend import quick_chat
-from cagou.core.constants import Const as C
+from .constants import Const as C
+from .common_widgets import CategorySeparator
 from cagou import G
 
 log = logging.getLogger(__name__)
@@ -63,17 +65,26 @@
     base_width = dp(150)
     avatar_layout = properties.ObjectProperty()
     avatar = properties.ObjectProperty()
+    badge = properties.ObjectProperty(allownone=True)
+    badge_text = properties.StringProperty('')
     profile = properties.StringProperty()
     data = properties.DictProperty()
     jid = properties.StringProperty('')
 
-    def on_kv_post(self, __):
-        if self.data and self.data.get('notifs'):
-            notif = NotifLabel(
-                pos_hint={"right": 0.8, "y": 0},
-                text=str(len(self.data['notifs']))
-            )
-            self.avatar_layout.add_widget(notif)
+    def on_badge_text(self, wid, text):
+        if text:
+            if self.badge is not None:
+                self.badge.text = text
+            else:
+                self.badge = NotifLabel(
+                    pos_hint={"right": 0.8, "y": 0},
+                    text=text,
+                )
+                self.avatar_layout.add_widget(self.badge)
+        else:
+            if self.badge is not None:
+                self.avatar_layout.remove_widget(self.badge)
+                self.badge = None
 
 
 class ContactButton(ButtonBehavior, ContactItem):
@@ -174,12 +185,25 @@
             self.add_widget(icon_wid)
 
 
+class JidSelectorCategoryLayout(StackLayout):
+    pass
+
+
 class JidSelector(ScrollView, EventDispatcher):
     layout = properties.ObjectProperty(None)
+    # if item_class is changed, the properties must be the same as for ContactButton
+    # and ordering must be supported
+    item_class = properties.ObjectProperty(ContactButton)
+    add_separators = properties.ObjectProperty(True)
     # list of item to show, can be:
-    #    - a well-known string like:
-    #       * "roster": to show all roster jids
-    #       * "opened_chats": to show jids of all opened chat widgets
+    #    - a well-known string which can be:
+    #       * "roster": all roster jids
+    #       * "opened_chats": all opened chat widgets
+    #       * "bookmarks": MUC bookmarks
+    #       A layout will be created each time and stored in the attribute of the same
+    #       name.
+    #       If add_separators is True, a CategorySeparator will be added on top of each
+    #       layout.
     #    - a kivy Widget, which will be added to the layout (notable useful with
     #      common_widgets.CategorySeparator)
     #    - a callable, which must return an iterable of kwargs for ContactButton
@@ -190,6 +214,10 @@
 
     def __init__(self, **kwargs):
         self.register_event_type('on_select')
+        # list of layouts containing items
+        self.items_layouts = []
+        # jid to list of ContactButton instances map
+        self.items_map = {}
         super().__init__(**kwargs)
 
     def on_kv_post(self, wid):
@@ -210,6 +238,13 @@
         log.debug("onContactsFilled event received")
         self.update()
 
+
+    def _createItem(self, **kwargs):
+        item = self.item_class(**kwargs)
+        jid = kwargs['jid']
+        self.items_map.setdefault(jid, []).append(item)
+        return item
+
     def update(self):
         log.debug("starting update")
         self.layout.clear_widgets()
@@ -228,52 +263,69 @@
             elif callable(item):
                 items_kwargs = item()
                 for item_kwargs in items_kwargs:
-                    item = ContactButton(**item_kwargs)
+                    item = self._createItem(**items_kwargs)
                     item.bind(on_press=partial(self.dispatch, 'on_select'))
                     self.layout.add_widget(item)
             else:
                 log.error(f"unmanaged to_show item type: {item!r}")
 
-    def addOpenedChatsItems(self):
-        opened_chats = G.host.widgets.getWidgets(
-            quick_chat.QuickChat,
-            profiles = G.host.profiles)
+    def addCategoryLayout(self, label=None):
+        category_layout = JidSelectorCategoryLayout()
+
+        if label and self.add_separators:
+            category_layout.add_widget(CategorySeparator(text=label))
+
+        self.layout.add_widget(category_layout)
+        self.items_layouts.append(category_layout)
+        return category_layout
 
-        for wid in opened_chats:
-            contact_list = G.host.contact_lists[wid.profile]
-            data=contact_list.getItem(wid.target)
-            notifs = list(G.host.getNotifs(wid.target, profile=wid.profile))
-            if notifs:
-                # we shallow copy the dict to have the notification displayed only with
-                # opened chats (otherwise, the counter would appear on each other
-                # instance of ContactButton for this entity, i.e. in roster too).
-                data = data.copy()
-                data['notifs'] = notifs
-            try:
-                item = ContactButton(
-                    jid=wid.target,
-                    data=data,
-                    profile=wid.profile,
-                )
-            except Exception as e:
-                log.warning(f"Can't add contact {wid.target}: {e}")
+    def getItemFromWid(self, wid):
+        """create JidSelector item from QuickChat widget"""
+        contact_list = G.host.contact_lists[wid.profile]
+        data=contact_list.getItem(wid.target)
+        try:
+            item = self._createItem(
+                jid=wid.target,
+                data=data,
+                profile=wid.profile,
+            )
+        except Exception as e:
+            log.warning(f"Can't add contact {wid.target}: {e}")
+            return
+        notifs = list(G.host.getNotifs(wid.target, profile=wid.profile))
+        if notifs:
+            item.badge_text = str(len(notifs))
+        item.bind(on_press=partial(self.dispatch, 'on_select'))
+        return item
+
+    def addOpenedChatsItems(self):
+        self.opened_chats = category_layout = self.addCategoryLayout(_("Opened chats"))
+        widgets = sorted(G.host.widgets.getWidgets(
+            quick_chat.QuickChat,
+            profiles = G.host.profiles,
+            with_duplicates=False))
+
+        for wid in widgets:
+            item = self.getItemFromWid(wid)
+            if item is None:
                 continue
-            item.bind(on_press=partial(self.dispatch, 'on_select'))
-            self.layout.add_widget(item)
+            category_layout.add_widget(item)
 
     def addRosterItems(self):
+        self.roster = category_layout = self.addCategoryLayout(_("Your contacts"))
         for profile in G.host.profiles:
             contact_list = G.host.contact_lists[profile]
             for entity_jid in sorted(contact_list.roster):
-                item = ContactButton(
+                item = self._createItem(
                     jid=entity_jid,
                     data=contact_list.getItem(entity_jid),
                     profile=profile,
                 )
                 item.bind(on_press=partial(self.dispatch, 'on_select'))
-                self.layout.add_widget(item)
+                category_layout.add_widget(item)
 
     def addBookmarksItems(self):
+        self.bookmarks = category_layout = self.addCategoryLayout(_("Your chat rooms"))
         for profile in G.host.profiles:
             profile_manager = G.host.profiles[profile]
             try:
@@ -288,10 +340,10 @@
                     cache = contact_list.getItem(entity_jid)
                 except KeyError:
                     cache = {}
-                item = ContactButton(
+                item = self._createItem(
                     jid=entity_jid,
                     data=cache,
                     profile=profile,
                 )
                 item.bind(on_press=partial(self.dispatch, 'on_select'))
-                self.layout.add_widget(item)
+                category_layout.add_widget(item)