diff src/browser/sat_browser/list_manager.py @ 600:32dbbc941123 frontends_multi_profiles

browser_side: fixes the contact group manager
author souliane <souliane@mailoo.org>
date Fri, 06 Feb 2015 17:53:01 +0100
parents 97c72fe4a5f2
children c22b47d63fe2
line wrap: on
line diff
--- a/src/browser/sat_browser/list_manager.py	Fri Feb 06 19:31:30 2015 +0100
+++ b/src/browser/sat_browser/list_manager.py	Fri Feb 06 17:53:01 2015 +0100
@@ -19,15 +19,10 @@
 
 from sat.core.log import getLogger
 log = getLogger(__name__)
-from pyjamas.ui.Grid import Grid
 from pyjamas.ui.Button import Button
 from pyjamas.ui.ListBox import ListBox
 from pyjamas.ui.FlowPanel import FlowPanel
 from pyjamas.ui.AutoComplete import AutoCompleteTextBox
-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.KeyboardListener import KEY_ENTER
 from pyjamas.ui.MouseListener import MouseHandler
 from pyjamas.ui.FocusListener import FocusHandler
@@ -38,221 +33,245 @@
 import base_panels
 import base_widget
 
-# HTML content for the removal button (image or text)
-REMOVE_BUTTON = '<span class="recipientRemoveIcon">x</span>'
+from sat_frontends.tools import jid
+
+
+unicode = str  # FIXME: pyjamas workaround
 
-# Item to be considered for an empty list box selection.
-# Could be whatever which doesn't look like a JID or a group name.
-EMPTY_SELECTION_ITEM = ""
+# HTML content for the removal button (image or text)
+REMOVE_BUTTON = '<span class="itemRemoveIcon">x</span>'
+
+
+# FIXME: dirty method and magic string to fix ASAP
+def tryJID(obj):
+    return jid.JID(obj) if (isinstance(obj, unicode) and not obj.startswith('@')) else obj
 
 
-class ListManager():
-    """A manager for sub-panels to assign elements to lists."""
+class ListManager(object):
+    """A base class to manage one or several lists of items."""
 
-    def __init__(self, parent, keys_dict={}, contact_list=[], offsets={}, style={}):
-        """
-        @param parent: FlexTable parent widget for the manager
-        @param keys_dict: dict with the contact keys mapped to data
-        @param contact_list: list of string (the contact JID userhosts)
-        @param offsets: dict to set widget positions offset within parent
-        - "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
+    def __init__(self, container, keys=None, items=None, offsets=None, style=None):
         """
-        self._parent = parent
-        if isinstance(keys_dict, set) or isinstance(keys_dict, list):
-            tmp = {}
-            for key in keys_dict:
-                tmp[key] = {}
-            keys_dict = tmp
-        self.__keys_dict = keys_dict
-        if isinstance(contact_list, set):
-            contact_list = list(contact_list)
-        self.__list = contact_list
-        self.__list.sort()
-        # store the list of contacts that are not assigned yet
-        self.__remaining_list = []
-        self.__remaining_list.extend(self.__list)
-        # mark a change to sort the list before it's used
-        self.__remaining_list_sorted = True
+        @param container (FlexTable): FlexTable parent widget
+        @param keys (dict{unicode: dict{unicode: unicode}}): dict binding items
+            keys to their display config data.
+        @param items (list): list of items
+        @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
+        """
+        self.container = container
+        self.keys = {} if keys is None else keys
+        self.items = [] if items is None else items
+        self.items.sort()
+
+        # store the list of items that are not assigned yet
+        self.items_remaining = [item for item in self.items]
+        self.items_remaining_sorted = True
 
         self.offsets = {"x_first": 0, "x": 0, "y": 0}
-        if "x" in offsets and not "x_first" in offsets:
-            offsets["x_first"] = offsets["x"]
-        self.offsets.update(offsets)
+        if offsets is not None:
+            if "x" in offsets and "x_first" not in offsets:
+                offsets["x_first"] = offsets["x"]
+            self.offsets.update(offsets)
 
-        self.style = {
-           "keyItem": "recipientTypeItem",
-           "popupMenuItem": "recipientTypeItem",
-           "buttonCell": "recipientButtonCell",
-           "dragoverPanel": "dragover-recipientPanel",
-           "keyPanel": "recipientPanel",
-           "textBox": "recipientTextBox",
-           "textBox-invalid": "recipientTextBox-invalid",
-           "removeButton": "recipientRemoveButton",
-        }
-        self.style.update(style)
+        self.style = {"keyItem": "itemKey",
+                      "popupMenuItem": "itemKey",
+                      "buttonCell": "itemButtonCell",
+                      "dragoverPanel": "itemPanel-dragover",
+                      "keyPanel": "itemPanel",
+                      "textBox": "itemTextBox",
+                      "textBox-invalid": "itemTextBox-invalid",
+                      "removeButton": "itemRemoveButton",
+                      }
+        if style is not None:
+            self.style.update(style)
 
     def createWidgets(self, title_format="%s"):
-        """Fill the parent grid with all the widgets (some may be hidden during the initialization)."""
-        self.__children = {}
-        for key in self.__keys_dict:
-            self.addContactKey(key, title_format=title_format)
+        """Fill the container widget with one ListPanel per item key (some may be
+        hidden during the initialization).
 
-    def addContactKey(self, key, dict_={}, title_format="%s"):
-        if key not in self.__keys_dict:
-            self.__keys_dict[key] = dict_
-        # copy the key to its associated sub-map
-        self.__keys_dict[key]["title"] = key
-        self._addChild(self.__keys_dict[key], title_format)
+        @param title_format (unicode): format string for the title
+        """
+        self.children = {}
+        for key in self.keys:
+            self.addItemKey(key, title_format=title_format)
+
+    def addItemKey(self, key, data=None, title_format="%s"):
+        """Add to the container a Button and ListPanel for a new item key.
 
-    def removeContactKey(self, key):
-        """Remove a list panel and all its associated data."""
-        contacts = self.__children[key]["panel"].getContacts()
-        (y, x) = self._parent.getIndex(self.__children[key]["button"])
-        self._parent.removeRow(y)
-        del self.__children[key]
-        del self.__keys_dict[key]
-        self.addToRemainingList(contacts)
+        @param key (unicode): item key
+        @param data (dict{unicode: unicode}): config data
+        """
+        key_data = self.keys.setdefault(key, {})
+        if data is not None:
+            key_data.update(data)
+        key_data["title"] = key  # copy the key to its associated sub-map
 
-    def _addChild(self, entry, title_format):
-        """Add a button and FlowPanel for the corresponding map entry."""
-        button = Button(title_format % entry["title"])
+        button = Button(title_format % key)
         button.setStyleName(self.style["keyItem"])
-        if hasattr(entry, "desc"):
-            button.setTitle(entry["desc"])
-        if not "optional" in entry:
-            entry["optional"] = False
-        button.setVisible(not entry["optional"])
-        y = len(self.__children) + self.offsets["y"]
+        if hasattr(key_data, "desc"):
+            button.setTitle(key_data["desc"])
+        if "optional" not in key_data:
+            key_data["optional"] = False
+        button.setVisible(not key_data["optional"])
+        y = len(self.children) + self.offsets["y"]
         x = self.offsets["x_first"] if y == self.offsets["y"] else self.offsets["x"]
 
-        self._parent.insertRow(y)
-        self._parent.setWidget(y, x, button)
-        self._parent.getCellFormatter().setStyleName(y, x, self.style["buttonCell"])
+        self.container.insertRow(y)
+        self.container.setWidget(y, x, button)
+        self.container.getCellFormatter().setStyleName(y, x, self.style["buttonCell"])
 
-        _child = ListPanel(self, entry, self.style)
-        self._parent.setWidget(y, x + 1, _child)
+        _child = ListPanel(self, key_data, self.style)
+        self.container.setWidget(y, x + 1, _child)
 
-        self.__children[entry["title"]] = {}
-        self.__children[entry["title"]]["button"] = button
-        self.__children[entry["title"]]["panel"] = _child
+        self.children[key] = {}
+        self.children[key]["button"] = button
+        self.children[key]["panel"] = _child
 
         if hasattr(self, "popup_menu"):
-            # this is done if self.registerPopupMenuPanel has been called yet
+            # self.registerPopupMenuPanel has been called yet
             self.popup_menu.registerClickSender(button)
 
-    def _refresh(self, visible=True):
-        """Set visible the sub-panels that are non optional or non empty, hide the rest."""
-        for key in self.__children:
-            self.setContactPanelVisible(key, False)
-        if not visible:
+    def removeItemKey(self, key):
+        """Remove from the container a ListPanel representing an item key, and all
+        its associated data.
+
+        @param key (unicode): item key
+        """
+        items = self.children[key]["panel"].getItems()
+        (y, x) = self.container.getIndex(self.children[key]["button"])
+        self.container.removeRow(y)
+        del self.children[key]
+        del self.keys[key]
+        self.addToRemainingList(items)
+
+    def refresh(self, hide_everything=False):
+        """Set visible the sub-panels that are non optional or non empty, hide
+        the rest. Setting the attribute "hide_everything" to True you can also
+        hide everything.
+
+        @param hide_everything (boolean): set to True to hide everything
+        """
+        for key in self.children:
+            self.setItemPanelVisible(key, False)
+        if hide_everything:
             return
-        _map = self.getContacts()
-        for key in _map:
-            if len(_map[key]) > 0 or not self.__keys_dict[key]["optional"]:
-                self.setContactPanelVisible(key, True)
+        for key, items in self.getItemsByKey().iteritems():
+            if len(items) > 0 or not self.keys[key]["optional"]:
+                self.setItemPanelVisible(key, True)
 
     def setVisible(self, visible):
-        self._refresh(visible)
+        self.refresh(not visible)
+
+    def setItemPanelVisible(self, key, visible=True, sender=None):
+        """Set the item key's widgets visibility.
 
-    def setContactPanelVisible(self, key, visible=True, sender=None):
-        """Do not remove the "sender" param as it is needed for the context menu."""
-        self.__children[key]["button"].setVisible(visible)
-        self.__children[key]["panel"].setVisible(visible)
-
-    @property
-    def list(self):
-        """Return the full list of potential contacts."""
-        return self.__list
+        @param key (unicode): item key
+        @param visible (bool): set to True to display the widgets
+        @param sender
+        """
+        self.children[key]["button"].setVisible(visible)
+        self.children[key]["panel"].setVisible(visible)
 
     @property
-    def keys(self):
-        return self.__keys_dict.keys()
-
-    @property
-    def keys_dict(self):
-        return self.__keys_dict
-
-    @property
-    def remaining_list(self):
-        """Return the contacts that have not been selected yet."""
-        if not self.__remaining_list_sorted:
-            self.__remaining_list_sorted = True
-            self.__remaining_list.sort()
-        return self.__remaining_list
+    def items_remaining(self):
+        """Return the unused items."""
+        if not self.items_remaining_sorted:
+            self.items_remaining.sort()
+            self.items_remaining_sorted = True
+        return self.items_remaining
 
     def setRemainingListUnsorted(self):
-        """Mark a change (deletion) so the list will be sorted before it's used."""
-        self.__remaining_list_sorted = False
+        """Mark the list of unused items as being unsorted."""
+        self.items_remaining_sorted = False
+
+    def removeFromRemainingList(self, items):
+        """Remove some items from the list of unused items.
 
-    def removeFromRemainingList(self, contacts):
-        """Remove contacts after they have been added to a sub-panel."""
-        if not isinstance(contacts, list):
-            contacts = [contacts]
-        for contact_ in contacts:
-            if contact_ in self.__remaining_list:
-                self.__remaining_list.remove(contact_)
+        @param items (list): items to be removed
+        """
+        for item in items:
+            if item in self.items_remaining:
+                self.items_remaining.remove(item)
 
-    def addToRemainingList(self, contacts, ignore_key=None):
-        """Add contacts after they have been removed from a sub-panel."""
-        if not isinstance(contacts, list):
-            contacts = [contacts]
-        assigned_contacts = set()
-        assigned_map = self.getContacts()
-        for key_ in assigned_map.keys():
-            if ignore_key is not None and key_ == ignore_key:
+    def addToRemainingList(self, items, ignore_key=None):
+        """Add some items to the list of unused items. Check first if the
+        items are really not used in any ListPanel.
+
+        @param items (list): items to be removed
+        @param ignore_key (unicode): item key to be ignored while checking
+        """
+        items_assigned = set()
+        for key, current_items in self.getItemsByKey().iteritems():
+            if ignore_key is not None and key == ignore_key:
                 continue
-            assigned_contacts.update(assigned_map[key_])
-        for contact_ in contacts:
-            if contact_ not in self.__list or contact_ in self.__remaining_list:
+            items_assigned.update(current_items)
+        for item in items:
+            if item not in self.items or item in self.items_remaining or item in items_assigned:
                 continue
-            if contact_ in assigned_contacts:
-                continue  # the contact is assigned somewhere else
-            self.__remaining_list.append(contact_)
+            self.items_remaining.append(item)
             self.setRemainingListUnsorted()
 
-    def setContacts(self, _map={}):
-        """Set the contacts for each contact key."""
-        for key in self.__keys_dict:
-            if key in _map:
-                self.__children[key]["panel"].setContacts(_map[key])
+    def resetItems(self, data={}):
+        """Repopulate all the lists (one per item key) with the given items.
+
+        @param data (dict{unicode: list}): dict binding items keys to items.
+        """
+        for key in self.keys:
+            if key in data:
+                self.children[key]["panel"].resetItems(data[key])
             else:
-                self.__children[key]["panel"].setContacts([])
-        self._refresh()
+                self.children[key]["panel"].resetItems([])
+        self.refresh()
+
+    def getItemsByKey(self):
+        """Get all the items by key.
 
-    def getContacts(self):
-        """Get the contacts for all the lists.
-        @return: a mapping between keys and contact lists."""
-        _map = {}
-        for key in self.__children:
-            _map[key] = self.__children[key]["panel"].getContacts()
-        return _map
+        @return: dict{unicode: set}
+        """
+        return {key: self.children[key]["panel"].getItems() for key in self.children}
+
+    def getKeysByItem(self):
+        """Get all the keys by item.
 
-    @property
-    def target_drop_cell(self):
-        """@return: the panel where something has been dropped."""
-        return self._target_drop_cell
-
-    def setTargetDropCell(self, target_drop_cell):
-        """@param: target_drop_cell: the panel where something has been dropped."""
-        self._target_drop_cell = target_drop_cell
+        @return: dict{object: set(unicode)}
+        """
+        result = {}
+        for key in self.children:
+            for item in self.children[key]["panel"].getItems():
+                result.setdefault(item, set()).add(key)
+        return result
 
     def registerPopupMenuPanel(self, entries, hide, callback):
-        "Register a popup menu panel that will be bound to all contact keys elements."
-        self.popup_menu = base_panels.PopupMenuPanel(entries=entries, hide=hide, callback=callback, style={"item": self.style["popupMenuItem"]})
+        """Register a popup menu panel for the item keys buttons.
+
+        @param entries (dict{unicode: dict{unicode: unicode}}): menu entries
+        @param hide (callable): method to call in order to know if a menu item
+            should be hidden from the menu. Takes in the button widget and the
+            item key and returns a boolean.
+        @param callback (callable): common callback for all menu items, takes in
+            the button widget and the item key.
+        """
+        self.popup_menu = base_panels.PopupMenuPanel(entries, hide, callback, style={"item": self.style["popupMenuItem"]})
 
 
 class DragAutoCompleteTextBox(AutoCompleteTextBox, base_widget.DragLabel, MouseHandler, FocusHandler):
-    """A draggable AutoCompleteTextBox which is used for representing a contact.
-    This class is NOT generic because of the onDragEnd method which call methods
-    from ListPanel. It's probably not reusable for another scenario.
-    """
+    """A draggable AutoCompleteTextBox which is used for representing an item."""
+    # XXX: this class is NOT generic because of the onDragEnd method which calls methods from ListPanel. It's probably not reusable for another scenario.
 
-    def __init__(self, parent, event_cbs, style):
+    def __init__(self, list_panel, event_cbs, style):
+        """
+
+        @param list_panel (ListPanel)
+        @param event_cbs (list[callable])
+        @param style (dict)
+        """
         AutoCompleteTextBox.__init__(self)
         base_widget.DragLabel.__init__(self, '', 'CONTACT_TEXTBOX')  # The group prefix "@" is already in text so we use only the "CONTACT_TEXTBOX" type
-        self._parent = parent
+        self.list_panel = list_panel
         self.event_cbs = event_cbs
         self.style = style
         self.addMouseListener(self)
@@ -277,11 +296,11 @@
     def onDragStart(self, event):
         self._text = self.getText()
         base_widget.DragLabel.onDragStart(self, event)
-        self._parent.setTargetDropCell(None)
+        self.list_panel.manager.target_drop_cell = None
         self.setSelectionRange(len(self.getText()), 0)
 
     def onDragEnd(self, event):
-        target = self._parent.target_drop_cell  # parent or another ListPanel
+        target = self.list_panel.manager.target_drop_cell  # parent or another ListPanel
         if self.getText() == "" or target is None:
             return
         self.event_cbs["drop"](self, target)
@@ -289,14 +308,14 @@
     def setRemoveButton(self):
 
         def remove_cb(sender):
-            """Callback for the button to remove this contact."""
-            self._parent.remove(self)
-            self._parent.remove(self.remove_btn)
+            """Callback for the button to remove this item."""
+            self.list_panel.remove(self)
+            self.list_panel.remove(self.remove_btn)
             self.event_cbs["remove"](self)
 
         self.remove_btn = Button(REMOVE_BUTTON, remove_cb, Visible=False)
         self.remove_btn.setStyleName(self.style["removeButton"])
-        self._parent.add(self.remove_btn)
+        self.list_panel.add(self.remove_btn)
 
     def removeOrReset(self):
         if hasattr(self, "remove_btn"):
@@ -344,10 +363,14 @@
     """A cell where you can drop widgets. This class is NOT generic because of
     onDrop which uses methods from ListPanel. It has been created to
     separate the drag and drop methods from the others and add a bit of
-    lisibility, but it's probably not reusable for another scenario.
+    readability, but it's probably not reusable for another scenario.
     """
 
     def __init__(self, drop_cbs):
+        """
+
+        @param drop_cbs (list[callable])
+        """
         DropWidget.__init__(self)
         self.drop_cbs = drop_cbs
 
@@ -384,28 +407,43 @@
 
 
 class ListPanel(FlowPanel, DropCell):
-    """Sub-panel used for each contact key. Beware that pyjamas.ui.FlowPanel
-    is not fully implemented yet and can not be used with pyjamas.ui.Label."""
+    """Panel used for listing items sharing the same key. The key is showed as
+    a Button to which you can bind a popup menu and the items are represented
+    with a sequence of DragAutoCompleteTextBoxeditable."""
+    # XXX: beware that pyjamas.ui.FlowPanel is not fully implemented yet and can not be used with pyjamas.ui.Label
+
+    def __init__(self, manager, data, style={}):
+        """Initialization with a button and a DragAutoCompleteTextBox.
 
-    def __init__(self, parent, entry, style={}):
-        """Initialization with a button and a DragAutoCompleteTextBox."""
-        FlowPanel.__init__(self, Visible=(False if entry["optional"] else True))
-        drop_cbs = {"GROUP": lambda panel, item: self.addContact("@%s" % item),
-                    "CONTACT": lambda panel, item: self.addContact(item),
-                    "CONTACT_TITLE": lambda panel, item: self.addContact('@@'),
-                    "CONTACT_TEXTBOX": lambda panel, item: self.setTargetDropCell(panel)
+        @param manager (ListManager)
+        @param data (dict{unicode: unicode})
+        @param style (dict{unicode: unicode})
+        """
+        FlowPanel.__init__(self, Visible=(False if data["optional"] else True))
+
+        def setTargetDropCell(panel, item):
+            self.manager.target_drop_cell = panel
+
+        # FIXME: dirty magic strings '@' and '@@'
+        drop_cbs = {"GROUP": lambda panel, item: self.addItem("@%s" % item),
+                    "CONTACT": lambda panel, item: self.addItem(tryJID(item)),
+                    "CONTACT_TITLE": lambda panel, item: self.addItem('@@'),
+                    "CONTACT_TEXTBOX": setTargetDropCell
                     }
         DropCell.__init__(self, drop_cbs)
         self.style = style
         self.addStyleName(self.style["keyPanel"])
-        self._parent = parent
-        self.key = entry["title"]
+        self.manager = manager
+        self.key = data["title"]
         self._addTextBox()
 
     def _addTextBox(self, switchPrevious=False):
-        """Add a text box to the last position. If switchPrevious is True, simulate
-        an insertion before the current last textbox by copying the text and valid state.
-        @return: the created textbox or the previous one if switchPrevious is True.
+        """Add an empty text box to the last position.
+
+        @param switchPrevious (bool): if True, simulate an insertion before the
+            current last textbox by switching the texts and valid states
+        @return: an DragAutoCompleteTextBox, the created text box or the
+            previous one if switchPrevious is True.
         """
         if hasattr(self, "_last_textbox"):
             if self._last_textbox.getText() == "":
@@ -417,24 +455,29 @@
         def focus_cb(sender):
             if sender != self._last_textbox:
                 # save the current value before it's being modified
-                self._parent.addToRemainingList(sender.getText(), ignore_key=self.key)
-            sender.setCompletionItems(self._parent.remaining_list)
+                self.manager.addToRemainingList([tryJID(sender.getText())], ignore_key=self.key)
+
+            items = [unicode(item) for item in self.manager.items_remaining]
+            sender.setCompletionItems(items)
+
+        def add_cb(sender):
+            self.addItem(tryJID(sender.getText()), sender)
 
         def remove_cb(sender):
-            """Callback for the button to remove this contact."""
-            self._parent.addToRemainingList(sender.getText())
-            self._parent.setRemainingListUnsorted()
+            """Callback for the button to remove this item."""
+            self.manager.addToRemainingList([tryJID(sender.getText())])
+            self.manager.setRemainingListUnsorted()
             self._last_textbox.setFocus(True)
 
         def drop_cb(sender, target):
             """Callback when the textbox is drag-n-dropped."""
-            parent = sender._parent
-            if target != parent and target.addContact(sender.getText()):
+            list_panel = sender.list_panel
+            if target != list_panel and target.addItem(tryJID(sender.getText())):
                 sender.removeOrReset()
             else:
-                parent._parent.removeFromRemainingList(sender.getText())
+                list_panel.manager.removeFromRemainingList([tryJID(sender.getText())])
 
-        events_cbs = {"focus": focus_cb, "validate": self.addContact, "remove": remove_cb, "drop": drop_cb}
+        events_cbs = {"focus": focus_cb, "validate": add_cb, "remove": remove_cb, "drop": drop_cb}
         textbox = DragAutoCompleteTextBox(self, events_cbs, self.style)
         self.add(textbox)
         if switchPrevious:
@@ -445,45 +488,41 @@
         self._last_textbox = textbox
         return previous if switchPrevious else textbox
 
-    def _checkContact(self, contact, modify):
-        """
-        @param contact: the contact to check
-        @param modify: True if the contact is being modified
-        @return:
-        - VALID if the contact is valid
-        - INVALID if the contact is not valid but can be displayed
-        - DELETE if the contact should not be displayed at all
+    def _checkItem(self, item, modify):
         """
-        def countItemInList(list_, item):
-            """For some reason the built-in count function doesn't work..."""
-            count = 0
-            for elem in list_:
-                if elem == item:
-                    count += 1
-            return count
-        if contact is None or contact == "":
+        @param item (object): the item to check
+        @param modify (bool): True if the item is being modified
+        @return: int value defined by one of these constants:
+            - VALID if the item is valid
+            - INVALID if the item is not valid but can be displayed
+            - DELETE if the item should not be displayed at all
+        """
+        def count(list_, item):
+            # XXX: list.count in not implemented by pyjamas
+            return len([elt for elt in list_ if elt == item])
+
+        if not item:
             return DELETE
-        if countItemInList(self.getContacts(), contact) > (1 if modify else 0):
+        if count(self.getItems(), item) > (1 if modify else 0):
             return DELETE
-        return VALID if contact in self._parent.list else INVALID
+        return VALID if item in self.manager.items else INVALID
+
+    def addItem(self, item, sender=None):
+        """
 
-    def addContact(self, contact, sender=None):
-        """The first parameter type is checked, so it is also possible to call addContact(sender).
-        If contact is not defined, sender.getText() is used. If sender is not defined, contact will
-        be written to the last textbox and a new textbox is added afterward.
-        @param contact: unicode
-        @param sender: DragAutoCompleteTextBox instance
+        @param item (object): item to be added
+        @param (DragAutoCompleteTextBox): widget triggering the event
+        @param sender: if True, the item will be "written" to the last textbox
+            and a new text box will be added afterward.
         """
-        if isinstance(contact, DragAutoCompleteTextBox):
-            sender = contact
-            contact = sender.getText()
-        valid = self._checkContact(contact, sender is not None)
+        valid = self._checkItem(item, sender is not None)
+        item_s = unicode(item)
         if sender is None:
-            # method has been called to modify but to add a contact
+            # method has been called not to modify but to add an item
             if valid == VALID:
                 # eventually insert before the last textbox if it's not empty
                 sender = self._addTextBox(True) if self._last_textbox.getText() != "" else self._last_textbox
-                sender.setText(contact)
+                sender.setText(item_s)
         else:
             sender.setValid(valid == VALID)
         if valid != VALID:
@@ -492,117 +531,35 @@
             return False
         if sender == self._last_textbox:
             self._addTextBox()
-        try:
-            sender.setVisibleLength(len(contact))
-        except:
-            # IndexSizeError: Index or size is negative or greater than the allowed amount
-            log.warning("FIXME: len(%s) returns %d... javascript bug?" % (contact, len(contact)))
-        self._parent.removeFromRemainingList(contact)
+        sender.setVisibleLength(len(item_s))
+        self.manager.removeFromRemainingList([item])
         self._last_textbox.setFocus(True)
         return True
 
-    def emptyContacts(self):
-        """Empty the list of contacts."""
+    def emptyItems(self):
+        """Empty the list of items."""
         for child in self.getChildren():
             if hasattr(child, "remove_btn"):
                 child.remove_btn.click()
 
-    def setContacts(self, tab):
-        """Set the contacts."""
-        self.emptyContacts()
-        if isinstance(tab, set):
-            tab = list(tab)
-        tab.sort()
-        for contact in tab:
-            self.addContact(contact)
-
-    def getContacts(self):
-        """Get the contacts
-        @return: an array of string"""
-        tab = []
-        for widget in self.getChildren():
-            if isinstance(widget, DragAutoCompleteTextBox):
-                # not to be mixed with EMPTY_SELECTION_ITEM
-                if widget.getText() != "":
-                    tab.append(widget.getText())
-        return tab
-
-    @property
-    def target_drop_cell(self):
-        """@return: the panel where something has been dropped."""
-        return self._parent.target_drop_cell
-
-    def setTargetDropCell(self, target_drop_cell):
-        """
-        XXX: Property setter here would not make it, you need a proper method!
-        @param target_drop_cell: the panel where something has been dropped."""
-        self._parent.setTargetDropCell(target_drop_cell)
-
-
-class ContactChooserPanel(DialogBox):
-    """Display the contacts chooser dialog. This has been implemented while
-    prototyping and is currently not used. Left for an eventual later use.
-    Replaced by the popup menu which allows to add a panel for Cc or Bcc.
-    """
-
-    def __init__(self, manager, **kwargs):
-        """Display a listbox for each contact key"""
-        DialogBox.__init__(self, autoHide=False, centered=True, **kwargs)
-        self.setHTML("Select contacts")
-        self.manager = manager
-        self.listboxes = {}
-        self.contacts = manager.getContacts()
-
-        container = VerticalPanel(Visible=True)
-        container.addStyleName("marginAuto")
+    def resetItems(self, items):
+        """Repopulate the items.
 
-        grid = Grid(2, len(self.manager.keys_dict))
-        index = -1
-        for key in self.manager.keys_dict:
-            index += 1
-            grid.add(Label("%s:" % self.manager.keys_dict[key]["desc"]), 0, index)
-            listbox = ListBox()
-            listbox.setMultipleSelect(True)
-            listbox.setVisibleItemCount(15)
-            listbox.addItem(EMPTY_SELECTION_ITEM)
-            for element in manager.list:
-                listbox.addItem(element)
-            self.listboxes[key] = listbox
-            grid.add(listbox, 1, index)
-        self._reset()
-
-        buttons = HorizontalPanel()
-        buttons.addStyleName("marginAuto")
-        btn_close = Button("Cancel", self.hide)
-        buttons.add(btn_close)
-        btn_reset = Button("Reset", self._reset)
-        buttons.add(btn_reset)
-        btn_ok = Button("OK", self._validate)
-        buttons.add(btn_ok)
+        @param items (list): the items to be listed.
+        """
+        self.emptyItems()
+        if isinstance(items, set):
+            items = list(items)
+        items.sort()
+        for item in items:
+            self.addItem(item)
 
-        container.add(grid)
-        container.add(buttons)
-
-        self.add(container)
-        self.center()
+    def getItems(self):
+        """Get the listed items.
 
-    def _reset(self):
-        """Reset the selections."""
-        for key in self.manager.keys_dict:
-            listbox = self.listboxes[key]
-            for i in xrange(0, listbox.getItemCount()):
-                if listbox.getItemText(i) in self.contacts[key]:
-                    listbox.setItemSelected(i, "selected")
-                else:
-                    listbox.setItemSelected(i, "")
-
-    def _validate(self):
-        """Sets back the selected contacts to the good sub-panels."""
-        _map = {}
-        for key in self.manager.keys_dict:
-            selections = self.listboxes[key].getSelectedItemText()
-            if EMPTY_SELECTION_ITEM in selections:
-                selections.remove(EMPTY_SELECTION_ITEM)
-            _map[key] = selections
-        self.manager.setContacts(_map)
-        self.hide()
+        @return: set"""
+        items = set()
+        for widget in self.getChildren():
+            if isinstance(widget, DragAutoCompleteTextBox) and widget.getText() != "":
+                items.add(tryJID(widget.getText()))
+        return items