view src/browser/sat_browser/contact_group.py @ 716:3b91225b457a

server + browser side: blogging refactoring (draft), huge commit sorry: /!\ everything is not working yet, group blog is not working for now - adaptation to backend changes - frontend commons part of blog have been moved to QuickFrontend - (editors) button "WYSIWYG edition" renamed to "preview" - (editors) Shift + [ENTER] is now used to send a text message, [ENTER] to finish a ligne normally - (editors) fixed modifiers handling - global simplification, resulting of the refactoring - with backend refactoring, we are now using PEP again, XEP-0277 compatibility is restored \o/
author Goffi <goffi@goffi.org>
date Sun, 16 Aug 2015 01:51:12 +0200
parents 16079280a39e
children fe3c2357a8c9
line wrap: on
line source

#!/usr/bin/python
# -*- coding: utf-8 -*-

# Libervia: a Salut à Toi frontend
# Copyright (C) 2013, 2014, 2015 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.FlexTable import FlexTable
from pyjamas.ui.DockPanel import DockPanel
from pyjamas.Timer import Timer
from pyjamas.ui.Button import Button
from pyjamas.ui.HorizontalPanel import HorizontalPanel
from pyjamas.ui.VerticalPanel import VerticalPanel
from pyjamas.ui.DialogBox import DialogBox
from pyjamas.ui import HasAlignment

import dialog
import list_manager
import contact_panel
import contact_list


unicode = str  # FIXME: pyjamas workaround


class ContactGroupManager(list_manager.ListManager):

    def __init__(self, container, keys, contacts, offsets, style):
        """
        @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
        @param offsets (dict): define widgets positions offsets within container:
            - "x_first": the x offset for the first widget's row on the grid
            - "x": the x offset for all widgets rows, except the first one if "x_first" is defined
            - "y": the y offset for all widgets columns on the grid
        @param style (dict): define CSS styles
        """
        list_manager.ListManager.__init__(self, container, keys, contacts, offsets, style)
        self.registerPopupMenuPanel(entries={"Remove group": {}},
                                    callback=lambda sender, key: Timer(5, lambda timer: self.removeContactKey(sender, key)))

    def removeContactKey(self, sender, key):
        key = sender.getText()

        def confirm_cb(answer):
            if answer:
                list_manager.ListManager.removeItemKey(self, key)
                self.container.removeKeyFromAddGroupPanel(key)

        _dialog = dialog.ConfirmDialog(confirm_cb, text="Do you really want to delete the group '%s'?" % key)
        _dialog.show()

    def removeFromRemainingList(self, contacts):
        list_manager.ListManager.removeFromRemainingList(self, contacts)
        self.container.updateContactList(contacts)

    def addToRemainingList(self, contacts, ignore_key=None):
        list_manager.ListManager.addToRemainingList(self, contacts, ignore_key)
        self.container.updateContactList(contacts)


class ContactGroupEditor(DockPanel):
    """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)
        """
        DockPanel.__init__(self)
        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_entities)
        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()

        self.add_group_panel = self.initAddGroupPanel(roster_groups)
        south_panel = self.initCloseSaveButtons()
        center_panel = self.initContactGroupManager(roster_groups)
        east_panel = self.initContactList()

        self.add(self.add_group_panel, DockPanel.CENTER)
        self.add(east_panel, DockPanel.EAST)
        self.add(center_panel, DockPanel.NORTH)
        self.add(south_panel, DockPanel.SOUTH)

        self.setCellHorizontalAlignment(center_panel, HasAlignment.ALIGN_LEFT)
        self.setCellVerticalAlignment(center_panel, HasAlignment.ALIGN_TOP)
        self.setCellHorizontalAlignment(east_panel, HasAlignment.ALIGN_RIGHT)
        self.setCellVerticalAlignment(east_panel, HasAlignment.ALIGN_TOP)
        self.setCellVerticalAlignment(self.add_group_panel, HasAlignment.ALIGN_BOTTOM)
        self.setCellHorizontalAlignment(self.add_group_panel, HasAlignment.ALIGN_LEFT)
        self.setCellVerticalAlignment(south_panel, HasAlignment.ALIGN_BOTTOM)
        self.setCellHorizontalAlignment(south_panel, HasAlignment.ALIGN_CENTER)

        # need to be done after the contact list has been initialized
        self.groups.resetItems(roster_entities_by_group)
        self.toggleContacts(showAll=True)

        # Hide the contacts list from the main panel to not confuse the user
        self.restore_contact_panel = False
        clist = self.host.contact_list
        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, groups):
        """Initialise the contact group manager.

        @param groups (list[unicode]): contact groups
        """
        flex_table = FlexTable()
        flex_table.addStyleName('contactGroupEditor')

        # overwrite the default style which has been set for rich text editor
        style = {"keyItem": "group",
                 "popupMenuItem": "popupMenuItem",
                 "removeButton": "contactGroupRemoveButton",
                 "buttonCell": "contactGroupButtonCell",
                 "keyPanel": "contactGroupPanel"
                 }

        groups = {group: {} for group in groups}
        self.groups = ContactGroupManager(flex_table, groups, self.all_contacts, style=style)
        self.groups.createWidgets()  # widgets are automatically added to the FlexTable

        # FIXME: clean that part which is dangerous
        flex_table.updateContactList = self.updateContactList
        flex_table.removeKeyFromAddGroupPanel = self.add_group_panel.groups.remove

        return flex_table

    def initAddGroupPanel(self, groups):
        """Initialise the 'Add group' panel.

        @param groups (list[unicode]): contact groups
        """

        def add_group_cb(key):
            self.groups.addItemKey(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("Save", listener=self.closeAndSave))
        buttons.add(Button("Cancel", listener=self.cancelWithoutSaving))
        return buttons

    def initContactList(self):
        """Add the contact list to the DockPanel."""
        self.toggle = Button("", self.toggleContacts)
        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 toggleContacts(self, sender=None, showAll=None):
        """Toggle the button to show contacts and the contact list.

        @param sender (Button)
        @param showAll (bool): if set, initialise with True to show all contacts
            or with False to show only the ones that are not assigned yet.
        """
        self.toggle.showAll = (not self.toggle.showAll) if showAll is None else showAll
        self.toggle.setText("Hide assigned" if self.toggle.showAll else "Show assigned")
        self.updateContactList()

    def updateContactList(self, contacts=None):
        """Update the contact list's items visibility, depending of the toggle
        button and the "contacts" attribute.

        @param contacts (list): contacts to be updated, or None to update all.
        """
        if not hasattr(self, "toggle") or not hasattr(self.toggle, "showAll"):
            return
        if contacts is not None:
            to_remove = set()
            for contact in contacts:
                if contact not in self.all_contacts:
                    to_remove.add(contact)
            for contact in to_remove:
                contacts.remove(contact)
        else:
            contacts = self.all_contacts
        for contact in contacts:
            if self.toggle.showAll:
                self.contacts.updateContactBox(contact).setVisible(True)
            else:
                if contact in self.groups.items_remaining:
                    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()
        groups_by_entity = contact_list.JIDDict(self.groups.getKeysByItem())
        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()