# HG changeset patch # User Goffi # Date 1529871975 -7200 # Node ID 9faccd140119d8f63ebe51427b1d3a253e23f13b # Parent 30be583dbabc5582813cc91ae1fbef79e6990ea6 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. diff -r 30be583dbabc -r 9faccd140119 cagou/core/utils.py --- 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 diff -r 30be583dbabc -r 9faccd140119 cagou/plugins/plugin_wid_contact_list.kv --- 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 . -: - row_height: dp(50) +#:import _ sat.core.i18n._ : - 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' + +: + 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 diff -r 30be583dbabc -r 9faccd140119 cagou/plugins/plugin_wid_contact_list.py --- 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_))