Mercurial > libervia-backend
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libervia/backend/stdui/ui_contact_list.py Fri Jun 02 11:49:51 2023 +0200 @@ -0,0 +1,308 @@ +#!/usr/bin/env python3 + + +# SAT standard user interface for managing contacts +# Copyright (C) 2009-2021 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/>. + +from libervia.backend.core.i18n import _, D_ +from libervia.backend.core.constants import Const as C +from libervia.backend.tools import xml_tools +from twisted.words.protocols.jabber import jid +from xml.dom.minidom import Element + + +class ContactList(object): + """Add, update and remove contacts.""" + + def __init__(self, host): + self.host = host + self.__add_id = host.register_callback(self._add_contact, with_data=True) + self.__update_id = host.register_callback(self._update_contact, with_data=True) + self.__confirm_delete_id = host.register_callback( + self._get_confirm_remove_xmlui, with_data=True + ) + + host.import_menu( + (D_("Contacts"), D_("Add contact")), + self._get_add_dialog_xmlui, + security_limit=2, + help_string=D_("Add contact"), + ) + host.import_menu( + (D_("Contacts"), D_("Update contact")), + self._get_update_dialog_xmlui, + security_limit=2, + help_string=D_("Update contact"), + ) + host.import_menu( + (D_("Contacts"), D_("Remove contact")), + self._get_remove_dialog_xmlui, + security_limit=2, + help_string=D_("Remove contact"), + ) + + # FIXME: a plugin should not be used here, and current profile's jid host would be better than installation wise host + if "MISC-ACCOUNT" in self.host.plugins: + self.default_host = self.host.plugins["MISC-ACCOUNT"].account_domain_new_get() + else: + self.default_host = "example.net" + + def contacts_get(self, profile): + """Return a sorted list of the contacts for that profile + + @param profile: %(doc_profile)s + @return: list[string] + """ + client = self.host.get_client(profile) + ret = [contact.full() for contact in client.roster.get_jids()] + ret.sort() + return ret + + def get_groups(self, new_groups=None, profile=C.PROF_KEY_NONE): + """Return a sorted list of the groups for that profile + + @param new_group (list): add these groups to the existing ones + @param profile: %(doc_profile)s + @return: list[string] + """ + client = self.host.get_client(profile) + ret = client.roster.get_groups() + ret.sort() + ret.extend([group for group in new_groups if group not in ret]) + return ret + + def get_groups_of_contact(self, user_jid_s, profile): + """Return all the groups of the given contact + + @param user_jid_s (string) + @param profile: %(doc_profile)s + @return: list[string] + """ + client = self.host.get_client(profile) + return client.roster.get_item(jid.JID(user_jid_s)).groups + + def get_groups_of_all_contacts(self, profile): + """Return a mapping between the contacts and their groups + + @param profile: %(doc_profile)s + @return: dict (key: string, value: list[string]): + - key: the JID userhost + - value: list of groups + """ + client = self.host.get_client(profile) + return {item.jid.userhost(): item.groups for item in client.roster.get_items()} + + def _data2elts(self, data): + """Convert a contacts data dict to minidom Elements + + @param data (dict) + @return list[Element] + """ + elts = [] + for key in data: + key_elt = Element("jid") + key_elt.setAttribute("name", key) + for value in data[key]: + value_elt = Element("group") + value_elt.setAttribute("name", value) + key_elt.childNodes.append(value_elt) + elts.append(key_elt) + return elts + + def get_dialog_xmlui(self, options, data, profile): + """Generic method to return the XMLUI dialog for adding or updating a contact + + @param options (dict): parameters for the dialog, with the keys: + - 'id': the menu callback id + - 'title': deferred localized string + - 'contact_text': deferred localized string + @param data (dict) + @param profile: %(doc_profile)s + @return dict + """ + form_ui = xml_tools.XMLUI("form", title=options["title"], submit_id=options["id"]) + if "message" in data: + form_ui.addText(data["message"]) + form_ui.addDivider("dash") + + form_ui.addText(options["contact_text"]) + if options["id"] == self.__add_id: + contact = data.get( + xml_tools.form_escape("contact_jid"), "@%s" % self.default_host + ) + form_ui.addString("contact_jid", value=contact) + elif options["id"] == self.__update_id: + contacts = self.contacts_get(profile) + list_ = form_ui.addList("contact_jid", options=contacts, selected=contacts[0]) + elts = self._data2elts(self.get_groups_of_all_contacts(profile)) + list_.set_internal_callback( + "groups_of_contact", fields=["contact_jid", "groups_list"], data_elts=elts + ) + + form_ui.addDivider("blank") + + form_ui.addText(_("Select in which groups your contact is:")) + selected_groups = [] + if "selected_groups" in data: + selected_groups = data["selected_groups"] + elif options["id"] == self.__update_id: + try: + selected_groups = self.get_groups_of_contact(contacts[0], profile) + except IndexError: + pass + groups = self.get_groups(selected_groups, profile) + form_ui.addList( + "groups_list", options=groups, selected=selected_groups, styles=["multi"] + ) + + adv_list = form_ui.change_container("advanced_list", columns=3, selectable="no") + form_ui.addLabel(D_("Add group")) + form_ui.addString("add_group") + button = form_ui.addButton("", value=D_("Add")) + button.set_internal_callback("move", fields=["add_group", "groups_list"]) + adv_list.end() + + form_ui.addDivider("blank") + return {"xmlui": form_ui.toXml()} + + def _get_add_dialog_xmlui(self, data, profile): + """Get the dialog for adding contact + + @param data (dict) + @param profile: %(doc_profile)s + @return dict + """ + options = { + "id": self.__add_id, + "title": D_("Add contact"), + "contact_text": D_("New contact identifier (JID):"), + } + return self.get_dialog_xmlui(options, {}, profile) + + def _get_update_dialog_xmlui(self, data, profile): + """Get the dialog for updating contact + + @param data (dict) + @param profile: %(doc_profile)s + @return dict + """ + if not self.contacts_get(profile): + _dialog = xml_tools.XMLUI("popup", title=D_("Nothing to update")) + _dialog.addText(_("Your contact list is empty.")) + return {"xmlui": _dialog.toXml()} + + options = { + "id": self.__update_id, + "title": D_("Update contact"), + "contact_text": D_("Which contact do you want to update?"), + } + return self.get_dialog_xmlui(options, {}, profile) + + def _get_remove_dialog_xmlui(self, data, profile): + """Get the dialog for removing contact + + @param data (dict) + @param profile: %(doc_profile)s + @return dict + """ + if not self.contacts_get(profile): + _dialog = xml_tools.XMLUI("popup", title=D_("Nothing to delete")) + _dialog.addText(_("Your contact list is empty.")) + return {"xmlui": _dialog.toXml()} + + form_ui = xml_tools.XMLUI( + "form", + title=D_("Who do you want to remove from your contacts?"), + submit_id=self.__confirm_delete_id, + ) + form_ui.addList("contact_jid", options=self.contacts_get(profile)) + return {"xmlui": form_ui.toXml()} + + def _get_confirm_remove_xmlui(self, data, profile): + """Get the confirmation dialog for removing contact + + @param data (dict) + @param profile: %(doc_profile)s + @return dict + """ + if C.bool(data.get("cancelled", "false")): + return {} + contact = data[xml_tools.form_escape("contact_jid")] + + def delete_cb(data, profile): + if not C.bool(data.get("cancelled", "false")): + self._delete_contact(jid.JID(contact), profile) + return {} + + delete_id = self.host.register_callback(delete_cb, with_data=True, one_shot=True) + form_ui = xml_tools.XMLUI("form", title=D_("Delete contact"), submit_id=delete_id) + form_ui.addText( + D_("Are you sure you want to remove %s from your contact list?") % contact + ) + return {"xmlui": form_ui.toXml()} + + def _add_contact(self, data, profile): + """Add the selected contact + + @param data (dict) + @param profile: %(doc_profile)s + @return dict + """ + if C.bool(data.get("cancelled", "false")): + return {} + contact_jid_s = data[xml_tools.form_escape("contact_jid")] + try: + contact_jid = jid.JID(contact_jid_s) + except (RuntimeError, jid.InvalidFormat, AttributeError): + # TODO: replace '\t' by a constant (see tools.xmlui.XMLUI.on_form_submitted) + data["selected_groups"] = data[xml_tools.form_escape("groups_list")].split( + "\t" + ) + options = { + "id": self.__add_id, + "title": D_("Add contact"), + "contact_text": D_('Please enter a valid JID (like "contact@%s"):') + % self.default_host, + } + return self.get_dialog_xmlui(options, data, profile) + self.host.contact_add(contact_jid, profile_key=profile) + return self._update_contact(data, profile) # after adding, updating + + def _update_contact(self, data, profile): + """Update the selected contact + + @param data (dict) + @param profile: %(doc_profile)s + @return dict + """ + client = self.host.get_client(profile) + if C.bool(data.get("cancelled", "false")): + return {} + contact_jid = jid.JID(data[xml_tools.form_escape("contact_jid")]) + # TODO: replace '\t' by a constant (see tools.xmlui.XMLUI.on_form_submitted) + groups = data[xml_tools.form_escape("groups_list")].split("\t") + self.host.contact_update(client, contact_jid, name="", groups=groups) + return {} + + def _delete_contact(self, contact_jid, profile): + """Delete the selected contact + + @param contact_jid (JID) + @param profile: %(doc_profile)s + @return dict + """ + self.host.contact_del(contact_jid, profile_key=profile) + return {}