diff browser/sat_browser/contact_group.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_group.py@5d9f6d25c586
children f9091f4e04f0
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/browser/sat_browser/contact_group.py	Sat Aug 25 17:59:48 2018 +0200
@@ -0,0 +1,245 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Libervia: a Salut à Toi frontend
+# Copyright (C) 2013-2016 Adrien Cossa <souliane@mailoo.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 pyjamas.ui.Button import Button
+from pyjamas.ui.CheckBox import CheckBox
+from pyjamas.ui.Label import Label
+from pyjamas.ui.HorizontalPanel import HorizontalPanel
+from pyjamas.ui.VerticalPanel import VerticalPanel
+from pyjamas.ui.DialogBox import DialogBox
+from pyjamas.ui.ScrollPanel import ScrollPanel
+from pyjamas.ui import HasAlignment
+
+import dialog
+import list_manager
+import contact_panel
+import contact_list
+from sat_frontends.tools import jid
+
+
+unicode = str  # FIXME: pyjamas workaround
+
+
+class ContactGroupManager(list_manager.ListManager):
+
+    def __init__(self, editor, data, contacts, offsets):
+        """
+        @param container (FlexTable): FlexTable parent widget
+        @param keys (dict{unicode: dict{unicode: unicode}}): dict binding items
+            keys to their display config data.
+        @param contacts (list): list of contacts
+        """
+        self.editor = editor
+        list_manager.ListManager.__init__(self, data, contacts)
+        self.registerPopupMenuPanel(entries={"Remove group": {}},
+                                    callback=lambda sender, key: self.removeGroup(sender))
+
+    def removeGroup(self, sender):
+        group = sender.getHTML()
+
+        def confirm_cb(answer):
+            if answer:
+                list_manager.ListManager.removeList(self, group)
+                self.editor.add_group_panel.groups.remove(group)
+
+        _dialog = dialog.ConfirmDialog(confirm_cb, text="Do you really want to delete the group '%s'?" % group)
+        _dialog.show()
+
+    def tag(self, contacts):
+        list_manager.ListManager.tag(self, contacts)
+        self.editor.updateContactList(contacts)
+
+    def untag(self, contacts, ignore_key=None):
+        list_manager.ListManager.untag(self, contacts, ignore_key)
+        self.editor.updateContactList(contacts)
+
+
+class ContactGroupEditor(VerticalPanel):
+    """A big panel including a ContactGroupManager and other UI stuff."""
+
+    def __init__(self, host, container=None, onCloseCallback=None):
+        """
+
+        @param host (SatWebFrontend)
+        @param container (PanelBase): parent panel or None to display in a popup
+        @param onCloseCallback (callable)
+        """
+        VerticalPanel.__init__(self, StyleName="contactGroupEditor")
+        self.host = host
+
+        # eventually display in a popup
+        if container is None:
+            container = DialogBox(autoHide=False, centered=True)
+            container.setHTML("Manage contact groups")
+        self.container = container
+        self._on_close_callback = onCloseCallback
+
+        self.all_contacts = contact_list.JIDList(self.host.contact_list.roster)
+        roster_entities_by_group = self.host.contact_list.roster_entities_by_group
+        del roster_entities_by_group[None]  # remove the empty group
+        roster_groups = roster_entities_by_group.keys()
+        roster_groups.sort()
+
+        # groups on the left
+        manager = self.initContactGroupManager(roster_entities_by_group)
+        self.add_group_panel = self.initAddGroupPanel(roster_groups)
+        left_container = VerticalPanel(Width="100%")
+        left_container.add(manager)
+        left_container.add(self.add_group_panel)
+        left_container.setCellHorizontalAlignment(self.add_group_panel, HasAlignment.ALIGN_CENTER)
+        left_panel = ScrollPanel(left_container, StyleName="contactGroupManager")
+        left_panel.setAlwaysShowScrollBars(True)
+
+        # contact list on the right
+        east_panel = ScrollPanel(self.initContactList(), StyleName="contactGroupRoster")
+        east_panel.setAlwaysShowScrollBars(True)
+
+        south_panel = self.initCloseSaveButtons()
+
+        main_panel = HorizontalPanel()
+        main_panel.add(left_panel)
+        main_panel.add(east_panel)
+        self.add(Label("You get here an over whole view of your contact groups. There are two ways to assign your contacts to an existing group: write them into auto-completed textboxes or use the right panel to drag and drop them into the group."))
+        self.add(main_panel)
+        self.add(south_panel)
+
+        self.setCellHorizontalAlignment(south_panel, HasAlignment.ALIGN_CENTER)
+
+        # need to be done after the contact list has been initialized
+        self.updateContactList()
+
+        # Hide the contacts list from the main panel to not confuse the user
+        self.restore_contact_panel = False
+        clist = self.host.contact_list_widget
+        if clist.getVisible():
+            self.restore_contact_panel = True
+            self.host.panel._contactsSwitch()
+
+        container.add(self)
+        container.setVisible(True)
+        if isinstance(container, DialogBox):
+            container.center()
+
+    def initContactGroupManager(self, data):
+        """Initialise the contact group manager.
+
+        @param groups (list[unicode]): contact groups
+        """
+        self.groups = ContactGroupManager(self, data, self.all_contacts)
+        return self.groups
+
+    def initAddGroupPanel(self, groups):
+        """Initialise the 'Add group' panel.
+
+        @param groups (list[unicode]): contact groups
+        """
+
+        def add_group_cb(key):
+            self.groups.addList(key)
+            self.add_group_panel.textbox.setFocus(True)
+
+        add_group_panel = dialog.AddGroupPanel(groups, add_group_cb)
+        add_group_panel.addStyleName("addContactGroupPanel")
+        return add_group_panel
+
+    def initCloseSaveButtons(self):
+        """Add the buttons to close the dialog and save the groups."""
+        buttons = HorizontalPanel()
+        buttons.addStyleName("marginAuto")
+        buttons.add(Button("Cancel", listener=self.cancelWithoutSaving))
+        buttons.add(Button("Save", listener=self.closeAndSave))
+        return buttons
+
+    def initContactList(self):
+        """Add the contact list to the DockPanel."""
+
+        self.toggle = CheckBox("Hide assigned contacts")
+        self.toggle.addClickListener(lambda dummy: self.updateContactList())
+        self.toggle.addStyleName("toggleAssignedContacts")
+        self.contacts = contact_panel.ContactsPanel(self.host)
+        for contact in self.all_contacts:
+            self.contacts.updateContactBox(contact)
+        panel = VerticalPanel()
+        panel.add(self.toggle)
+        panel.add(self.contacts)
+        return panel
+
+    def updateContactList(self, contacts=None):
+        """Update the contact list's items visibility, depending of the toggle
+        checkbox and the "contacts" attribute.
+
+        @param contacts (list): contacts to be updated, or None to update all.
+        """
+        if not hasattr(self, "toggle"):
+            return
+        if contacts is not None:
+            contacts = [jid.JID(contact) for contact in contacts]
+            contacts = set(contacts).intersection(self.all_contacts)
+        else:
+            contacts = self.all_contacts
+
+        for contact in contacts:
+            if not self.toggle.getChecked():  # show all contacts
+                self.contacts.updateContactBox(contact).setVisible(True)
+            else:  # show only non-assigned contacts
+                if contact in self.groups.untagged:
+                    self.contacts.updateContactBox(contact).setVisible(True)
+                else:
+                    self.contacts.updateContactBox(contact).setVisible(False)
+
+    def __close(self):
+        """Remove the widget from parent or close the popup."""
+        if isinstance(self.container, DialogBox):
+            self.container.hide()
+        self.container.remove(self)
+        if self._on_close_callback is not None:
+            self._on_close_callback()
+        if self.restore_contact_panel:
+            self.host.panel._contactsSwitch()
+
+    def cancelWithoutSaving(self):
+        """Ask for confirmation before closing the dialog."""
+        def confirm_cb(answer):
+            if answer:
+                self.__close()
+
+        _dialog = dialog.ConfirmDialog(confirm_cb, text="Do you really want to cancel without saving?")
+        _dialog.show()
+
+    def closeAndSave(self):
+        """Call bridge methods to save the changes and close the dialog"""
+        old_groups_by_entity = contact_list.JIDDict(self.host.contact_list.roster_groups_by_entity)
+        old_entities = old_groups_by_entity.keys()
+        result = {jid.JID(item): keys for item, keys in self.groups.getKeysByItem().iteritems()}
+        groups_by_entity = contact_list.JIDDict(result)
+        entities = groups_by_entity.keys()
+
+        for invalid in entities.difference(self.all_contacts):
+            dialog.InfoDialog("Invalid contact(s)",
+                              "The contact '%s' is not in your contact list but has been assigned to: '%s'." % (invalid, "', '".join(groups_by_entity[invalid])) +
+                              "Your changes could not be saved: please check your assignments and save again.", Width="400px").center()
+            return
+
+        for entity in old_entities.difference(entities):
+            self.host.bridge.call('updateContact', None, unicode(entity), '', [])
+
+        for entity, groups in groups_by_entity.iteritems():
+            if entity not in old_groups_by_entity or groups != old_groups_by_entity[entity]:
+                self.host.bridge.call('updateContact', None, unicode(entity), '', list(groups))
+        self.__close()