Mercurial > libervia-web
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)