comparison 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
comparison
equal deleted inserted replaced
1123:63a4b8fe9782 1124:28e3eb3bb217
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 # Libervia: a Salut à Toi frontend
5 # Copyright (C) 2011-2018 Jérôme Poisson <goffi@goffi.org>
6
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
16
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20 import pyjd # this is dummy in pyjs
21 from sat.core.log import getLogger
22 log = getLogger(__name__)
23 from sat_frontends.quick_frontend.quick_contact_list import QuickContactList
24 from pyjamas.ui.SimplePanel import SimplePanel
25 from pyjamas.ui.ScrollPanel import ScrollPanel
26 from pyjamas.ui.VerticalPanel import VerticalPanel
27 from pyjamas.ui.ClickListener import ClickHandler
28 from pyjamas.ui.Label import Label
29 from pyjamas import Window
30 from pyjamas import DOM
31
32 from constants import Const as C
33 from sat_frontends.tools import jid
34 import libervia_widget
35 import contact_panel
36 import blog
37 import chat
38
39 unicode = str # XXX: pyjama doesn't manage unicode
40
41
42 class GroupLabel(libervia_widget.DragLabel, Label, ClickHandler):
43 def __init__(self, host, group):
44 """
45
46 @param host (SatWebFrontend)
47 @param group (unicode): group name
48 """
49 self.group = group
50 Label.__init__(self, group) # , Element=DOM.createElement('div')
51 self.setStyleName('group')
52 libervia_widget.DragLabel.__init__(self, group, "GROUP", host)
53 ClickHandler.__init__(self)
54 self.addClickListener(self)
55
56 def onClick(self, sender):
57 self.host.displayWidget(blog.Blog, (self.group,))
58
59
60 class GroupPanel(VerticalPanel):
61
62 def __init__(self, parent):
63 VerticalPanel.__init__(self)
64 self.setStyleName('groupPanel')
65 self._parent = parent
66 self._groups = set()
67
68 def add(self, group):
69 if group in self._groups:
70 log.warning("trying to add an already existing group")
71 return
72 _item = GroupLabel(self._parent.host, group)
73 _item.addMouseListener(self._parent)
74 DOM.setStyleAttribute(_item.getElement(), "cursor", "pointer")
75 index = 0
76 for group_ in [child.group for child in self.getChildren()]:
77 if group_ > group:
78 break
79 index += 1
80 VerticalPanel.insert(self, _item, index)
81 self._groups.add(group)
82
83 def remove(self, group):
84 for wid in self:
85 if isinstance(wid, GroupLabel) and wid.group == group:
86 VerticalPanel.remove(self, wid)
87 self._groups.remove(group)
88 return
89 log.warning("Trying to remove a non existent group")
90
91 def getGroupBox(self, group):
92 """get the widget of a group
93
94 @param group (unicode): the group
95 @return: GroupLabel instance if present, else None"""
96 for wid in self:
97 if isinstance(wid, GroupLabel) and wid.group == group:
98 return wid
99 return None
100
101 def getGroups(self):
102 return self._groups
103
104
105 class ContactTitleLabel(libervia_widget.DragLabel, Label, ClickHandler):
106
107 def __init__(self, host, text):
108 Label.__init__(self, text) # , Element=DOM.createElement('div')
109 self.setStyleName('contactTitle')
110 libervia_widget.DragLabel.__init__(self, text, "CONTACT_TITLE", host)
111 ClickHandler.__init__(self)
112 self.addClickListener(self)
113
114 def onClick(self, sender):
115 self.host.displayWidget(blog.Blog, ())
116
117
118 class ContactList(SimplePanel, QuickContactList):
119 """Manage the contacts and groups"""
120
121 def __init__(self, host, target, on_click=None, on_change=None, user_data=None, profiles=None):
122 QuickContactList.__init__(self, host, C.PROF_KEY_NONE)
123 self.contact_list = self.host.contact_list
124 SimplePanel.__init__(self)
125 self.host = host
126 self.scroll_panel = ScrollPanel()
127 self.scroll_panel.addStyleName("gwt-ScrollPanel") # XXX: no class is set by Pyjamas
128 self.vPanel = VerticalPanel()
129 _title = ContactTitleLabel(host, 'Contacts')
130 DOM.setStyleAttribute(_title.getElement(), "cursor", "pointer")
131
132 def on_click(contact_jid):
133 self.host.displayWidget(chat.Chat, contact_jid, type_=C.CHAT_ONE2ONE)
134 self.removeAlerts(contact_jid, True)
135
136 self._contacts_panel = contact_panel.ContactsPanel(host, contacts_click=on_click, contacts_menus=(C.MENU_JID_CONTEXT, C.MENU_ROSTER_JID_CONTEXT))
137 self._group_panel = GroupPanel(self)
138
139 self.vPanel.add(_title)
140 self.vPanel.add(self._group_panel)
141 self.vPanel.add(self._contacts_panel)
142 self.scroll_panel.add(self.vPanel)
143 self.add(self.scroll_panel)
144 self.setStyleName('contactList')
145 Window.addWindowResizeListener(self)
146
147 # 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)
148 self.avatarListener = self.onAvatarUpdate
149 host.addListener('avatar', self.avatarListener, [C.PROF_KEY_NONE])
150 self.postInit()
151
152 @property
153 def profile(self):
154 return C.PROF_KEY_NONE
155
156 def onDelete(self):
157 QuickContactList.onDelete(self)
158 self.host.removeListener('avatar', self.avatarListener)
159
160 def update(self, entities=None, type_=None, profile=None):
161 # XXX: as update is slow, we avoid many updates on profile plugs
162 # and do them all at once at the end
163 if not self.host._profile_plugged: # FIXME: should not be necessary anymore (done in QuickFrontend)
164 return
165 ### GROUPS ###
166 _keys = self.contact_list._groups.keys()
167 try:
168 # XXX: Pyjamas doesn't do the set casting if None is present
169 _keys.remove(None)
170 except (KeyError, ValueError): # XXX: error raised depend on pyjama's compilation options
171 pass
172 current_groups = set(_keys)
173 shown_groups = self._group_panel.getGroups()
174 new_groups = current_groups.difference(shown_groups)
175 removed_groups = shown_groups.difference(current_groups)
176 for group in new_groups:
177 self._group_panel.add(group)
178 for group in removed_groups:
179 self._group_panel.remove(group)
180
181 ### JIDS ###
182 to_show = [jid_ for jid_ in self.contact_list.roster if self.contact_list.entityVisible(jid_) and jid_ != self.contact_list.whoami.bare]
183 to_show.sort()
184
185 self._contacts_panel.setList(to_show)
186
187 def onWindowResized(self, width, height):
188 ideal_height = height - DOM.getAbsoluteTop(self.getElement()) - 5
189 tab_panel = self.host.panel.tab_panel
190 if tab_panel.getWidgetCount() > 1:
191 ideal_height -= tab_panel.getTabBar().getOffsetHeight()
192 self.scroll_panel.setHeight("%s%s" % (ideal_height, "px"))
193
194 def isContactInRoster(self, contact_jid):
195 """Test if the contact is in our roster list"""
196 for contact_box in self._contacts_panel:
197 if contact_jid == contact_box.jid:
198 return True
199 return False
200
201 def getGroups(self):
202 return set([g for g in self._groups if g is not None])
203
204 def onMouseMove(self, sender, x, y):
205 pass
206
207 def onMouseDown(self, sender, x, y):
208 pass
209
210 def onMouseUp(self, sender, x, y):
211 pass
212
213 def onMouseEnter(self, sender):
214 if isinstance(sender, GroupLabel):
215 jids = self.contact_list.getGroupData(sender.group, "jids")
216 for contact in self._contacts_panel.getBoxes():
217 if contact.jid in jids:
218 contact.label.addStyleName("selected")
219
220 def onMouseLeave(self, sender):
221 if isinstance(sender, GroupLabel):
222 jids = self.contact_list.getGroupData(sender.group, "jids")
223 for contact in self._contacts_panel.getBoxes():
224 if contact.jid in jids:
225 contact.label.removeStyleName("selected")
226
227 def onAvatarUpdate(self, jid_, hash_, profile):
228 """Called on avatar update events
229
230 @param jid_: jid of the entity with updated avatar
231 @param hash_: hash of the avatar
232 @param profile: %(doc_profile)s
233 """
234 box = self._contacts_panel.getContactBox(jid_)
235 if box:
236 box.update()
237
238 def onNickUpdate(self, jid_, new_nick, profile):
239 box = self._contacts_panel.getContactBox(jid_)
240 if box:
241 box.update()
242
243 def offlineContactsToShow(self):
244 """Tell if offline contacts should be visible according to the user settings
245
246 @return: boolean
247 """
248 return C.bool(self.host.getCachedParam('General', C.SHOW_OFFLINE_CONTACTS))
249
250 def emtyGroupsToShow(self):
251 """Tell if empty groups should be visible according to the user settings
252
253 @return: boolean
254 """
255 return C.bool(self.host.getCachedParam('General', C.SHOW_EMPTY_GROUPS))
256
257 def onPresenceUpdate(self, entity, show, priority, statuses, profile):
258 QuickContactList.onPresenceUpdate(self, entity, show, priority, statuses, profile)
259 box = self._contacts_panel.getContactBox(entity)
260 if box: # box doesn't exist for MUC bare entity, don't create it
261 box.update()
262
263
264 class JIDList(list):
265 """JID-friendly list implementation for Pyjamas"""
266
267 def __contains__(self, item):
268 """Tells if the list contains the given item.
269
270 @param item (object): element to check
271 @return: bool
272 """
273 # Since our JID doesn't inherit from str/unicode, without this method
274 # the test would return True only when the objects references are the
275 # same. Tests have shown that the other iterable "set" and "dict" don't
276 # need this hack to reproduce the Twisted's behavior.
277 for other in self:
278 if other == item:
279 return True
280 return False
281
282 def index(self, item):
283 i = 0
284 for other in self:
285 if other == item:
286 return i
287 i += 1
288 raise ValueError("JIDList.index(%(item)s): %(item)s not in list" % {"item": item})
289
290 class JIDSet(set):
291 """JID set implementation for Pyjamas"""
292
293 def __contains__(self, item):
294 return __containsJID(self, item)
295
296
297 class JIDDict(dict):
298 """JID dict implementation for Pyjamas (a dict with JID keys)"""
299
300 def __contains__(self, item):
301 return __containsJID(self, item)
302
303 def keys(self):
304 return JIDSet(dict.keys(self))
305
306
307 def __containsJID(iterable, item):
308 """Tells if the given item is in the iterable, works with JID.
309
310 @param iterable(object): list, set or another iterable object
311 @param item (object): element
312 @return: bool
313 """
314 # Pyjamas JID-friendly implementation of the "in" operator. Since our JID
315 # doesn't inherit from str, without this method the test would return True
316 # only when the objects references are the same.
317 if isinstance(item, jid.JID):
318 return hash(item) in [hash(other) for other in iterable if isinstance(other, jid.JID)]
319 return super(type(iterable), iterable).__contains__(iterable, item)