diff browser/sat_browser/contact_list.py @ 1124:28e3eb3bb217

files reorganisation and installation rework: - files have been reorganised to follow other SàT projects and usual Python organisation (no more "/src" directory) - VERSION file is now used, as for other SàT projects - replace the overcomplicated setup.py be a more sane one. Pyjamas part is not compiled anymore by setup.py, it must be done separatly - removed check for data_dir if it's empty - installation tested working in virtual env - libervia launching script is now in bin/libervia
author Goffi <goffi@goffi.org>
date Sat, 25 Aug 2018 17:59:48 +0200
parents src/browser/sat_browser/contact_list.py@f287fc8bb31a
children 2af117bfe6cc
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/browser/sat_browser/contact_list.py	Sat Aug 25 17:59:48 2018 +0200
@@ -0,0 +1,319 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Libervia: a Salut à Toi frontend
+# Copyright (C) 2011-2018 Jérôme Poisson <goffi@goffi.org>
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+
+# 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/>.
+
+import pyjd  # this is dummy in pyjs
+from sat.core.log import getLogger
+log = getLogger(__name__)
+from sat_frontends.quick_frontend.quick_contact_list import QuickContactList
+from pyjamas.ui.SimplePanel import SimplePanel
+from pyjamas.ui.ScrollPanel import ScrollPanel
+from pyjamas.ui.VerticalPanel import VerticalPanel
+from pyjamas.ui.ClickListener import ClickHandler
+from pyjamas.ui.Label import Label
+from pyjamas import Window
+from pyjamas import DOM
+
+from constants import Const as C
+from sat_frontends.tools import jid
+import libervia_widget
+import contact_panel
+import blog
+import chat
+
+unicode = str # XXX: pyjama doesn't manage unicode
+
+
+class GroupLabel(libervia_widget.DragLabel, Label, ClickHandler):
+    def __init__(self, host, group):
+        """
+
+        @param host (SatWebFrontend)
+        @param group (unicode): group name
+        """
+        self.group = group
+        Label.__init__(self, group)  # , Element=DOM.createElement('div')
+        self.setStyleName('group')
+        libervia_widget.DragLabel.__init__(self, group, "GROUP", host)
+        ClickHandler.__init__(self)
+        self.addClickListener(self)
+
+    def onClick(self, sender):
+        self.host.displayWidget(blog.Blog, (self.group,))
+
+
+class GroupPanel(VerticalPanel):
+
+    def __init__(self, parent):
+        VerticalPanel.__init__(self)
+        self.setStyleName('groupPanel')
+        self._parent = parent
+        self._groups = set()
+
+    def add(self, group):
+        if group in self._groups:
+            log.warning("trying to add an already existing group")
+            return
+        _item = GroupLabel(self._parent.host, group)
+        _item.addMouseListener(self._parent)
+        DOM.setStyleAttribute(_item.getElement(), "cursor", "pointer")
+        index = 0
+        for group_ in [child.group for child in self.getChildren()]:
+            if group_ > group:
+                break
+            index += 1
+        VerticalPanel.insert(self, _item, index)
+        self._groups.add(group)
+
+    def remove(self, group):
+        for wid in self:
+            if isinstance(wid, GroupLabel) and wid.group == group:
+                VerticalPanel.remove(self, wid)
+                self._groups.remove(group)
+                return
+        log.warning("Trying to remove a non existent group")
+
+    def getGroupBox(self, group):
+        """get the widget of a group
+
+        @param group (unicode): the group
+        @return: GroupLabel instance if present, else None"""
+        for wid in self:
+            if isinstance(wid, GroupLabel) and wid.group == group:
+                return wid
+        return None
+
+    def getGroups(self):
+        return self._groups
+
+
+class ContactTitleLabel(libervia_widget.DragLabel, Label, ClickHandler):
+
+    def __init__(self, host, text):
+        Label.__init__(self, text)  # , Element=DOM.createElement('div')
+        self.setStyleName('contactTitle')
+        libervia_widget.DragLabel.__init__(self, text, "CONTACT_TITLE", host)
+        ClickHandler.__init__(self)
+        self.addClickListener(self)
+
+    def onClick(self, sender):
+        self.host.displayWidget(blog.Blog, ())
+
+
+class ContactList(SimplePanel, QuickContactList):
+    """Manage the contacts and groups"""
+
+    def __init__(self, host, target, on_click=None, on_change=None, user_data=None, profiles=None):
+        QuickContactList.__init__(self, host, C.PROF_KEY_NONE)
+        self.contact_list = self.host.contact_list
+        SimplePanel.__init__(self)
+        self.host = host
+        self.scroll_panel = ScrollPanel()
+        self.scroll_panel.addStyleName("gwt-ScrollPanel")  # XXX: no class is set by Pyjamas
+        self.vPanel = VerticalPanel()
+        _title = ContactTitleLabel(host, 'Contacts')
+        DOM.setStyleAttribute(_title.getElement(), "cursor", "pointer")
+
+        def on_click(contact_jid):
+            self.host.displayWidget(chat.Chat, contact_jid, type_=C.CHAT_ONE2ONE)
+            self.removeAlerts(contact_jid, True)
+
+        self._contacts_panel = contact_panel.ContactsPanel(host, contacts_click=on_click, contacts_menus=(C.MENU_JID_CONTEXT, C.MENU_ROSTER_JID_CONTEXT))
+        self._group_panel = GroupPanel(self)
+
+        self.vPanel.add(_title)
+        self.vPanel.add(self._group_panel)
+        self.vPanel.add(self._contacts_panel)
+        self.scroll_panel.add(self.vPanel)
+        self.add(self.scroll_panel)
+        self.setStyleName('contactList')
+        Window.addWindowResizeListener(self)
+
+        # FIXME: workaround for a pyjamas issue: calling hash on a class method always return a different value if that method is defined directly within the class (with the "def" keyword)
+        self.avatarListener = self.onAvatarUpdate
+        host.addListener('avatar', self.avatarListener, [C.PROF_KEY_NONE])
+        self.postInit()
+
+    @property
+    def profile(self):
+        return C.PROF_KEY_NONE
+
+    def onDelete(self):
+        QuickContactList.onDelete(self)
+        self.host.removeListener('avatar', self.avatarListener)
+
+    def update(self, entities=None, type_=None, profile=None):
+        # XXX: as update is slow, we avoid many updates on profile plugs
+        # and do them all at once at the end
+        if not self.host._profile_plugged:  # FIXME: should not be necessary anymore (done in QuickFrontend)
+            return
+        ### GROUPS ###
+        _keys = self.contact_list._groups.keys()
+        try:
+            # XXX: Pyjamas doesn't do the set casting if None is present
+            _keys.remove(None)
+        except (KeyError, ValueError): # XXX: error raised depend on pyjama's compilation options
+            pass
+        current_groups = set(_keys)
+        shown_groups = self._group_panel.getGroups()
+        new_groups = current_groups.difference(shown_groups)
+        removed_groups = shown_groups.difference(current_groups)
+        for group in new_groups:
+            self._group_panel.add(group)
+        for group in removed_groups:
+            self._group_panel.remove(group)
+
+        ### JIDS ###
+        to_show = [jid_ for jid_ in self.contact_list.roster if self.contact_list.entityVisible(jid_) and jid_ != self.contact_list.whoami.bare]
+        to_show.sort()
+
+        self._contacts_panel.setList(to_show)
+
+    def onWindowResized(self, width, height):
+        ideal_height = height - DOM.getAbsoluteTop(self.getElement()) - 5
+        tab_panel = self.host.panel.tab_panel
+        if tab_panel.getWidgetCount() > 1:
+            ideal_height -= tab_panel.getTabBar().getOffsetHeight()
+        self.scroll_panel.setHeight("%s%s" % (ideal_height, "px"))
+
+    def isContactInRoster(self, contact_jid):
+        """Test if the contact is in our roster list"""
+        for contact_box in self._contacts_panel:
+            if contact_jid == contact_box.jid:
+                return True
+        return False
+
+    def getGroups(self):
+        return set([g for g in self._groups if g is not None])
+
+    def onMouseMove(self, sender, x, y):
+        pass
+
+    def onMouseDown(self, sender, x, y):
+        pass
+
+    def onMouseUp(self, sender, x, y):
+        pass
+
+    def onMouseEnter(self, sender):
+        if isinstance(sender, GroupLabel):
+            jids = self.contact_list.getGroupData(sender.group, "jids")
+            for contact in self._contacts_panel.getBoxes():
+                if contact.jid in jids:
+                    contact.label.addStyleName("selected")
+
+    def onMouseLeave(self, sender):
+        if isinstance(sender, GroupLabel):
+            jids = self.contact_list.getGroupData(sender.group, "jids")
+            for contact in self._contacts_panel.getBoxes():
+                if contact.jid in jids:
+                    contact.label.removeStyleName("selected")
+
+    def onAvatarUpdate(self, jid_, hash_, profile):
+        """Called on avatar update events
+
+        @param jid_: jid of the entity with updated avatar
+        @param hash_: hash of the avatar
+        @param profile: %(doc_profile)s
+        """
+        box = self._contacts_panel.getContactBox(jid_)
+        if box:
+            box.update()
+
+    def onNickUpdate(self, jid_, new_nick, profile):
+        box = self._contacts_panel.getContactBox(jid_)
+        if box:
+            box.update()
+
+    def offlineContactsToShow(self):
+        """Tell if offline contacts should be visible according to the user settings
+
+        @return: boolean
+        """
+        return C.bool(self.host.getCachedParam('General', C.SHOW_OFFLINE_CONTACTS))
+
+    def emtyGroupsToShow(self):
+        """Tell if empty groups should be visible according to the user settings
+
+        @return: boolean
+        """
+        return C.bool(self.host.getCachedParam('General', C.SHOW_EMPTY_GROUPS))
+
+    def onPresenceUpdate(self, entity, show, priority, statuses, profile):
+        QuickContactList.onPresenceUpdate(self, entity, show, priority, statuses, profile)
+        box = self._contacts_panel.getContactBox(entity)
+        if box:  # box doesn't exist for MUC bare entity, don't create it
+            box.update()
+
+
+class JIDList(list):
+    """JID-friendly list implementation for Pyjamas"""
+
+    def __contains__(self, item):
+        """Tells if the list contains the given item.
+
+        @param item (object): element to check
+        @return: bool
+        """
+        # Since our JID doesn't inherit from str/unicode, without this method
+        # the test would return True only when the objects references are the
+        # same. Tests have shown that the other iterable "set" and "dict" don't
+        # need this hack to reproduce the Twisted's behavior.
+        for other in self:
+            if other == item:
+                return True
+        return False
+
+    def index(self, item):
+        i = 0
+        for other in self:
+            if other == item:
+                return i
+            i += 1
+        raise ValueError("JIDList.index(%(item)s): %(item)s not in list" % {"item": item})
+
+class JIDSet(set):
+    """JID set implementation for Pyjamas"""
+
+    def __contains__(self, item):
+        return __containsJID(self, item)
+
+
+class JIDDict(dict):
+    """JID dict implementation for Pyjamas (a dict with JID keys)"""
+
+    def __contains__(self, item):
+        return __containsJID(self, item)
+
+    def keys(self):
+        return JIDSet(dict.keys(self))
+
+
+def __containsJID(iterable, item):
+    """Tells if the given item is in the iterable, works with JID.
+
+    @param iterable(object): list, set or another iterable object
+    @param item (object): element
+    @return: bool
+    """
+    # Pyjamas JID-friendly implementation of the "in" operator. Since our JID
+    # doesn't inherit from str, without this method the test would return True
+    # only when the objects references are the same.
+    if isinstance(item, jid.JID):
+        return hash(item) in [hash(other) for other in iterable if isinstance(other, jid.JID)]
+    return super(type(iterable), iterable).__contains__(iterable, item)