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