Mercurial > libervia-backend
comparison frontends/src/quick_frontend/quick_contact_list.py @ 1265:e3a9ea76de35 frontends_multi_profiles
quick_frontend, primitivus: multi-profiles refactoring part 1 (big commit, sorry :p):
This refactoring allow primitivus to manage correctly several profiles at once, with various other improvments:
- profile_manager can now plug several profiles at once, requesting password when needed. No more profile plug specific method is used anymore in backend, instead a "validated" key is used in actions
- Primitivus widget are now based on a common "PrimitivusWidget" classe which mainly manage the decoration so far
- all widgets are treated in the same way (contactList, Chat, Progress, etc), no more chat_wins specific behaviour
- widgets are created in a dedicated manager, with facilities to react on new widget creation or other events
- quick_frontend introduce a new QuickWidget class, which aims to be as generic and flexible as possible. It can manage several targets (jids or something else), and several profiles
- each widget class return a Hash according to its target. For example if given a target jid and a profile, a widget class return a hash like (target.bare, profile), the same widget will be used for all resources of the same jid
- better management of CHAT_GROUP mode for Chat widgets
- some code moved from Primitivus to QuickFrontend, the final goal is to have most non backend code in QuickFrontend, and just graphic code in subclasses
- no more (un)escapePrivate/PRIVATE_PREFIX
- contactList improved a lot: entities not in roster and special entities (private MUC conversations) are better managed
- resources can be displayed in Primitivus, and their status messages
- profiles are managed in QuickFrontend with dedicated managers
This is work in progress, other frontends are broken. Urwid SàText need to be updated. Most of features of Primitivus should work as before (or in a better way ;))
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 10 Dec 2014 19:00:09 +0100 |
parents | 3abc6563a0d2 |
children | faa1129559b8 |
comparison
equal
deleted
inserted
replaced
1264:60dfa2f5d61f | 1265:e3a9ea76de35 |
---|---|
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. |
19 | 19 |
20 from sat.core.i18n import _ | 20 from sat.core.i18n import _ |
21 from sat.core.log import getLogger | 21 from sat.core.log import getLogger |
22 log = getLogger(__name__) | 22 log = getLogger(__name__) |
23 | 23 from sat_frontends.quick_frontend.quick_widgets import QuickWidget |
24 | 24 from sat_frontends.quick_frontend.constants import Const as C |
25 class QuickContactList(object): | 25 from sat_frontends.tools import jid |
26 | |
27 | |
28 class QuickContactList(QuickWidget): | |
26 """This class manage the visual representation of contacts""" | 29 """This class manage the visual representation of contacts""" |
27 | 30 |
28 def __init__(self): | 31 def __init__(self, host, profile): |
29 log.debug(_("Contact List init")) | 32 log.debug(_("Contact List init")) |
33 super(QuickContactList, self).__init__(host, profile, profile) | |
34 # bare jids as keys, resources are used in data | |
30 self._cache = {} | 35 self._cache = {} |
31 self.specials={} | 36 |
32 | 37 # special entities (groupchat, gateways, etc), bare jids |
33 def update_jid(self, jid): | 38 self._specials = set() |
34 """Update the jid in the list when something changed""" | 39 # extras are specials with full jids (e.g.: private MUC conversation) |
40 self._special_extras = set() | |
41 | |
42 # group data contain jids in groups and misc frontend data | |
43 self._groups = {} # groups to group data map | |
44 | |
45 # contacts in roster (bare jids) | |
46 self._roster = set() | |
47 | |
48 # entities with an alert (usually a waiting message), full jid | |
49 self._alerts = set() | |
50 | |
51 # selected entities, full jid | |
52 self._selected = set() | |
53 | |
54 # options | |
55 self.show_disconnected = False | |
56 self.show_empty_groups = True | |
57 self.show_resources = False | |
58 self.show_status = False | |
59 # TODO: this may lead to two successive UI refresh and needs an optimization | |
60 self.host.bridge.asyncGetParamA(C.SHOW_EMPTY_GROUPS, "General", profile_key=profile, callback=self.showEmptyGroups) | |
61 self.host.bridge.asyncGetParamA(C.SHOW_OFFLINE_CONTACTS, "General", profile_key=profile, callback=self.showOfflineContacts) | |
62 | |
63 def __contains__(self, entity): | |
64 """Check if entity is in contact list | |
65 | |
66 @param entity (jid.JID): jid of the entity (resource is not ignored, use bare jid if needed) | |
67 """ | |
68 if entity.resource: | |
69 try: | |
70 return entity.resource in self.getCache(entity.bare, C.CONTACT_RESOURCES) | |
71 except KeyError: | |
72 return False | |
73 return entity in self._cache | |
74 | |
75 def fill(self): | |
76 """Get all contacts from backend, and fill the widget""" | |
77 def gotContacts(contacts): | |
78 for contact in contacts: | |
79 self.host.newContactHandler(*contact, profile=self.profile) | |
80 | |
81 presences = self.host.bridge.getPresenceStatuses(self.profile) | |
82 for contact in presences: | |
83 for res in presences[contact]: | |
84 jabber_id = ('%s/%s' % (jid.JID(contact).bare, res)) if res else contact | |
85 show = presences[contact][res][0] | |
86 priority = presences[contact][res][1] | |
87 statuses = presences[contact][res][2] | |
88 self.host.presenceUpdateHandler(jabber_id, show, priority, statuses, self.profile) | |
89 data = self.host.bridge.getEntityData(contact, ['avatar', 'nick'], self.profile) | |
90 for key in ('avatar', 'nick'): | |
91 if key in data: | |
92 self.host.entityDataUpdatedHandler(contact, key, data[key], self.profile) | |
93 self.host.bridge.getContacts(self.profile, callback=gotContacts) | |
94 | |
95 def update(self): | |
96 """Update the display when something changed""" | |
35 raise NotImplementedError | 97 raise NotImplementedError |
36 | 98 |
37 def getCache(self, jid, name): | 99 def getCache(self, entity, name=None): |
100 """Return a cache value for a contact | |
101 | |
102 @param entity(entity.entity): entity of the contact from who we want data (resource is used if given) | |
103 if a resource specific information is requested: | |
104 - if no resource is given (bare jid), the main resource is used, according to priority | |
105 - if resource is given, it is used | |
106 @param name(unicode): name the data to get, or None to get everything | |
107 """ | |
108 cache = self._cache[entity.bare] | |
109 if name is None: | |
110 return cache | |
38 try: | 111 try: |
39 jid_cache = self._cache[jid.bare] | 112 if name in ('status', C.PRESENCE_STATUSES, C.PRESENCE_PRIORITY, C.PRESENCE_SHOW): |
40 if name == 'status': #XXX: we get the first status for 'status' key | 113 # these data are related to the resource |
41 return jid_cache['statuses'].get('default','') | 114 if not entity.resource: |
42 return jid_cache[name] | 115 main_resource = cache[C.CONTACT_MAIN_RESOURCE] |
43 except (KeyError, IndexError): | 116 cache = cache[C.CONTACT_RESOURCES][main_resource] |
117 else: | |
118 cache = cache[C.CONTACT_RESOURCES][entity.resource] | |
119 | |
120 if name == 'status': #XXX: we get the first status for 'status' key | |
121 # TODO: manage main language for statuses | |
122 return cache[C.PRESENCE_STATUSES].get('default','') | |
123 | |
124 return cache[name] | |
125 except KeyError: | |
44 return None | 126 return None |
45 | 127 |
46 def setCache(self, jid, name, value): | 128 def setCache(self, entity, name, value): |
47 jid_cache = self._cache.setdefault(jid.bare, {}) | 129 """Set or update value for one data in cache |
48 jid_cache[name] = value | 130 |
49 | 131 @param entity(JID): entity to update |
50 def __contains__(self, jid): | 132 @param name(unicode): value to set or update |
51 raise NotImplementedError | 133 """ |
134 self.setContact(entity, None, {name: value}) | |
135 | |
136 def setGroupData(self, group, name, value): | |
137 """Register a data for a group | |
138 | |
139 @param group: a valid (existing) group name | |
140 @param name: name of the data (can't be "jids") | |
141 @param value: value to set | |
142 """ | |
143 assert name is not 'jids' | |
144 self._groups[group][name] = value | |
145 | |
146 def getGroupData(self, group, name=None): | |
147 """Return value associated to group data | |
148 | |
149 @param group: a valid (existing) group name | |
150 @param name: name of the data or None to get the whole dict | |
151 @return: registered value | |
152 """ | |
153 if name is None: | |
154 return self._groups[group] | |
155 return self._groups[group][name] | |
156 | |
157 def setSpecial(self, entity, special_type): | |
158 """Set special flag on an entity | |
159 | |
160 @param entity(jid.JID): jid of the special entity | |
161 @param special_type: one of special type (e.g. C.CONTACT_SPECIAL_GROUP) or None to remove special flag | |
162 """ | |
163 assert special_type in C.CONTACT_SPECIAL_ALLOWED + (None,) | |
164 self.setCache(entity, C.CONTACT_SPECIAL, special_type) | |
52 | 165 |
53 def clearContacts(self): | 166 def clearContacts(self): |
54 """Clear all the contact list""" | 167 """Clear all the contact list""" |
55 self.specials.clear() | 168 self.unselectAll() |
56 | 169 self._cache.clear() |
57 def replace(self, jid, groups=None, attributes=None): | 170 self._groups.clear() |
171 self._specials.clear() | |
172 self.update() | |
173 | |
174 def setContact(self, entity, groups=None, attributes=None, in_roster=False): | |
58 """Add a contact to the list if doesn't exist, else update it. | 175 """Add a contact to the list if doesn't exist, else update it. |
59 | 176 |
60 This method can be called with groups=None for the purpose of updating | 177 This method can be called with groups=None for the purpose of updating |
61 the contact's attributes (e.g. nickname). In that case, the groups | 178 the contact's attributes (e.g. nickname). In that case, the groups |
62 attribute must not be set to the default group but ignored. If not, | 179 attribute must not be set to the default group but ignored. If not, |
63 you may move your contact from its actual group(s) to the default one. | 180 you may move your contact from its actual group(s) to the default one. |
64 | 181 |
65 None value for 'groups' has a different meaning than [None] which is for the default group. | 182 None value for 'groups' has a different meaning than [None] which is for the default group. |
66 | 183 |
67 @param jid (JID) | 184 @param entity (jid.JID): entity to add or replace |
68 @param groups (list): list of groups or None to ignore the groups membership. | 185 @param groups (list): list of groups or None to ignore the groups membership. |
69 @param attributes (dict) | 186 @param attributes (dict): attibutes of the added jid or to update |
70 """ | 187 @param in_roster (bool): True if contact is from roster |
71 if attributes and 'name' in attributes: | 188 """ |
72 self.setCache(jid, 'name', attributes['name']) | 189 if attributes is None: |
73 | 190 attributes = {} |
74 def remove(self, jid): | 191 |
75 """remove a contact from the list""" | 192 entity_bare = entity.bare |
193 | |
194 if in_roster: | |
195 self._roster.add(entity_bare) | |
196 | |
197 cache = self._cache.setdefault(entity_bare, {C.CONTACT_RESOURCES: {}}) | |
198 | |
199 assert not C.CONTACT_DATA_FORBIDDEN.intersection(attributes) # we don't want forbidden data in attributes | |
200 | |
201 # we set groups and fill self._groups accordingly | |
202 if groups is not None: | |
203 if not groups: | |
204 groups = [None] # [None] is the default group | |
205 cache[C.CONTACT_GROUPS] = groups | |
206 for group in groups: | |
207 self._groups.setdefault(group, {}).setdefault('jids', set()).add(entity_bare) | |
208 | |
209 # special entities management | |
210 if C.CONTACT_SPECIAL in attributes: | |
211 if attributes[C.CONTACT_SPECIAL] is None: | |
212 del attributes[C.CONTACT_SPECIAL] | |
213 self._specials.remove(entity_bare) | |
214 else: | |
215 self._specials.add(entity_bare) | |
216 | |
217 # now the attribute we keep in cache | |
218 for attribute, value in attributes.iteritems(): | |
219 cache[attribute] = value | |
220 | |
221 # we can update the display | |
222 self.update() | |
223 | |
224 def getContacts(self): | |
225 """Return contacts currently selected | |
226 | |
227 @return (set): set of selected entities""" | |
228 return self._selected | |
229 | |
230 def entityToShow(self, entity, check_resource=False): | |
231 """Tell if the contact should be showed or hidden. | |
232 | |
233 @param contact (jid.JID): jid of the contact | |
234 @param check_resource (bool): True if resource must be significant | |
235 @return: True if that contact should be showed in the list | |
236 """ | |
237 show = self.getCache(entity, C.PRESENCE_SHOW) | |
238 | |
239 if check_resource: | |
240 alerts = self._alerts | |
241 selected = self._selected | |
242 else: | |
243 alerts = {alert.bare for alert in self._alerts} | |
244 selected = {selected.bare for selected in self._selected} | |
245 return ((show is not None and show != "unavailable") | |
246 or self.show_disconnected | |
247 or entity in alerts | |
248 or entity in selected) | |
249 | |
250 def anyEntityToShow(self, entities, check_resources=False): | |
251 """Tell if in a list of entities, at least one should be shown | |
252 | |
253 @param entities (list[jid.JID]): list of jids | |
254 @param check_resources (bool): True if resources must be significant | |
255 @return: bool | |
256 """ | |
257 for entity in entities: | |
258 if self.entityToShow(entity, check_resources): | |
259 return True | |
260 return False | |
261 | |
262 def remove(self, entity): | |
263 """remove a contact from the list | |
264 | |
265 @param entity(jid.JID): jid of the entity to remove (bare jid is used) | |
266 """ | |
267 entity_bare = entity.bare | |
76 try: | 268 try: |
77 del self.specials[jid.bare] | 269 groups = self._cache[entity_bare].get(C.CONTACT_GROUPS, set()) |
78 except KeyError: | 270 except KeyError: |
79 pass | 271 log.warning(_("Trying to delete an unknow entity [{}]").format(entity)) |
272 del self._cache[entity_bare] | |
273 for group in groups: | |
274 self._groups[group]['jids'].remove(entity_bare) | |
275 for set_ in (self._selected, self._alerts, self._specials, self._special_extras): | |
276 to_remove = set() | |
277 for set_entity in set_: | |
278 if set_entity.bare == entity.bare: | |
279 to_remove.add(set_entity) | |
280 set_.difference_update(to_remove) | |
281 self.update() | |
80 | 282 |
81 def add(self, jid, param_groups=None): | 283 def add(self, jid, param_groups=None): |
82 """add a contact to the list""" | 284 """add a contact to the list""" |
83 raise NotImplementedError | 285 raise NotImplementedError |
84 | 286 |
85 def getSpecial(self, jid): | 287 def updatePresence(self, entity, show, priority, statuses): |
86 """Return special type of jid, or None if it's not special""" | |
87 return self.specials.get(jid.bare) | |
88 | |
89 def setSpecial(self, jid, _type, show=False): | |
90 """Set entity as a special | |
91 @param jid: jid of the entity | |
92 @param _type: special type (e.g.: "MUC") | |
93 @param show: True to display the dialog to chat with this entity | |
94 """ | |
95 self.specials[jid.bare] = _type | |
96 | |
97 def updatePresence(self, jid, show, priority, statuses): | |
98 """Update entity's presence status | 288 """Update entity's presence status |
99 @param jid: entity to update's jid | 289 |
290 @param entity(jid.JID): entity to update's entity | |
100 @param show: availability | 291 @param show: availability |
101 @parap priority: resource's priority | 292 @parap priority: resource's priority |
102 @param statuses: dict of statuses""" | 293 @param statuses: dict of statuses |
103 self.setCache(jid, 'show', show) | 294 """ |
104 self.setCache(jid, 'prority', priority) | 295 cache = self.getCache(entity) |
105 self.setCache(jid, 'statuses', statuses) | 296 if show == C.PRESENCE_UNAVAILABLE: |
106 self.update_jid(jid) | 297 if not entity.resource: |
298 cache[C.CONTACT_RESOURCES].clear() | |
299 cache[C.CONTACT_MAIN_RESOURCE]= None | |
300 else: | |
301 del cache[C.CONTACT_RESOURCES][entity.resource] | |
302 if not cache[C.CONTACT_RESOURCES]: | |
303 cache[C.CONTACT_MAIN_RESOURCE] = None | |
304 else: | |
305 assert entity.resource | |
306 resources_data = cache[C.CONTACT_RESOURCES] | |
307 resource_data = resources_data.setdefault(entity.resource, {}) | |
308 resource_data[C.PRESENCE_SHOW] = show | |
309 resource_data[C.PRESENCE_PRIORITY] = int(priority) | |
310 resource_data[C.PRESENCE_STATUSES] = statuses | |
311 | |
312 priority_resource = max(resources_data, key=lambda res: resources_data[res][C.PRESENCE_PRIORITY]) | |
313 cache[C.CONTACT_MAIN_RESOURCE] = priority_resource | |
314 self.update() | |
315 | |
316 def unselectAll(self): | |
317 """Unselect all contacts""" | |
318 self._selected.clear() | |
319 self.update() | |
320 | |
321 def select(self, entity): | |
322 """Select an entity | |
323 | |
324 @param entity(jid.JID): entity to select (resource is significant) | |
325 """ | |
326 log.debug("select %s" % entity) | |
327 self._selected.add(entity) | |
328 self.update() | |
329 | |
330 def setAlert(self, entity): | |
331 """Set an alert on the entity (usually for a waiting message) | |
332 | |
333 @param entity(jid.JID): entity which must displayed in alert mode (resource is significant) | |
334 """ | |
335 self._alerts.add(entity) | |
336 self.update() | |
107 | 337 |
108 def showOfflineContacts(self, show): | 338 def showOfflineContacts(self, show): |
109 pass | 339 show = C.bool(show) |
340 if self.show_disconnected == show: | |
341 return | |
342 self.show_disconnected = show | |
343 self.update() | |
110 | 344 |
111 def showEmptyGroups(self, show): | 345 def showEmptyGroups(self, show): |
112 pass | 346 show = C.bool(show) |
347 if self.show_empty_groups == show: | |
348 return | |
349 self.show_empty_groups = show | |
350 self.update() | |
351 | |
352 def showResources(self, show): | |
353 show = C.bool(show) | |
354 if self.show_resources == show: | |
355 return | |
356 self.show_resources = show | |
357 self.update() |