comparison libervia/backend/stdui/ui_contact_list.py @ 4071:4b842c1fb686

refactoring: renamed `sat` package to `libervia.backend`
author Goffi <goffi@goffi.org>
date Fri, 02 Jun 2023 11:49:51 +0200
parents sat/stdui/ui_contact_list.py@524856bd7b19
children
comparison
equal deleted inserted replaced
4070:d10748475025 4071:4b842c1fb686
1 #!/usr/bin/env python3
2
3
4 # SAT standard user interface for managing contacts
5 # Copyright (C) 2009-2021 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 from libervia.backend.core.i18n import _, D_
21 from libervia.backend.core.constants import Const as C
22 from libervia.backend.tools import xml_tools
23 from twisted.words.protocols.jabber import jid
24 from xml.dom.minidom import Element
25
26
27 class ContactList(object):
28 """Add, update and remove contacts."""
29
30 def __init__(self, host):
31 self.host = host
32 self.__add_id = host.register_callback(self._add_contact, with_data=True)
33 self.__update_id = host.register_callback(self._update_contact, with_data=True)
34 self.__confirm_delete_id = host.register_callback(
35 self._get_confirm_remove_xmlui, with_data=True
36 )
37
38 host.import_menu(
39 (D_("Contacts"), D_("Add contact")),
40 self._get_add_dialog_xmlui,
41 security_limit=2,
42 help_string=D_("Add contact"),
43 )
44 host.import_menu(
45 (D_("Contacts"), D_("Update contact")),
46 self._get_update_dialog_xmlui,
47 security_limit=2,
48 help_string=D_("Update contact"),
49 )
50 host.import_menu(
51 (D_("Contacts"), D_("Remove contact")),
52 self._get_remove_dialog_xmlui,
53 security_limit=2,
54 help_string=D_("Remove contact"),
55 )
56
57 # FIXME: a plugin should not be used here, and current profile's jid host would be better than installation wise host
58 if "MISC-ACCOUNT" in self.host.plugins:
59 self.default_host = self.host.plugins["MISC-ACCOUNT"].account_domain_new_get()
60 else:
61 self.default_host = "example.net"
62
63 def contacts_get(self, profile):
64 """Return a sorted list of the contacts for that profile
65
66 @param profile: %(doc_profile)s
67 @return: list[string]
68 """
69 client = self.host.get_client(profile)
70 ret = [contact.full() for contact in client.roster.get_jids()]
71 ret.sort()
72 return ret
73
74 def get_groups(self, new_groups=None, profile=C.PROF_KEY_NONE):
75 """Return a sorted list of the groups for that profile
76
77 @param new_group (list): add these groups to the existing ones
78 @param profile: %(doc_profile)s
79 @return: list[string]
80 """
81 client = self.host.get_client(profile)
82 ret = client.roster.get_groups()
83 ret.sort()
84 ret.extend([group for group in new_groups if group not in ret])
85 return ret
86
87 def get_groups_of_contact(self, user_jid_s, profile):
88 """Return all the groups of the given contact
89
90 @param user_jid_s (string)
91 @param profile: %(doc_profile)s
92 @return: list[string]
93 """
94 client = self.host.get_client(profile)
95 return client.roster.get_item(jid.JID(user_jid_s)).groups
96
97 def get_groups_of_all_contacts(self, profile):
98 """Return a mapping between the contacts and their groups
99
100 @param profile: %(doc_profile)s
101 @return: dict (key: string, value: list[string]):
102 - key: the JID userhost
103 - value: list of groups
104 """
105 client = self.host.get_client(profile)
106 return {item.jid.userhost(): item.groups for item in client.roster.get_items()}
107
108 def _data2elts(self, data):
109 """Convert a contacts data dict to minidom Elements
110
111 @param data (dict)
112 @return list[Element]
113 """
114 elts = []
115 for key in data:
116 key_elt = Element("jid")
117 key_elt.setAttribute("name", key)
118 for value in data[key]:
119 value_elt = Element("group")
120 value_elt.setAttribute("name", value)
121 key_elt.childNodes.append(value_elt)
122 elts.append(key_elt)
123 return elts
124
125 def get_dialog_xmlui(self, options, data, profile):
126 """Generic method to return the XMLUI dialog for adding or updating a contact
127
128 @param options (dict): parameters for the dialog, with the keys:
129 - 'id': the menu callback id
130 - 'title': deferred localized string
131 - 'contact_text': deferred localized string
132 @param data (dict)
133 @param profile: %(doc_profile)s
134 @return dict
135 """
136 form_ui = xml_tools.XMLUI("form", title=options["title"], submit_id=options["id"])
137 if "message" in data:
138 form_ui.addText(data["message"])
139 form_ui.addDivider("dash")
140
141 form_ui.addText(options["contact_text"])
142 if options["id"] == self.__add_id:
143 contact = data.get(
144 xml_tools.form_escape("contact_jid"), "@%s" % self.default_host
145 )
146 form_ui.addString("contact_jid", value=contact)
147 elif options["id"] == self.__update_id:
148 contacts = self.contacts_get(profile)
149 list_ = form_ui.addList("contact_jid", options=contacts, selected=contacts[0])
150 elts = self._data2elts(self.get_groups_of_all_contacts(profile))
151 list_.set_internal_callback(
152 "groups_of_contact", fields=["contact_jid", "groups_list"], data_elts=elts
153 )
154
155 form_ui.addDivider("blank")
156
157 form_ui.addText(_("Select in which groups your contact is:"))
158 selected_groups = []
159 if "selected_groups" in data:
160 selected_groups = data["selected_groups"]
161 elif options["id"] == self.__update_id:
162 try:
163 selected_groups = self.get_groups_of_contact(contacts[0], profile)
164 except IndexError:
165 pass
166 groups = self.get_groups(selected_groups, profile)
167 form_ui.addList(
168 "groups_list", options=groups, selected=selected_groups, styles=["multi"]
169 )
170
171 adv_list = form_ui.change_container("advanced_list", columns=3, selectable="no")
172 form_ui.addLabel(D_("Add group"))
173 form_ui.addString("add_group")
174 button = form_ui.addButton("", value=D_("Add"))
175 button.set_internal_callback("move", fields=["add_group", "groups_list"])
176 adv_list.end()
177
178 form_ui.addDivider("blank")
179 return {"xmlui": form_ui.toXml()}
180
181 def _get_add_dialog_xmlui(self, data, profile):
182 """Get the dialog for adding contact
183
184 @param data (dict)
185 @param profile: %(doc_profile)s
186 @return dict
187 """
188 options = {
189 "id": self.__add_id,
190 "title": D_("Add contact"),
191 "contact_text": D_("New contact identifier (JID):"),
192 }
193 return self.get_dialog_xmlui(options, {}, profile)
194
195 def _get_update_dialog_xmlui(self, data, profile):
196 """Get the dialog for updating contact
197
198 @param data (dict)
199 @param profile: %(doc_profile)s
200 @return dict
201 """
202 if not self.contacts_get(profile):
203 _dialog = xml_tools.XMLUI("popup", title=D_("Nothing to update"))
204 _dialog.addText(_("Your contact list is empty."))
205 return {"xmlui": _dialog.toXml()}
206
207 options = {
208 "id": self.__update_id,
209 "title": D_("Update contact"),
210 "contact_text": D_("Which contact do you want to update?"),
211 }
212 return self.get_dialog_xmlui(options, {}, profile)
213
214 def _get_remove_dialog_xmlui(self, data, profile):
215 """Get the dialog for removing contact
216
217 @param data (dict)
218 @param profile: %(doc_profile)s
219 @return dict
220 """
221 if not self.contacts_get(profile):
222 _dialog = xml_tools.XMLUI("popup", title=D_("Nothing to delete"))
223 _dialog.addText(_("Your contact list is empty."))
224 return {"xmlui": _dialog.toXml()}
225
226 form_ui = xml_tools.XMLUI(
227 "form",
228 title=D_("Who do you want to remove from your contacts?"),
229 submit_id=self.__confirm_delete_id,
230 )
231 form_ui.addList("contact_jid", options=self.contacts_get(profile))
232 return {"xmlui": form_ui.toXml()}
233
234 def _get_confirm_remove_xmlui(self, data, profile):
235 """Get the confirmation dialog for removing contact
236
237 @param data (dict)
238 @param profile: %(doc_profile)s
239 @return dict
240 """
241 if C.bool(data.get("cancelled", "false")):
242 return {}
243 contact = data[xml_tools.form_escape("contact_jid")]
244
245 def delete_cb(data, profile):
246 if not C.bool(data.get("cancelled", "false")):
247 self._delete_contact(jid.JID(contact), profile)
248 return {}
249
250 delete_id = self.host.register_callback(delete_cb, with_data=True, one_shot=True)
251 form_ui = xml_tools.XMLUI("form", title=D_("Delete contact"), submit_id=delete_id)
252 form_ui.addText(
253 D_("Are you sure you want to remove %s from your contact list?") % contact
254 )
255 return {"xmlui": form_ui.toXml()}
256
257 def _add_contact(self, data, profile):
258 """Add the selected contact
259
260 @param data (dict)
261 @param profile: %(doc_profile)s
262 @return dict
263 """
264 if C.bool(data.get("cancelled", "false")):
265 return {}
266 contact_jid_s = data[xml_tools.form_escape("contact_jid")]
267 try:
268 contact_jid = jid.JID(contact_jid_s)
269 except (RuntimeError, jid.InvalidFormat, AttributeError):
270 # TODO: replace '\t' by a constant (see tools.xmlui.XMLUI.on_form_submitted)
271 data["selected_groups"] = data[xml_tools.form_escape("groups_list")].split(
272 "\t"
273 )
274 options = {
275 "id": self.__add_id,
276 "title": D_("Add contact"),
277 "contact_text": D_('Please enter a valid JID (like "contact@%s"):')
278 % self.default_host,
279 }
280 return self.get_dialog_xmlui(options, data, profile)
281 self.host.contact_add(contact_jid, profile_key=profile)
282 return self._update_contact(data, profile) # after adding, updating
283
284 def _update_contact(self, data, profile):
285 """Update the selected contact
286
287 @param data (dict)
288 @param profile: %(doc_profile)s
289 @return dict
290 """
291 client = self.host.get_client(profile)
292 if C.bool(data.get("cancelled", "false")):
293 return {}
294 contact_jid = jid.JID(data[xml_tools.form_escape("contact_jid")])
295 # TODO: replace '\t' by a constant (see tools.xmlui.XMLUI.on_form_submitted)
296 groups = data[xml_tools.form_escape("groups_list")].split("\t")
297 self.host.contact_update(client, contact_jid, name="", groups=groups)
298 return {}
299
300 def _delete_contact(self, contact_jid, profile):
301 """Delete the selected contact
302
303 @param contact_jid (JID)
304 @param profile: %(doc_profile)s
305 @return dict
306 """
307 self.host.contact_del(contact_jid, profile_key=profile)
308 return {}