changeset 449:981ed669d3b3

/!\ reorganize all the file hierarchy, move the code and launching script to src: - browser_side --> src/browser - public --> src/browser_side/public - libervia.py --> src/browser/libervia_main.py - libervia_server --> src/server - libervia_server/libervia.sh --> src/libervia.sh - twisted --> src/twisted - new module src/common - split constants.py in 3 files: - src/common/constants.py - src/browser/constants.py - src/server/constants.py - output --> html (generated by pyjsbuild during the installation) - new option/parameter "data_dir" (-d) to indicates the directory containing html and server_css - setup.py installs libervia to the following paths: - src/common --> <LIB>/libervia/common - src/server --> <LIB>/libervia/server - src/twisted --> <LIB>/twisted - html --> <SHARE>/libervia/html - server_side --> <SHARE>libervia/server_side - LIBERVIA_INSTALL environment variable takes 2 new options with prompt confirmation: - clean: remove previous installation directories - purge: remove building and previous installation directories You may need to update your sat.conf and/or launching script to update the following options/parameters: - ssl_certificate - data_dir
author souliane <souliane@mailoo.org>
date Tue, 20 May 2014 06:41:16 +0200
parents 14c35f7f1ef5
children 41aae13cab2b
files browser_side/__init__.py browser_side/base_panels.py browser_side/base_widget.py browser_side/card_game.py browser_side/contact.py browser_side/contact_group.py browser_side/dialog.py browser_side/file_tools.py browser_side/html_tools.py browser_side/jid.py browser_side/list_manager.py browser_side/logging.py browser_side/menu.py browser_side/nativedom.py browser_side/notification.py browser_side/panels.py browser_side/plugin_xep_0085.py browser_side/radiocol.py browser_side/register.py browser_side/richtext.py browser_side/xmlui.py constants.py libervia.py libervia_server/__init__.py libervia_server/blog.py libervia_server/html_tools.py libervia_server/libervia.sh public/contrat_social.html public/libervia.css public/libervia.html public/sat_logo_16.png setup.py src/__init__.py src/browser/__init__.py src/browser/base_panels.py src/browser/base_widget.py src/browser/card_game.py src/browser/constants.py src/browser/contact.py src/browser/contact_group.py src/browser/dialog.py src/browser/file_tools.py src/browser/html_tools.py src/browser/jid.py src/browser/libervia_main.py src/browser/list_manager.py src/browser/logging.py src/browser/menu.py src/browser/nativedom.py src/browser/notification.py src/browser/panels.py src/browser/plugin_xep_0085.py src/browser/public/contrat_social.html src/browser/public/libervia.css src/browser/public/libervia.html src/browser/public/sat_logo_16.png src/browser/radiocol.py src/browser/register.py src/browser/richtext.py src/browser/xmlui.py src/common/__init__.py src/common/constants.py src/libervia.sh src/server/__init__.py src/server/blog.py src/server/constants.py src/server/html_tools.py src/server/server.py src/twisted/plugins/libervia_server.py tools/__init__.py twisted/plugins/libervia.py
diffstat 65 files changed, 11796 insertions(+), 11601 deletions(-) [+]
line wrap: on
line diff
--- a/browser_side/base_panels.py	Fri May 16 11:51:10 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,609 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Libervia: a Salut à Toi frontend
-# Copyright (C) 2011, 2012, 2013, 2014 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/>.
-
-import pyjd  # this is dummy in pyjs
-from sat.core.log import getLogger
-log = getLogger(__name__)
-from pyjamas.ui.AbsolutePanel import AbsolutePanel
-from pyjamas.ui.VerticalPanel import VerticalPanel
-from pyjamas.ui.HorizontalPanel import HorizontalPanel
-from pyjamas.ui.HTMLPanel import HTMLPanel
-from pyjamas.ui.Button import Button
-from pyjamas.ui.HTML import HTML
-from pyjamas.ui.SimplePanel import SimplePanel
-from pyjamas.ui.PopupPanel import PopupPanel
-from pyjamas.ui.StackPanel import StackPanel
-from pyjamas.ui.TextArea import TextArea
-from pyjamas.ui.Event import BUTTON_LEFT, BUTTON_MIDDLE, BUTTON_RIGHT
-from pyjamas.ui.KeyboardListener import KEY_ENTER, KEY_SHIFT, KeyboardHandler
-from pyjamas.ui.FocusListener import FocusHandler
-from pyjamas.ui.ClickListener import ClickHandler
-from pyjamas import DOM
-
-from datetime import datetime
-from time import time
-
-from html_tools import html_sanitize, html_strip, inlineRoot, convertNewLinesToXHTML
-
-from constants import Const as C
-from sat_frontends.tools.strings import addURLToText, addURLToImage
-from sat.core.i18n import _
-
-
-class ChatText(HTMLPanel):
-
-    def __init__(self, timestamp, nick, mymess, msg, xhtml=None):
-        _date = datetime.fromtimestamp(float(timestamp or time()))
-        _msg_class = ["chat_text_msg"]
-        if mymess:
-            _msg_class.append("chat_text_mymess")
-        HTMLPanel.__init__(self, "<span class='chat_text_timestamp'>%(timestamp)s</span> <span class='chat_text_nick'>%(nick)s</span> <span class='%(msg_class)s'>%(msg)s</span>" %
-                           {"timestamp": _date.strftime("%H:%M"),
-                            "nick": "[%s]" % html_sanitize(nick),
-                            "msg_class": ' '.join(_msg_class),
-                            "msg": addURLToText(html_sanitize(msg)) if not xhtml else inlineRoot(xhtml)}  # FIXME: images and external links must be removed according to preferences
-                           )
-        self.setStyleName('chatText')
-
-
-class Occupant(HTML):
-    """Occupant of a MUC room"""
-
-    def __init__(self, nick, state=None, special=""):
-        """
-        @param nick: the user nickname
-        @param state: the user chate state (XEP-0085)
-        @param special: a string of symbols (e.g: for activities)
-        """
-        HTML.__init__(self)
-        self.nick = nick
-        self._state = state
-        self.special = special
-        self._refresh()
-
-    def __str__(self):
-        return self.nick
-
-    def setState(self, state):
-        self._state = state
-        self._refresh()
-
-    def addSpecial(self, special):
-        """@param special: unicode"""
-        if special not in self.special:
-            self.special += special
-            self._refresh()
-
-    def removeSpecials(self, special):
-        """@param special: unicode or list"""
-        if not isinstance(special, list):
-            special = [special]
-        for symbol in special:
-            self.special = self.special.replace(symbol, "")
-            self._refresh()
-
-    def _refresh(self):
-        state = (' %s' % C.MUC_USER_STATES[self._state]) if self._state else ''
-        special = "" if len(self.special) == 0 else " %s" % self.special
-        self.setHTML("<div class='occupant'>%s%s%s</div>" % (html_sanitize(self.nick), special, state))
-
-
-class OccupantsList(AbsolutePanel):
-    """Panel user to show occupants of a room"""
-
-    def __init__(self):
-        AbsolutePanel.__init__(self)
-        self.occupants_list = {}
-        self.setStyleName('occupantsList')
-
-    def addOccupant(self, nick):
-        _occupant = Occupant(nick)
-        self.occupants_list[nick] = _occupant
-        self.add(_occupant)
-
-    def removeOccupant(self, nick):
-        try:
-            self.remove(self.occupants_list[nick])
-        except KeyError:
-            log.error("trying to remove an unexisting nick")
-
-    def clear(self):
-        self.occupants_list.clear()
-        AbsolutePanel.clear(self)
-
-    def updateSpecials(self, occupants=[], html=""):
-        """Set the specified html "symbol" to the listed occupants,
-        and eventually remove it from the others (if they got it).
-        This is used for example to visualize who is playing a game.
-        @param occupants: list of the occupants that need the symbol
-        @param html: unicode symbol (actually one character or more)
-        or a list to assign different symbols of the same family.
-        """
-        index = 0
-        special = html
-        for occupant in self.occupants_list.keys():
-            if occupant in occupants:
-                if isinstance(html, list):
-                    special = html[index]
-                    index = (index + 1) % len(html)
-                self.occupants_list[occupant].addSpecial(special)
-            else:
-                self.occupants_list[occupant].removeSpecials(html)
-
-
-class PopupMenuPanel(PopupPanel):
-    """This implementation of a popup menu (context menu) allow you to assign
-    two special methods which are common to all the items, in order to hide
-    certain items and also easily define their callbacks. The menu can be
-    bound to any of the mouse button (left, middle, right).
-    """
-    def __init__(self, entries, hide=None, callback=None, vertical=True, style=None, **kwargs):
-        """
-        @param entries: a dict of dicts, where each sub-dict is representing
-        one menu item: the sub-dict key can be used as the item text and
-        description, but optional "title" and "desc" entries would be used
-        if they exists. The sub-dicts may be extended later to do
-        more complicated stuff or overwrite the common methods.
-        @param hide: function  with 2 args: widget, key as string and
-        returns True if that item should be hidden from the context menu.
-        @param callback: function with 2 args: sender, key as string
-        @param vertical: True or False, to set the direction
-        @param item_style: alternative CSS class for the menu items
-        @param menu_style: supplementary CSS class for the sender widget
-        """
-        PopupPanel.__init__(self, autoHide=True, **kwargs)
-        self._entries = entries
-        self._hide = hide
-        self._callback = callback
-        self.vertical = vertical
-        self.style = {"selected": None, "menu": "recipientTypeMenu", "item": "popupMenuItem"}
-        if isinstance(style, dict):
-            self.style.update(style)
-        self._senders = {}
-
-    def _show(self, sender):
-        """Popup the menu relative to this sender's position.
-        @param sender: the widget that has been clicked
-        """
-        menu = VerticalPanel() if self.vertical is True else HorizontalPanel()
-        menu.setStyleName(self.style["menu"])
-
-        def button_cb(item):
-            """You can not put that method in the loop and rely
-            on _key, because it is overwritten by each step.
-            You can rely on item.key instead, which is copied
-            from _key after the item creation.
-            @param item: the menu item that has been clicked
-            """
-            if self._callback is not None:
-                self._callback(sender=sender, key=item.key)
-            self.hide(autoClosed=True)
-
-        for _key in self._entries.keys():
-            entry = self._entries[_key]
-            if self._hide is not None and self._hide(sender=sender, key=_key) is True:
-                continue
-            title = entry["title"] if "title" in entry.keys() else _key
-            item = Button(title, button_cb)
-            item.key = _key
-            item.setStyleName(self.style["item"])
-            item.setTitle(entry["desc"] if "desc" in entry.keys() else title)
-            menu.add(item)
-        if len(menu.getChildren()) == 0:
-            return
-        self.add(menu)
-        if self.vertical is True:
-            x = sender.getAbsoluteLeft() + sender.getOffsetWidth()
-            y = sender.getAbsoluteTop()
-        else:
-            x = sender.getAbsoluteLeft()
-            y = sender.getAbsoluteTop() + sender.getOffsetHeight()
-        self.setPopupPosition(x, y)
-        self.show()
-        if self.style["selected"]:
-            sender.addStyleDependentName(self.style["selected"])
-
-        def _onHide(popup):
-            if self.style["selected"]:
-                sender.removeStyleDependentName(self.style["selected"])
-            return PopupPanel.onHideImpl(self, popup)
-
-        self.onHideImpl = _onHide
-
-    def registerClickSender(self, sender, button=BUTTON_LEFT):
-        """Bind the menu to the specified sender.
-        @param sender: the widget to which the menu should be bound
-        @param: BUTTON_LEFT, BUTTON_MIDDLE or BUTTON_RIGHT
-        """
-        self._senders.setdefault(sender, [])
-        self._senders[sender].append(button)
-
-        if button == BUTTON_RIGHT:
-            # WARNING: to disable the context menu is a bit tricky...
-            # The following seems to work on Firefox 24.0, but:
-            # TODO: find a cleaner way to disable the context menu
-            sender.getElement().setAttribute("oncontextmenu", "return false")
-
-        def _onBrowserEvent(event):
-            button = DOM.eventGetButton(event)
-            if DOM.eventGetType(event) == "mousedown" and button in self._senders[sender]:
-                self._show(sender)
-            return sender.__class__.onBrowserEvent(sender, event)
-
-        sender.onBrowserEvent = _onBrowserEvent
-
-    def registerMiddleClickSender(self, sender):
-        self.registerClickSender(sender, BUTTON_MIDDLE)
-
-    def registerRightClickSender(self, sender):
-        self.registerClickSender(sender, BUTTON_RIGHT)
-
-
-class ToggleStackPanel(StackPanel):
-    """This is a pyjamas.ui.StackPanel with modified behavior. All sub-panels ca be
-    visible at the same time, clicking a sub-panel header will not display it and hide
-    the others but only toggle its own visibility. The argument 'visibleStack' is ignored.
-    Note that the argument 'visible' has been added to listener's 'onStackChanged' method.
-    """
-
-    def __init__(self, **kwargs):
-        StackPanel.__init__(self, **kwargs)
-
-    def onBrowserEvent(self, event):
-        if DOM.eventGetType(event) == "click":
-            index = self.getDividerIndex(DOM.eventGetTarget(event))
-            if index != -1:
-                self.toggleStack(index)
-
-    def add(self, widget, stackText="", asHTML=False, visible=False):
-        StackPanel.add(self, widget, stackText, asHTML)
-        self.setStackVisible(self.getWidgetCount() - 1, visible)
-
-    def toggleStack(self, index):
-        if index >= self.getWidgetCount():
-            return
-        visible = not self.getWidget(index).getVisible()
-        self.setStackVisible(index, visible)
-        for listener in self.stackListeners:
-            listener.onStackChanged(self, index, visible)
-
-
-class TitlePanel(ToggleStackPanel):
-    """A toggle panel to set the message title"""
-    def __init__(self):
-        ToggleStackPanel.__init__(self, Width="100%")
-        self.text_area = TextArea()
-        self.add(self.text_area, _("Title"))
-        self.addStackChangeListener(self)
-
-    def onStackChanged(self, sender, index, visible=None):
-        if visible is None:
-            visible = sender.getWidget(index).getVisible()
-        text = self.text_area.getText()
-        suffix = "" if (visible or not text) else (": %s" % text)
-        sender.setStackText(index, _("Title") + suffix)
-
-    def getText(self):
-        return self.text_area.getText()
-
-    def setText(self, text):
-        self.text_area.setText(text)
-
-
-class BaseTextEditor(object):
-    """Basic definition of a text editor. The method edit gets a boolean parameter which
-    should be set to True when you want to edit the text and False to only display it."""
-
-    def __init__(self, content=None, strproc=None, modifiedCb=None, afterEditCb=None):
-        """
-        Remark when inheriting this class: since the setContent method could be
-        overwritten by the child class, you should consider calling this __init__
-        after all the parameters affecting this setContent method have been set.
-        @param content: dict with at least a 'text' key
-        @param strproc: method to be applied on strings to clean the content
-        @param modifiedCb: method to be called when the text has been modified.
-        If this method returns:
-        - True: the modification will be saved and afterEditCb called;
-        - False: the modification won't be saved and afterEditCb called;
-        - None: the modification won't be saved and afterEditCb not called.
-        @param afterEditCb: method to be called when the edition is done
-        """
-        if content is None:
-            content = {'text': ''}
-        assert('text' in content)
-        if strproc is None:
-            def strproc(text):
-                try:
-                    return text.strip()
-                except (TypeError, AttributeError):
-                    return text
-        self.strproc = strproc
-        self.__modifiedCb = modifiedCb
-        self._afterEditCb = afterEditCb
-        self.initialized = False
-        self.edit_listeners = []
-        self.setContent(content)
-
-    def setContent(self, content=None):
-        """Set the editable content. The displayed content, which is set from the child class, could differ.
-        @param content: dict with at least a 'text' key
-        """
-        if content is None:
-            content = {'text': ''}
-        elif not isinstance(content, dict):
-            content = {'text': content}
-        assert('text' in content)
-        self._original_content = {}
-        for key in content:
-            self._original_content[key] = self.strproc(content[key])
-
-    def getContent(self):
-        """Get the current edited or editable content.
-        @return: dict with at least a 'text' key
-        """
-        raise NotImplementedError
-
-    def setOriginalContent(self, content):
-        """Use this method with care! Content initialization should normally be
-        done with self.setContent. This method exists to let you trick the editor,
-        e.g. for self.modified to return True also when nothing has been modified.
-        @param content: dict
-        """
-        self._original_content = content
-
-    def getOriginalContent(self):
-        """
-        @return the original content before modification (dict)
-        """
-        return self._original_content
-
-    def modified(self, content=None):
-        """Check if the content has been modified.
-        Remark: we don't use the direct comparison because we want to ignore empty elements
-        @content: content to be check against the original content or None to use the current content
-        @return: True if the content has been modified.
-        """
-        if content is None:
-            content = self.getContent()
-        # the following method returns True if one non empty element exists in a but not in b
-        diff1 = lambda a, b: [a[key] for key in set(a.keys()).difference(b.keys()) if a[key]] != []
-        # the following method returns True if the values for the common keys are not equals
-        diff2 = lambda a, b: [1 for key in set(a.keys()).intersection(b.keys()) if a[key] != b[key]] != []
-        # finally the combination of both to return True if a difference is found
-        diff = lambda a, b: diff1(a, b) or diff1(b, a) or diff2(a, b)
-
-        return diff(content, self._original_content)
-
-    def edit(self, edit, abort=False, sync=False):
-        """
-        Remark: the editor must be visible before you call this method.
-        @param edit: set to True to edit the content or False to only display it
-        @param abort: set to True to cancel the edition and loose the changes.
-        If edit and abort are both True, self.abortEdition can be used to ask for a
-        confirmation. When edit is False and abort is True, abortion is actually done.
-        @param sync: set to True to cancel the edition after the content has been saved somewhere else
-        """
-        if edit:
-            if not self.initialized:
-                self.syncToEditor()  # e.g.: use the selected target and unibox content
-            self.setFocus(True)
-            if abort:
-                content = self.getContent()
-                if not self.modified(content) or self.abortEdition(content):  # e.g: ask for confirmation
-                    self.edit(False, True, sync)
-                    return
-            if sync:
-                self.syncFromEditor(content)  # e.g.: save the content to unibox
-                return
-        else:
-            if not self.initialized:
-                return
-            content = self.getContent()
-            if abort:
-                self._afterEditCb(content)
-                return
-            if self.__modifiedCb and self.modified(content):
-                result = self.__modifiedCb(content)  # e.g.: send a message or update something
-                if result is not None:
-                    if self._afterEditCb:
-                        self._afterEditCb(content)  # e.g.: restore the display mode
-                    if result is True:
-                        self.setContent(content)
-            elif self._afterEditCb:
-                self._afterEditCb(content)
-
-        self.initialized = True
-
-    def setFocus(self, focus):
-        """
-        @param focus: set to True to focus the editor
-        """
-        raise NotImplementedError
-
-    def syncToEditor(self):
-        pass
-
-    def syncFromEditor(self, content):
-        pass
-
-    def abortEdition(self, content):
-        return True
-
-    def addEditListener(self, listener):
-        """Add a method to be called whenever the text is edited.
-        @param listener: method taking two arguments: sender, keycode"""
-        self.edit_listeners.append(listener)
-
-
-class SimpleTextEditor(BaseTextEditor, FocusHandler, KeyboardHandler, ClickHandler):
-    """Base class for manage a simple text editor."""
-
-    def __init__(self, content=None, modifiedCb=None, afterEditCb=None, options=None):
-        """
-        @param content
-        @param modifiedCb
-        @param afterEditCb
-        @param options: dict with the following value:
-        - no_xhtml: set to True to clean any xhtml content.
-        - enhance_display: if True, the display text will be enhanced with addURLToText
-        - listen_keyboard: set to True to terminate the edition with <enter> or <escape>.
-        - listen_focus: set to True to terminate the edition when the focus is lost.
-        - listen_click: set to True to start the edition when you click on the widget.
-        """
-        self.options = {'no_xhtml': False,
-                        'enhance_display': True,
-                        'listen_keyboard': True,
-                        'listen_focus': False,
-                        'listen_click': False
-                        }
-        if options:
-            self.options.update(options)
-        self.__shift_down = False
-        if self.options['listen_focus']:
-            FocusHandler.__init__(self)
-        if self.options['listen_click']:
-            ClickHandler.__init__(self)
-        KeyboardHandler.__init__(self)
-        strproc = lambda text: html_sanitize(html_strip(text)) if self.options['no_xhtml'] else html_strip(text)
-        BaseTextEditor.__init__(self, content, strproc, modifiedCb, afterEditCb)
-        self.textarea = self.display = None
-
-    def setContent(self, content=None):
-        BaseTextEditor.setContent(self, content)
-
-    def getContent(self):
-        raise NotImplementedError
-
-    def edit(self, edit, abort=False, sync=False):
-        BaseTextEditor.edit(self, edit)
-        if edit:
-            if self.options['listen_focus'] and self not in self.textarea._focusListeners:
-                self.textarea.addFocusListener(self)
-            if self.options['listen_click']:
-                self.display.clearClickListener()
-            if self not in self.textarea._keyboardListeners:
-                self.textarea.addKeyboardListener(self)
-        else:
-            self.setDisplayContent()
-            if self.options['listen_focus']:
-                try:
-                    self.textarea.removeFocusListener(self)
-                except ValueError:
-                    pass
-            if self.options['listen_click'] and self not in self.display._clickListeners:
-                self.display.addClickListener(self)
-            try:
-                self.textarea.removeKeyboardListener(self)
-            except ValueError:
-                pass
-
-    def setDisplayContent(self):
-        text = self._original_content['text']
-        if not self.options['no_xhtml']:
-            text = addURLToImage(text)
-        if self.options['enhance_display']:
-            text = addURLToText(text)
-        self.display.setHTML(convertNewLinesToXHTML(text))
-
-    def setFocus(self, focus):
-        raise NotImplementedError
-
-    def onKeyDown(self, sender, keycode, modifiers):
-        for listener in self.edit_listeners:
-            listener(self.textarea, keycode)
-        if not self.options['listen_keyboard']:
-            return
-        if keycode == KEY_SHIFT or self.__shift_down:  # allow input a new line with <shift> + <enter>
-            self.__shift_down = True
-            return
-        if keycode == KEY_ENTER:  # finish the edition
-            self.textarea.setFocus(False)
-            if not self.options['listen_focus']:
-                self.edit(False)
-
-    def onKeyUp(self, sender, keycode, modifiers):
-        if keycode == KEY_SHIFT:
-            self.__shift_down = False
-
-    def onLostFocus(self, sender):
-        """Finish the edition when focus is lost"""
-        if self.options['listen_focus']:
-            self.edit(False)
-
-    def onClick(self, sender=None):
-        """Start the edition when the widget is clicked"""
-        if self.options['listen_click']:
-            self.edit(True)
-
-    def onBrowserEvent(self, event):
-        if self.options['listen_focus']:
-            FocusHandler.onBrowserEvent(self, event)
-        if self.options['listen_click']:
-            ClickHandler.onBrowserEvent(self, event)
-        KeyboardHandler.onBrowserEvent(self, event)
-
-
-class HTMLTextEditor(SimpleTextEditor, HTML, FocusHandler, KeyboardHandler):
-    """Manage a simple text editor with the HTML 5 "contenteditable" property."""
-
-    def __init__(self, content=None, modifiedCb=None, afterEditCb=None, options=None):
-        HTML.__init__(self)
-        SimpleTextEditor.__init__(self, content, modifiedCb, afterEditCb, options)
-        self.textarea = self.display = self
-
-    def getContent(self):
-        text = DOM.getInnerHTML(self.getElement())
-        return {'text': self.strproc(text) if text else ''}
-
-    def edit(self, edit, abort=False, sync=False):
-        if edit:
-            self.textarea.setHTML(self._original_content['text'])
-        self.getElement().setAttribute('contenteditable', 'true' if edit else 'false')
-        SimpleTextEditor.edit(self, edit, abort, sync)
-
-    def setFocus(self, focus):
-        if focus:
-            self.getElement().focus()
-        else:
-            self.getElement().blur()
-
-
-class LightTextEditor(SimpleTextEditor, SimplePanel, FocusHandler, KeyboardHandler):
-    """Manage a simple text editor with a TextArea for editing, HTML for display."""
-
-    def __init__(self, content=None, modifiedCb=None, afterEditCb=None, options=None):
-        SimplePanel.__init__(self)
-        SimpleTextEditor.__init__(self, content, modifiedCb, afterEditCb, options)
-        self.textarea = TextArea()
-        self.display = HTML()
-
-    def getContent(self):
-        text = self.textarea.getText()
-        return {'text': self.strproc(text) if text else ''}
-
-    def edit(self, edit, abort=False, sync=False):
-        if edit:
-            self.textarea.setText(self._original_content['text'])
-        self.setWidget(self.textarea if edit else self.display)
-        SimpleTextEditor.edit(self, edit, abort, sync)
-
-    def setFocus(self, focus):
-        if focus:
-            self.textarea.setCursorPos(len(self.textarea.getText()))
-        self.textarea.setFocus(focus)
--- a/browser_side/base_widget.py	Fri May 16 11:51:10 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,730 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Libervia: a Salut à Toi frontend
-# Copyright (C) 2011, 2012, 2013, 2014 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/>.
-
-import pyjd  # this is dummy in pyjs
-from sat.core.log import getLogger
-log = getLogger(__name__)
-from pyjamas.ui.SimplePanel import SimplePanel
-from pyjamas.ui.AbsolutePanel import AbsolutePanel
-from pyjamas.ui.VerticalPanel import VerticalPanel
-from pyjamas.ui.HorizontalPanel import HorizontalPanel
-from pyjamas.ui.ScrollPanel import ScrollPanel
-from pyjamas.ui.FlexTable import FlexTable
-from pyjamas.ui.TabPanel import TabPanel
-from pyjamas.ui.HTMLPanel import HTMLPanel
-from pyjamas.ui.Label import Label
-from pyjamas.ui.Button import Button
-from pyjamas.ui.Image import Image
-from pyjamas.ui.Widget import Widget
-from pyjamas.ui.DragWidget import DragWidget
-from pyjamas.ui.DropWidget import DropWidget
-from pyjamas.ui.ClickListener import ClickHandler
-from pyjamas.ui import HasAlignment
-from pyjamas import DOM
-from pyjamas import Window
-from __pyjamas__ import doc
-
-from browser_side import dialog
-
-
-class DragLabel(DragWidget):
-
-    def __init__(self, text, _type):
-        DragWidget.__init__(self)
-        self._text = text
-        self._type = _type
-
-    def onDragStart(self, event):
-        dt = event.dataTransfer
-        dt.setData('text/plain', "%s\n%s" % (self._text, self._type))
-        dt.setDragImage(self.getElement(), 15, 15)
-
-
-class LiberviaDragWidget(DragLabel):
-    """ A DragLabel which keep the widget being dragged as class value """
-    current = None  # widget currently dragged
-
-    def __init__(self, text, _type, widget):
-        DragLabel.__init__(self, text, _type)
-        self.widget = widget
-
-    def onDragStart(self, event):
-        LiberviaDragWidget.current = self.widget
-        DragLabel.onDragStart(self, event)
-
-    def onDragEnd(self, event):
-        LiberviaDragWidget.current = None
-
-
-class DropCell(DropWidget):
-    """Cell in the middle grid which replace itself with the dropped widget on DnD"""
-    drop_keys = {}
-
-    def __init__(self, host):
-        DropWidget.__init__(self)
-        self.host = host
-        self.setStyleName('dropCell')
-
-    @classmethod
-    def addDropKey(cls, key, callback):
-        DropCell.drop_keys[key] = callback
-
-    def onDragEnter(self, event):
-        if self == LiberviaDragWidget.current:
-            return
-        self.addStyleName('dragover')
-        DOM.eventPreventDefault(event)
-
-    def onDragLeave(self, event):
-        if event.clientX <= self.getAbsoluteLeft() or event.clientY <= self.getAbsoluteTop() or\
-            event.clientX >= self.getAbsoluteLeft() + self.getOffsetWidth() - 1 or event.clientY >= self.getAbsoluteTop() + self.getOffsetHeight() - 1:
-            # We check that we are inside widget's box, and we don't remove the style in this case because
-            # if the mouse is over a widget inside the DropWidget, if will leave the DropWidget, and we
-            # don't want that
-            self.removeStyleName('dragover')
-
-    def onDragOver(self, event):
-        DOM.eventPreventDefault(event)
-
-    def _getCellAndRow(self, grid, event):
-        """Return cell and row index where the event is occuring"""
-        cell = grid.getEventTargetCell(event)
-        row = DOM.getParent(cell)
-        return (row.rowIndex, cell.cellIndex)
-
-    def onDrop(self, event):
-        self.removeStyleName('dragover')
-        DOM.eventPreventDefault(event)
-        dt = event.dataTransfer
-        # 'text', 'text/plain', and 'Text' are equivalent.
-        try:
-            item, item_type = dt.getData("text/plain").split('\n')  # Workaround for webkit, only text/plain seems to be managed
-            if item_type and item_type[-1] == '\0':  # Workaround for what looks like a pyjamas bug: the \0 should not be there, and
-                item_type = item_type[:-1]           # .strip('\0') and .replace('\0','') don't work. TODO: check this and fill a bug report
-            # item_type = dt.getData("type")
-            log.debug("message: %s" % item)
-            log.debug("type: %s" % item_type)
-        except:
-            log.debug("no message found")
-            item = '&nbsp;'
-            item_type = None
-        if item_type == "WIDGET":
-            if not LiberviaDragWidget.current:
-                log.error("No widget registered in LiberviaDragWidget !")
-                return
-            _new_panel = LiberviaDragWidget.current
-            if self == _new_panel:  # We can't drop on ourself
-                return
-            # we need to remove the widget from the panel as it will be inserted elsewhere
-            widgets_panel = _new_panel.getWidgetsPanel()
-            wid_row = widgets_panel.getWidgetCoords(_new_panel)[0]
-            row_wids = widgets_panel.getLiberviaRowWidgets(wid_row)
-            if len(row_wids) == 1 and wid_row == widgets_panel.getWidgetCoords(self)[0]:
-                # the dropped widget is the only one in the same row
-                # as the target widget (self), we don't do anything
-                return
-            widgets_panel.removeWidget(_new_panel)
-        elif item_type in self.drop_keys:
-            _new_panel = self.drop_keys[item_type](self.host, item)
-        else:
-            log.warning("unmanaged item type")
-            return
-        if isinstance(self, LiberviaWidget):
-            self.host.unregisterWidget(self)
-            self.onQuit()
-            if not isinstance(_new_panel, LiberviaWidget):
-                log.warning("droping an object which is not a class of LiberviaWidget")
-        _flextable = self.getParent()
-        _widgetspanel = _flextable.getParent().getParent()
-        row_idx, cell_idx = self._getCellAndRow(_flextable, event)
-        if self.host.getSelected == self:
-            self.host.setSelected(None)
-        _widgetspanel.changeWidget(row_idx, cell_idx, _new_panel)
-        """_unempty_panels = filter(lambda wid:not isinstance(wid,EmptyWidget),list(_flextable))
-        _width = 90/float(len(_unempty_panels) or 1)
-        #now we resize all the cell of the column
-        for panel in _unempty_panels:
-            td_elt = panel.getElement().parentNode
-            DOM.setStyleAttribute(td_elt, "width", "%s%%" % _width)"""
-        #FIXME: delete object ? Check the right way with pyjamas
-
-
-class WidgetHeader(AbsolutePanel, LiberviaDragWidget):
-
-    def __init__(self, parent, title):
-        AbsolutePanel.__init__(self)
-        self.add(title)
-        button_group_wrapper = SimplePanel()
-        button_group_wrapper.setStyleName('widgetHeader_buttonsWrapper')
-        button_group = HorizontalPanel()
-        button_group.setStyleName('widgetHeader_buttonGroup')
-        setting_button = Image("media/icons/misc/settings.png")
-        setting_button.setStyleName('widgetHeader_settingButton')
-        setting_button.addClickListener(parent.onSetting)
-        close_button = Image("media/icons/misc/close.png")
-        close_button.setStyleName('widgetHeader_closeButton')
-        close_button.addClickListener(parent.onClose)
-        button_group.add(setting_button)
-        button_group.add(close_button)
-        button_group_wrapper.setWidget(button_group)
-        self.add(button_group_wrapper)
-        self.addStyleName('widgetHeader')
-        LiberviaDragWidget.__init__(self, "", "WIDGET", parent)
-
-
-class LiberviaWidget(DropCell, VerticalPanel, ClickHandler):
-    """Libervia's widget which can replace itself with a dropped widget on DnD"""
-
-    def __init__(self, host, title='', selectable=False):
-        """Init the widget
-        @param host: SatWebFrontend object
-        @param title: title show in the header of the widget
-        @param selectable: True is widget can be selected by user"""
-        VerticalPanel.__init__(self)
-        DropCell.__init__(self, host)
-        ClickHandler.__init__(self)
-        self.__selectable = selectable
-        self.__title_id = HTMLPanel.createUniqueId()
-        self.__setting_button_id = HTMLPanel.createUniqueId()
-        self.__close_button_id = HTMLPanel.createUniqueId()
-        self.__title = Label(title)
-        self.__title.setStyleName('widgetHeader_title')
-        self._close_listeners = []
-        header = WidgetHeader(self, self.__title)
-        self.add(header)
-        self.setSize('100%', '100%')
-        self.addStyleName('widget')
-        if self.__selectable:
-            self.addClickListener(self)
-
-            def onClose(sender):
-                """Check dynamically if the unibox is enable or not"""
-                if self.host.uni_box:
-                    self.host.uni_box.onWidgetClosed(sender)
-
-            self.addCloseListener(onClose)
-        self.host.registerWidget(self)
-
-    def getDebugName(self):
-        return "%s (%s)" % (self, self.__title.getText())
-
-    def getWidgetsPanel(self, verbose=True):
-        return self.getParent(WidgetsPanel, verbose)
-
-    def getParent(self, class_=None, verbose=True):
-        """
-        Note: this method overrides pyjamas.ui.Widget.getParent
-        @param class_: class of the ancestor to look for or None to return the first parent
-        @param verbose: set to True to log error messages # FIXME: must be removed
-        @return: the parent/ancestor or None if it has not been found
-        """
-        current = Widget.getParent(self)
-        if class_ is None:
-            return current  # this is the default behavior
-        while current is not None and not isinstance(current, class_):
-            current = Widget.getParent(current)
-        if current is None and verbose:
-            log.debug("Can't find parent %s for %s" % (class_, self))
-        return current
-
-    def onClick(self, sender):
-        self.host.setSelected(self)
-
-    def onClose(self, sender):
-        """ Called when the close button is pushed """
-        _widgetspanel = self.getWidgetsPanel()
-        _widgetspanel.removeWidget(self)
-        for callback in self._close_listeners:
-            callback(self)
-        self.onQuit()
-
-    def onQuit(self):
-        """ Called when the widget is actually ending """
-        pass
-
-    def addCloseListener(self, callback):
-        """Add a close listener to this widget
-        @param callback: function to be called from self.onClose"""
-        self._close_listeners.append(callback)
-
-    def refresh(self):
-        """This can be overwritten by a child class to refresh the display when,
-        instead of creating a new one, an existing widget is found and reused.
-        """
-        pass
-
-    def onSetting(self, sender):
-        widpanel = self.getWidgetsPanel()
-        row, col = widpanel.getIndex(self)
-        body = VerticalPanel()
-
-        # colspan & rowspan
-        colspan = widpanel.getColSpan(row, col)
-        rowspan = widpanel.getRowSpan(row, col)
-
-        def onColSpanChange(value):
-            widpanel.setColSpan(row, col, value)
-
-        def onRowSpanChange(value):
-            widpanel.setRowSpan(row, col, value)
-        colspan_setter = dialog.IntSetter("Columns span", colspan)
-        colspan_setter.addValueChangeListener(onColSpanChange)
-        colspan_setter.setWidth('100%')
-        rowspan_setter = dialog.IntSetter("Rows span", rowspan)
-        rowspan_setter.addValueChangeListener(onRowSpanChange)
-        rowspan_setter.setWidth('100%')
-        body.add(colspan_setter)
-        body.add(rowspan_setter)
-
-        # size
-        width_str = self.getWidth()
-        if width_str.endswith('px'):
-            width = int(width_str[:-2])
-        else:
-            width = 0
-        height_str = self.getHeight()
-        if height_str.endswith('px'):
-            height = int(height_str[:-2])
-        else:
-            height = 0
-
-        def onWidthChange(value):
-            if not value:
-                self.setWidth('100%')
-            else:
-                self.setWidth('%dpx' % value)
-
-        def onHeightChange(value):
-            if not value:
-                self.setHeight('100%')
-            else:
-                self.setHeight('%dpx' % value)
-        width_setter = dialog.IntSetter("width (0=auto)", width)
-        width_setter.addValueChangeListener(onWidthChange)
-        width_setter.setWidth('100%')
-        height_setter = dialog.IntSetter("height (0=auto)", height)
-        height_setter.addValueChangeListener(onHeightChange)
-        height_setter.setHeight('100%')
-        body.add(width_setter)
-        body.add(height_setter)
-
-        # reset
-        def onReset(sender):
-            colspan_setter.setValue(1)
-            rowspan_setter.setValue(1)
-            width_setter.setValue(0)
-            height_setter.setValue(0)
-
-        reset_bt = Button("Reset", onReset)
-        body.add(reset_bt)
-        body.setCellHorizontalAlignment(reset_bt, HasAlignment.ALIGN_CENTER)
-
-        _dialog = dialog.GenericDialog("Widget setting", body)
-        _dialog.show()
-
-    def setTitle(self, text):
-        """change the title in the header of the widget
-        @param text: text of the new title"""
-        self.__title.setText(text)
-
-    def isSelectable(self):
-        return self.__selectable
-
-    def setSelectable(self, selectable):
-        if not self.__selectable:
-            try:
-                self.removeClickListener(self)
-            except ValueError:
-                pass
-        if self.selectable and not self in self._clickListeners:
-            self.addClickListener(self)
-        self.__selectable = selectable
-
-    def getWarningData(self):
-        """ Return exposition warning level when this widget is selected and something is sent to it
-        This method should be overriden by children
-        @return: tuple (warning level type/HTML msg). Type can be one of:
-            - PUBLIC
-            - GROUP
-            - ONE2ONE
-            - MISC
-            - NONE
-        """
-        if not self.__selectable:
-            log.error("getWarningLevel must not be called for an unselectable widget")
-            raise Exception
-        # TODO: cleaner warning types (more general constants)
-        return ("NONE", None)
-
-    def setWidget(self, widget, scrollable=True):
-        """Set the widget that will be in the body of the LiberviaWidget
-        @param widget: widget to put in the body
-        @param scrollable: if true, the widget will be in a ScrollPanelWrapper"""
-        if scrollable:
-            _scrollpanelwrapper = ScrollPanelWrapper()
-            _scrollpanelwrapper.setStyleName('widgetBody')
-            _scrollpanelwrapper.setWidget(widget)
-            body_wid = _scrollpanelwrapper
-        else:
-            body_wid = widget
-        self.add(body_wid)
-        self.setCellHeight(body_wid, '100%')
-
-    def doDetachChildren(self):
-        # We need to force the use of a panel subclass method here,
-        # for the same reason as doAttachChildren
-        VerticalPanel.doDetachChildren(self)
-
-    def doAttachChildren(self):
-        # We need to force the use of a panel subclass method here, else
-        # the event will not propagate to children
-        VerticalPanel.doAttachChildren(self)
-
-    def matchEntity(self, entity):
-        """This method should be overwritten by child classes."""
-        raise NotImplementedError
-
-
-class ScrollPanelWrapper(SimplePanel):
-    """Scroll Panel like component, wich use the full available space
-    to work around percent size issue, it use some of the ideas found
-    here: http://code.google.com/p/google-web-toolkit/issues/detail?id=316
-    specially in code given at comment #46, thanks to Stefan Bachert"""
-
-    def __init__(self, *args, **kwargs):
-        SimplePanel.__init__(self)
-        self.spanel = ScrollPanel(*args, **kwargs)
-        SimplePanel.setWidget(self, self.spanel)
-        DOM.setStyleAttribute(self.getElement(), "position", "relative")
-        DOM.setStyleAttribute(self.getElement(), "top", "0px")
-        DOM.setStyleAttribute(self.getElement(), "left", "0px")
-        DOM.setStyleAttribute(self.getElement(), "width", "100%")
-        DOM.setStyleAttribute(self.getElement(), "height", "100%")
-        DOM.setStyleAttribute(self.spanel.getElement(), "position", "absolute")
-        DOM.setStyleAttribute(self.spanel.getElement(), "width", "100%")
-        DOM.setStyleAttribute(self.spanel.getElement(), "height", "100%")
-
-    def setWidget(self, widget):
-        self.spanel.setWidget(widget)
-
-    def setScrollPosition(self, position):
-        self.spanel.setScrollPosition(position)
-
-    def scrollToBottom(self):
-        self.setScrollPosition(self.spanel.getElement().scrollHeight)
-
-
-class EmptyWidget(DropCell, SimplePanel):
-    """Empty dropable panel"""
-
-    def __init__(self, host):
-        SimplePanel.__init__(self)
-        DropCell.__init__(self, host)
-        #self.setWidget(HTML(''))
-        self.setSize('100%', '100%')
-
-
-class BorderWidget(EmptyWidget):
-    def __init__(self, host):
-        EmptyWidget.__init__(self, host)
-        self.addStyleName('borderPanel')
-
-
-class LeftBorderWidget(BorderWidget):
-    def __init__(self, host):
-        BorderWidget.__init__(self, host)
-        self.addStyleName('leftBorderWidget')
-
-
-class RightBorderWidget(BorderWidget):
-    def __init__(self, host):
-        BorderWidget.__init__(self, host)
-        self.addStyleName('rightBorderWidget')
-
-
-class BottomBorderWidget(BorderWidget):
-    def __init__(self, host):
-        BorderWidget.__init__(self, host)
-        self.addStyleName('bottomBorderWidget')
-
-
-class WidgetsPanel(ScrollPanelWrapper):
-
-    def __init__(self, host, locked=False):
-        ScrollPanelWrapper.__init__(self)
-        self.setSize('100%', '100%')
-        self.host = host
-        self.locked = locked  # if True: tab will not be removed when there are no more widgets inside
-        self.selected = None
-        self.flextable = FlexTable()
-        self.flextable.setSize('100%', '100%')
-        self.setWidget(self.flextable)
-        self.setStyleName('widgetsPanel')
-        _bottom = BottomBorderWidget(self.host)
-        self.flextable.setWidget(0, 0, _bottom)  # There will be always an Empty widget on the last row,
-                                                 # dropping a widget there will add a new row
-        td_elt = _bottom.getElement().parentNode
-        DOM.setStyleAttribute(td_elt, "height", "1px")  # needed so the cell adapt to the size of the border (specially in webkit)
-        self._max_cols = 1  # give the maximum number of columns i a raw
-
-    def isLocked(self):
-        return self.locked
-
-    def changeWidget(self, row, col, wid):
-        """Change the widget in the given location, add row or columns when necessary"""
-        log.debug("changing widget: %s %s %s" % (wid.getDebugName(), row, col))
-        last_row = max(0, self.flextable.getRowCount() - 1)
-        try:
-            prev_wid = self.flextable.getWidget(row, col)
-        except:
-            log.error("Trying to change an unexisting widget !")
-            return
-
-        cellFormatter = self.flextable.getFlexCellFormatter()
-
-        if isinstance(prev_wid, BorderWidget):
-            # We are on a border, we must create a row and/or columns
-            log.debug("BORDER WIDGET")
-            prev_wid.removeStyleName('dragover')
-
-            if isinstance(prev_wid, BottomBorderWidget):
-                # We are on the bottom border, we create a new row
-                self.flextable.insertRow(last_row)
-                self.flextable.setWidget(last_row, 0, LeftBorderWidget(self.host))
-                self.flextable.setWidget(last_row, 1, wid)
-                self.flextable.setWidget(last_row, 2, RightBorderWidget(self.host))
-                cellFormatter.setHorizontalAlignment(last_row, 2, HasAlignment.ALIGN_RIGHT)
-                row = last_row
-
-            elif isinstance(prev_wid, LeftBorderWidget):
-                if col != 0:
-                    log.error("LeftBorderWidget must be on the first column !")
-                    return
-                self.flextable.insertCell(row, col + 1)
-                self.flextable.setWidget(row, 1, wid)
-
-            elif isinstance(prev_wid, RightBorderWidget):
-                if col != self.flextable.getCellCount(row) - 1:
-                    log.error("RightBorderWidget must be on the last column !")
-                    return
-                self.flextable.insertCell(row, col)
-                self.flextable.setWidget(row, col, wid)
-
-        else:
-            prev_wid.removeFromParent()
-            self.flextable.setWidget(row, col, wid)
-
-        _max_cols = max(self._max_cols, self.flextable.getCellCount(row))
-        if _max_cols != self._max_cols:
-            self._max_cols = _max_cols
-            self._sizesAdjust()
-
-    def _sizesAdjust(self):
-        cellFormatter = self.flextable.getFlexCellFormatter()
-        width = 100.0 / max(1, self._max_cols - 2)  # we don't count the borders
-
-        for row_idx in xrange(self.flextable.getRowCount()):
-            for col_idx in xrange(self.flextable.getCellCount(row_idx)):
-                _widget = self.flextable.getWidget(row_idx, col_idx)
-                if not isinstance(_widget, BorderWidget):
-                    td_elt = _widget.getElement().parentNode
-                    DOM.setStyleAttribute(td_elt, "width", "%.2f%%" % width)
-
-        last_row = max(0, self.flextable.getRowCount() - 1)
-        cellFormatter.setColSpan(last_row, 0, self._max_cols)
-
-    def addWidget(self, wid):
-        """Add a widget to a new cell on the next to last row"""
-        last_row = max(0, self.flextable.getRowCount() - 1)
-        log.debug("putting widget %s at %d, %d" % (wid.getDebugName(), last_row, 0))
-        self.changeWidget(last_row, 0, wid)
-
-    def removeWidget(self, wid):
-        """Remove a widget and the cell where it is"""
-        _row, _col = self.flextable.getIndex(wid)
-        self.flextable.remove(wid)
-        self.flextable.removeCell(_row, _col)
-        if not self.getLiberviaRowWidgets(_row):  # we have no more widgets, we remove the row
-            self.flextable.removeRow(_row)
-        _max_cols = 1
-        for row_idx in xrange(self.flextable.getRowCount()):
-            _max_cols = max(_max_cols, self.flextable.getCellCount(row_idx))
-        if _max_cols != self._max_cols:
-            self._max_cols = _max_cols
-            self._sizesAdjust()
-        current = self
-
-        blank_page = self.getLiberviaWidgetsCount() == 0  # do we still have widgets on the page ?
-
-        if blank_page and not self.isLocked():
-            # we now notice the MainTabPanel that the WidgetsPanel is empty and need to be removed
-            while current is not None:
-                if isinstance(current, MainTabPanel):
-                    current.onWidgetPanelRemove(self)
-                    return
-                current = current.getParent()
-            log.error("no MainTabPanel found !")
-
-    def getWidgetCoords(self, wid):
-        return self.flextable.getIndex(wid)
-
-    def getLiberviaRowWidgets(self, row):
-        """ Return all the LiberviaWidget in the row """
-        return [wid for wid in self.getRowWidgets(row) if isinstance(wid, LiberviaWidget)]
-
-    def getRowWidgets(self, row):
-        """ Return all the widgets in the row """
-        widgets = []
-        cols = self.flextable.getCellCount(row)
-        for col in xrange(cols):
-            widgets.append(self.flextable.getWidget(row, col))
-        return widgets
-
-    def getLiberviaWidgetsCount(self):
-        """ Get count of contained widgets """
-        return len([wid for wid in self.flextable if isinstance(wid, LiberviaWidget)])
-
-    def getIndex(self, wid):
-        return self.flextable.getIndex(wid)
-
-    def getColSpan(self, row, col):
-        cellFormatter = self.flextable.getFlexCellFormatter()
-        return cellFormatter.getColSpan(row, col)
-
-    def setColSpan(self, row, col, value):
-        cellFormatter = self.flextable.getFlexCellFormatter()
-        return cellFormatter.setColSpan(row, col, value)
-
-    def getRowSpan(self, row, col):
-        cellFormatter = self.flextable.getFlexCellFormatter()
-        return cellFormatter.getRowSpan(row, col)
-
-    def setRowSpan(self, row, col, value):
-        cellFormatter = self.flextable.getFlexCellFormatter()
-        return cellFormatter.setRowSpan(row, col, value)
-
-
-class DropTab(Label, DropWidget):
-
-    def __init__(self, tab_panel, text):
-        Label.__init__(self, text)
-        DropWidget.__init__(self, tab_panel)
-        self.tab_panel = tab_panel
-        self.setStyleName('dropCell')
-        self.setWordWrap(False)
-        DOM.setStyleAttribute(self.getElement(), "min-width", "30px")
-
-    def _getIndex(self):
-        """ get current index of the DropTab """
-        # XXX: awful hack, but seems the only way to get index
-        return self.tab_panel.tabBar.panel.getWidgetIndex(self.getParent().getParent()) - 1
-
-    def onDragEnter(self, event):
-        #if self == LiberviaDragWidget.current:
-        #    return
-        self.addStyleName('dragover')
-        DOM.eventPreventDefault(event)
-
-    def onDragLeave(self, event):
-        self.removeStyleName('dragover')
-
-    def onDragOver(self, event):
-        DOM.eventPreventDefault(event)
-
-    def onDrop(self, event):
-        DOM.eventPreventDefault(event)
-        self.removeStyleName('dragover')
-        if self._getIndex() == self.tab_panel.tabBar.getSelectedTab():
-            # the widget come from the DragTab, so nothing to do, we let it there
-            return
-
-        # FIXME: quite the same stuff as in DropCell, need some factorisation
-        dt = event.dataTransfer
-        # 'text', 'text/plain', and 'Text' are equivalent.
-        try:
-            item, item_type = dt.getData("text/plain").split('\n')  # Workaround for webkit, only text/plain seems to be managed
-            if item_type and item_type[-1] == '\0':  # Workaround for what looks like a pyjamas bug: the \0 should not be there, and
-                item_type = item_type[:-1]           # .strip('\0') and .replace('\0','') don't work. TODO: check this and fill a bug report
-            # item_type = dt.getData("type")
-            log.debug("message: %s" % item)
-            log.debug("type: %s" % item_type)
-        except:
-            log.debug("no message found")
-            item = '&nbsp;'
-            item_type = None
-        if item_type == "WIDGET":
-            if not LiberviaDragWidget.current:
-                log.error("No widget registered in LiberviaDragWidget !")
-                return
-            _new_panel = LiberviaDragWidget.current
-            _new_panel.getWidgetsPanel().removeWidget(_new_panel)
-        elif item_type in DropCell.drop_keys:
-            _new_panel = DropCell.drop_keys[item_type](self.tab_panel.host, item)
-        else:
-            log.warning("unmanaged item type")
-            return
-
-        widgets_panel = self.tab_panel.getWidget(self._getIndex())
-        widgets_panel.addWidget(_new_panel)
-
-
-class MainTabPanel(TabPanel):
-
-    def __init__(self, host):
-        TabPanel.__init__(self)
-        self.host = host
-        self.tabBar.setVisible(False)
-        self.setStyleName('liberviaTabPanel')
-        self.addStyleName('mainTabPanel')
-        Window.addWindowResizeListener(self)
-
-    def getCurrentPanel(self):
-        """ Get the panel of the currently selected tab """
-        return self.deck.visibleWidget
-
-    def onWindowResized(self, width, height):
-        tab_panel_elt = self.getElement()
-        _elts = doc().getElementsByClassName('gwt-TabBar')
-        if not _elts.length:
-            log.error("no TabBar found, it should exist !")
-            tab_bar_h = 0
-        else:
-            tab_bar_h = _elts.item(0).offsetHeight
-        ideal_height = height - DOM.getAbsoluteTop(tab_panel_elt) - tab_bar_h - 5
-        ideal_width = width - DOM.getAbsoluteLeft(tab_panel_elt) - 5
-        self.setWidth("%s%s" % (ideal_width, "px"))
-        self.setHeight("%s%s" % (ideal_height, "px"))
-
-    def add(self, widget, text=''):
-        tab = DropTab(self, text)
-        TabPanel.add(self, widget, tab, False)
-        if self.getWidgetCount() > 1:
-            self.tabBar.setVisible(True)
-            self.host.resize()
-
-    def onWidgetPanelRemove(self, panel):
-        """ Called when a child WidgetsPanel is empty and need to be removed """
-        self.remove(panel)
-        widgets_count = self.getWidgetCount()
-        if widgets_count == 1:
-            self.tabBar.setVisible(False)
-            self.host.resize()
-            self.selectTab(0)
-        else:
-            self.selectTab(widgets_count - 1)
--- a/browser_side/card_game.py	Fri May 16 11:51:10 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,386 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Libervia: a Salut à Toi frontend
-# Copyright (C) 2011, 2012, 2013, 2014 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/>.
-
-import pyjd  # this is dummy in pyjs
-from sat.core.log import getLogger
-log = getLogger(__name__)
-from pyjamas.ui.AbsolutePanel import AbsolutePanel
-from pyjamas.ui.DockPanel import DockPanel
-from pyjamas.ui.SimplePanel import SimplePanel
-from pyjamas.ui.Image import Image
-from pyjamas.ui.Label import Label
-from pyjamas.ui.ClickListener import ClickHandler
-from pyjamas.ui.MouseListener import MouseHandler
-from pyjamas.ui import HasAlignment
-from pyjamas import Window
-from pyjamas import DOM
-
-from dialog import ConfirmDialog, GenericDialog
-from xmlui import XMLUI
-from sat_frontends.tools.games import TarotCard
-from sat.core.i18n import _
-
-
-CARD_WIDTH = 74
-CARD_HEIGHT = 136
-CARD_DELTA_Y = 30
-MIN_WIDTH = 950  # Minimum size of the panel
-MIN_HEIGHT = 500
-
-
-class CardWidget(TarotCard, Image, MouseHandler):
-    """This class is used to represent a card, graphically and logically"""
-
-    def __init__(self, parent, file_):
-        """@param file: path of the PNG file"""
-        self._parent = parent
-        Image.__init__(self, file_)
-        root_name = file_[file_.rfind("/") + 1:-4]
-        suit, value = root_name.split('_')
-        TarotCard.__init__(self, (suit, value))
-        MouseHandler.__init__(self)
-        self.addMouseListener(self)
-
-    def onMouseEnter(self, sender):
-        if self._parent.state == "ecart" or self._parent.state == "play":
-            DOM.setStyleAttribute(self.getElement(), "top", "0px")
-
-    def onMouseLeave(self, sender):
-        if not self in self._parent.hand:
-            return
-        if not self in list(self._parent.selected):  # FIXME: Workaround pyjs bug, must report it
-            DOM.setStyleAttribute(self.getElement(), "top", "%dpx" % CARD_DELTA_Y)
-
-    def onMouseUp(self, sender, x, y):
-        if self._parent.state == "ecart":
-            if self not in list(self._parent.selected):
-                self._parent.addToSelection(self)
-            else:
-                self._parent.removeFromSelection(self)
-        elif self._parent.state == "play":
-            self._parent.playCard(self)
-
-
-class CardPanel(DockPanel, ClickHandler):
-
-    def __init__(self, parent, referee, player_nick, players):
-        DockPanel.__init__(self)
-        ClickHandler.__init__(self)
-        self._parent = parent
-        self._autoplay = None  # XXX: use 0 to activate fake play, None else
-        self.referee = referee
-        self.players = players
-        self.player_nick = player_nick
-        self.bottom_nick = self.player_nick
-        idx = self.players.index(self.player_nick)
-        idx = (idx + 1) % len(self.players)
-        self.right_nick = self.players[idx]
-        idx = (idx + 1) % len(self.players)
-        self.top_nick = self.players[idx]
-        idx = (idx + 1) % len(self.players)
-        self.left_nick = self.players[idx]
-        self.bottom_nick = player_nick
-        self.selected = set()  # Card choosed by the player (e.g. during ecart)
-        self.hand_size = 13  # number of cards in a hand
-        self.hand = []
-        self.to_show = []
-        self.state = None
-        self.setSize("%dpx" % MIN_WIDTH, "%dpx" % MIN_HEIGHT)
-        self.setStyleName("cardPanel")
-
-        # Now we set up the layout
-        _label = Label(self.top_nick)
-        _label.setStyleName('cardGamePlayerNick')
-        self.add(_label, DockPanel.NORTH)
-        self.setCellWidth(_label, '100%')
-        self.setCellHorizontalAlignment(_label, HasAlignment.ALIGN_CENTER)
-
-        self.hand_panel = AbsolutePanel()
-        self.add(self.hand_panel, DockPanel.SOUTH)
-        self.setCellWidth(self.hand_panel, '100%')
-        self.setCellHorizontalAlignment(self.hand_panel, HasAlignment.ALIGN_CENTER)
-
-        _label = Label(self.left_nick)
-        _label.setStyleName('cardGamePlayerNick')
-        self.add(_label, DockPanel.WEST)
-        self.setCellHeight(_label, '100%')
-        self.setCellVerticalAlignment(_label, HasAlignment.ALIGN_MIDDLE)
-
-        _label = Label(self.right_nick)
-        _label.setStyleName('cardGamePlayerNick')
-        self.add(_label, DockPanel.EAST)
-        self.setCellHeight(_label, '100%')
-        self.setCellHorizontalAlignment(_label, HasAlignment.ALIGN_RIGHT)
-        self.setCellVerticalAlignment(_label, HasAlignment.ALIGN_MIDDLE)
-
-        self.center_panel = DockPanel()
-        self.inner_left = SimplePanel()
-        self.inner_left.setSize("%dpx" % CARD_WIDTH, "%dpx" % CARD_HEIGHT)
-        self.center_panel.add(self.inner_left, DockPanel.WEST)
-        self.center_panel.setCellHeight(self.inner_left, '100%')
-        self.center_panel.setCellHorizontalAlignment(self.inner_left, HasAlignment.ALIGN_RIGHT)
-        self.center_panel.setCellVerticalAlignment(self.inner_left, HasAlignment.ALIGN_MIDDLE)
-
-        self.inner_right = SimplePanel()
-        self.inner_right.setSize("%dpx" % CARD_WIDTH, "%dpx" % CARD_HEIGHT)
-        self.center_panel.add(self.inner_right, DockPanel.EAST)
-        self.center_panel.setCellHeight(self.inner_right, '100%')
-        self.center_panel.setCellVerticalAlignment(self.inner_right, HasAlignment.ALIGN_MIDDLE)
-
-        self.inner_top = SimplePanel()
-        self.inner_top.setSize("%dpx" % CARD_WIDTH, "%dpx" % CARD_HEIGHT)
-        self.center_panel.add(self.inner_top, DockPanel.NORTH)
-        self.center_panel.setCellHorizontalAlignment(self.inner_top, HasAlignment.ALIGN_CENTER)
-        self.center_panel.setCellVerticalAlignment(self.inner_top, HasAlignment.ALIGN_BOTTOM)
-
-        self.inner_bottom = SimplePanel()
-        self.inner_bottom.setSize("%dpx" % CARD_WIDTH, "%dpx" % CARD_HEIGHT)
-        self.center_panel.add(self.inner_bottom, DockPanel.SOUTH)
-        self.center_panel.setCellHorizontalAlignment(self.inner_bottom, HasAlignment.ALIGN_CENTER)
-        self.center_panel.setCellVerticalAlignment(self.inner_bottom, HasAlignment.ALIGN_TOP)
-
-        self.inner_center = SimplePanel()
-        self.center_panel.add(self.inner_center, DockPanel.CENTER)
-        self.center_panel.setCellHorizontalAlignment(self.inner_center, HasAlignment.ALIGN_CENTER)
-        self.center_panel.setCellVerticalAlignment(self.inner_center, HasAlignment.ALIGN_MIDDLE)
-
-        self.add(self.center_panel, DockPanel.CENTER)
-        self.setCellWidth(self.center_panel, '100%')
-        self.setCellHeight(self.center_panel, '100%')
-        self.setCellVerticalAlignment(self.center_panel, HasAlignment.ALIGN_MIDDLE)
-        self.setCellHorizontalAlignment(self.center_panel, HasAlignment.ALIGN_CENTER)
-
-        self.loadCards()
-        self.mouse_over_card = None  # contain the card to highlight
-        self.visible_size = CARD_WIDTH / 2  # number of pixels visible for cards
-        self.addClickListener(self)
-
-    def loadCards(self):
-        """Load all the cards in memory"""
-        def _getTarotCardsPathsCb(paths):
-            log.debug("_getTarotCardsPathsCb")
-            for file_ in paths:
-                log.debug("path:", file_)
-                card = CardWidget(self, file_)
-                log.debug("card:", card)
-                self.cards[(card.suit, card.value)] = card
-                self.deck.append(card)
-            self._parent.host.bridge.call('tarotGameReady', None, self.player_nick, self.referee)
-        self.cards = {}
-        self.deck = []
-        self.cards["atout"] = {}  # As Tarot is a french game, it's more handy & logical to keep french names
-        self.cards["pique"] = {}  # spade
-        self.cards["coeur"] = {}  # heart
-        self.cards["carreau"] = {}  # diamond
-        self.cards["trefle"] = {}  # club
-        self._parent.host.bridge.call('getTarotCardsPaths', _getTarotCardsPathsCb)
-
-    def onClick(self, sender):
-        if self.state == "chien":
-            self.to_show = []
-            self.state = "wait"
-            self.updateToShow()
-        elif self.state == "wait_for_ecart":
-            self.state = "ecart"
-            self.hand.extend(self.to_show)
-            self.hand.sort()
-            self.to_show = []
-            self.updateToShow()
-            self.updateHand()
-
-    def tarotGameNew(self, hand):
-        """Start a new game, with given hand"""
-        if hand is []:  # reset the display after the scores have been showed
-            self.selected.clear()
-            del self.hand[:]
-            del self.to_show[:]
-            self.state = None
-            #empty hand
-            self.updateHand()
-            #nothing on the table
-            self.updateToShow()
-            for pos in ['top', 'left', 'bottom', 'right']:
-                getattr(self, "inner_%s" % pos).setWidget(None)
-            self._parent.host.bridge.call('tarotGameReady', None, self.player_nick, self.referee)
-            return
-        for suit, value in hand:
-            self.hand.append(self.cards[(suit, value)])
-        self.hand.sort()
-        self.state = "init"
-        self.updateHand()
-
-    def updateHand(self):
-        """Show the cards in the hand in the hand_panel (SOUTH panel)"""
-        self.hand_panel.clear()
-        self.hand_panel.setSize("%dpx" % (self.visible_size * (len(self.hand) + 1)), "%dpx" % (CARD_HEIGHT + CARD_DELTA_Y + 10))
-        x_pos = 0
-        y_pos = CARD_DELTA_Y
-        for card in self.hand:
-            self.hand_panel.add(card, x_pos, y_pos)
-            x_pos += self.visible_size
-
-    def updateToShow(self):
-        """Show cards in the center panel"""
-        if not self.to_show:
-            _widget = self.inner_center.getWidget()
-            if _widget:
-                self.inner_center.remove(_widget)
-            return
-        panel = AbsolutePanel()
-        panel.setSize("%dpx" % ((CARD_WIDTH + 5) * len(self.to_show) - 5), "%dpx" % (CARD_HEIGHT))
-        x_pos = 0
-        y_pos = 0
-        for card in self.to_show:
-            panel.add(card, x_pos, y_pos)
-            x_pos += CARD_WIDTH + 5
-        self.inner_center.setWidget(panel)
-
-    def _ecartConfirm(self, confirm):
-        if not confirm:
-            return
-        ecart = []
-        for card in self.selected:
-            ecart.append((card.suit, card.value))
-            self.hand.remove(card)
-        self.selected.clear()
-        self._parent.host.bridge.call('tarotGamePlayCards', None, self.player_nick, self.referee, ecart)
-        self.state = "wait"
-        self.updateHand()
-
-    def addToSelection(self, card):
-        self.selected.add(card)
-        if len(self.selected) == 6:
-            ConfirmDialog(self._ecartConfirm, "Put these cards into chien ?").show()
-
-    def tarotGameInvalidCards(self, phase, played_cards, invalid_cards):
-        """Invalid cards have been played
-        @param phase: phase of the game
-        @param played_cards: all the cards played
-        @param invalid_cards: cards which are invalid"""
-
-        if phase == "play":
-            self.state = "play"
-        elif phase == "ecart":
-            self.state = "ecart"
-        else:
-            log.error("INTERNAL ERROR: unmanaged game phase") # FIXME: raise an exception here
-
-        for suit, value in played_cards:
-            self.hand.append(self.cards[(suit, value)])
-
-        self.hand.sort()
-        self.updateHand()
-        if self._autoplay == None:  # No dialog if there is autoplay
-            Window.alert('Cards played are invalid !')
-        self.__fakePlay()
-
-    def removeFromSelection(self, card):
-        self.selected.remove(card)
-        if len(self.selected) == 6:
-            ConfirmDialog(self._ecartConfirm, "Put these cards into chien ?").show()
-
-    def tarotGameChooseContrat(self, xml_data):
-        """Called when the player has to select his contrat
-        @param xml_data: SàT xml representation of the form"""
-        body = XMLUI(self._parent.host, xml_data, flags=['NO_CANCEL'])
-        _dialog = GenericDialog(_('Please choose your contrat'), body, options=['NO_CLOSE'])
-        body.setCloseCb(_dialog.close)
-        _dialog.show()
-
-    def tarotGameShowCards(self, game_stage, cards, data):
-        """Display cards in the middle of the game (to show for e.g. chien ou poignée)"""
-        self.to_show = []
-        for suit, value in cards:
-            self.to_show.append(self.cards[(suit, value)])
-        self.updateToShow()
-        if game_stage == "chien" and data['attaquant'] == self.player_nick:
-            self.state = "wait_for_ecart"
-        else:
-            self.state = "chien"
-
-    def getPlayerLocation(self, nick):
-        """return player location (top,bottom,left or right)"""
-        for location in ['top', 'left', 'bottom', 'right']:
-            if getattr(self, '%s_nick' % location) == nick:
-                return location
-        log.error("This line should not be reached")
-
-    def tarotGameCardsPlayed(self, player, cards):
-        """A card has been played by player"""
-        if not len(cards):
-            log.warning("cards should not be empty")
-            return
-        if len(cards) > 1:
-            log.error("can't manage several cards played")
-        if self.to_show:
-            self.to_show = []
-            self.updateToShow()
-        suit, value = cards[0]
-        player_pos = self.getPlayerLocation(player)
-        player_panel = getattr(self, "inner_%s" % player_pos)
-
-        if player_panel.getWidget() != None:
-            #We have already cards on the table, we remove them
-            for pos in ['top', 'left', 'bottom', 'right']:
-                getattr(self, "inner_%s" % pos).setWidget(None)
-
-        card = self.cards[(suit, value)]
-        DOM.setElemAttribute(card.getElement(), "style", "")
-        player_panel.setWidget(card)
-
-    def tarotGameYourTurn(self):
-        """Called when we have to play :)"""
-        if self.state == "chien":
-            self.to_show = []
-            self.updateToShow()
-        self.state = "play"
-        self.__fakePlay()
-
-    def __fakePlay(self):
-        """Convenience method for stupid autoplay
-        /!\ don't forgot to comment any interactive dialog for invalid card"""
-        if self._autoplay == None:
-            return
-        if self._autoplay >= len(self.hand):
-            self._autoplay = 0
-        card = self.hand[self._autoplay]
-        self._parent.host.bridge.call('tarotGamePlayCards', None, self.player_nick, self.referee, [(card.suit, card.value)])
-        del self.hand[self._autoplay]
-        self.state = "wait"
-        self._autoplay += 1
-
-    def playCard(self, card):
-        self.hand.remove(card)
-        self._parent.host.bridge.call('tarotGamePlayCards', None, self.player_nick, self.referee, [(card.suit, card.value)])
-        self.state = "wait"
-        self.updateHand()
-
-    def tarotGameScore(self, xml_data, winners, loosers):
-        """Show score at the end of a round"""
-        if not winners and not loosers:
-            title = "Draw game"
-        else:
-            if self.player_nick in winners:
-                title = "You <b>win</b> !"
-            else:
-                title = "You <b>loose</b> :("
-        body = XMLUI(self._parent.host, xml_data, title=title, flags=['NO_CANCEL'])
-        _dialog = GenericDialog(title, body, options=['NO_CLOSE'])
-        body.setCloseCb(_dialog.close)
-        _dialog.show()
--- a/browser_side/contact.py	Fri May 16 11:51:10 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,414 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Libervia: a Salut à Toi frontend
-# Copyright (C) 2011, 2012, 2013, 2014 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/>.
-
-import pyjd  # this is dummy in pyjs
-from sat.core.log import getLogger
-log = getLogger(__name__)
-from pyjamas.ui.SimplePanel import SimplePanel
-from pyjamas.ui.ScrollPanel import ScrollPanel
-from pyjamas.ui.VerticalPanel import VerticalPanel
-from pyjamas.ui.ClickListener import ClickHandler
-from pyjamas.ui.Label import Label
-from pyjamas.ui.HTML import HTML
-from jid import JID
-from pyjamas import Window
-from pyjamas import DOM
-
-from browser_side.base_panels import PopupMenuPanel
-from browser_side.base_widget import DragLabel
-from browser_side.panels import ChatPanel, MicroblogPanel, WebPanel, UniBoxPanel
-from browser_side.html_tools import html_sanitize
-from __pyjamas__ import doc
-
-
-def setPresenceStyle(element, presence, base_style="contact"):
-    """
-    Set the CSS style of a contact's element according to its presence.
-    @param item: the UI element of the contact
-    @param presence: a value in ("", "chat", "away", "dnd", "xa").
-    @param base_style: the base name of the style to apply
-    """
-    if not hasattr(element, 'presence_style'):
-        element.presence_style = None
-    style = '%s-%s' % (base_style, presence or 'connected')
-    if style == element.presence_style:
-        return
-    if element.presence_style is not None:
-        element.removeStyleName(element.presence_style)
-    element.addStyleName(style)
-    element.presence_style = style
-
-
-class GroupLabel(DragLabel, Label, ClickHandler):
-    def __init__(self, host, group):
-        self.group = group
-        self.host = host
-        Label.__init__(self, group) #, Element=DOM.createElement('div')
-        self.setStyleName('group')
-        DragLabel.__init__(self, group, "GROUP")
-        ClickHandler.__init__(self)
-        self.addClickListener(self)
-
-    def onClick(self, sender):
-        self.host.getOrCreateLiberviaWidget(MicroblogPanel, self.group)
-
-
-class ContactLabel(DragLabel, HTML, ClickHandler):
-    def __init__(self, host, jid, name=None, handleClick=True):
-        HTML.__init__(self)
-        self.host = host
-        self.name = name or jid
-        self.waiting = False
-        self.jid = jid
-        self._fill()
-        self.setStyleName('contact')
-        DragLabel.__init__(self, jid, "CONTACT")
-        if handleClick:
-            ClickHandler.__init__(self)
-            self.addClickListener(self)
-
-    def _fill(self):
-        if self.waiting:
-            _wait_html = "<b>(*)</b>&nbsp;"
-        self.setHTML("%(wait)s%(name)s" % {'wait': _wait_html,
-                                           'name': html_sanitize(self.name)})
-
-    def setMessageWaiting(self, waiting):
-        """Show a visual indicator if message are waiting
-        @param waiting: True if message are waiting"""
-        self.waiting = waiting
-        self._fill()
-
-    def onClick(self, sender):
-        self.host.getOrCreateLiberviaWidget(ChatPanel, self.jid)
-
-
-class GroupList(VerticalPanel):
-
-    def __init__(self, parent):
-        VerticalPanel.__init__(self)
-        self.setStyleName('groupList')
-        self._parent = parent
-
-    def add(self, group):
-        _item = GroupLabel(self._parent.host, group)
-        _item.addMouseListener(self._parent)
-        DOM.setStyleAttribute(_item.getElement(), "cursor", "pointer")
-        index = 0
-        for group_ in [group.group for group in self.getChildren()]:
-            if group_ > group:
-                break
-            index += 1
-        VerticalPanel.insert(self, _item, index)
-
-    def remove(self, group):
-        for wid in self:
-            if isinstance(wid, GroupLabel) and wid.group == group:
-                VerticalPanel.remove(self, wid)
-
-
-class GenericContactList(VerticalPanel):
-    """Class that can be used to represent a contact list, but not necessarily
-    the one that is displayed on the left side. Special features like popup menu
-    panel or changing the contact states must be done in a sub-class."""
-
-    def __init__(self, host, handleClick=False):
-        VerticalPanel.__init__(self)
-        self.host = host
-        self.contacts = []
-        self.handleClick = handleClick
-
-    def add(self, jid, name=None, item_cb=None):
-        if jid in self.contacts:
-            return
-        index = 0
-        for contact_ in self.contacts:
-            if contact_ > jid:
-                break
-            index += 1
-        self.contacts.insert(index, jid)
-        _item = ContactLabel(self.host, jid, name, handleClick=self.handleClick)
-        DOM.setStyleAttribute(_item.getElement(), "cursor", "pointer")
-        VerticalPanel.insert(self, _item, index)
-        if item_cb is not None:
-            item_cb(_item)
-
-    def remove(self, jid):
-        wid = self.getContactLabel(jid)
-        if not wid:
-            return
-        VerticalPanel.remove(self, wid)
-        self.contacts.remove(jid)
-
-    def isContactPresent(self, contact_jid):
-        """Return True if a contact is present in the panel"""
-        return contact_jid in self.contacts
-
-    def getContacts(self):
-        return self.contacts
-
-    def getContactLabel(self, contact_jid):
-        """get contactList widget of a contact
-        @return: ContactLabel item if present, else None"""
-        for wid in self:
-            if isinstance(wid, ContactLabel) and wid.jid == contact_jid:
-                return wid
-        return None
-
-
-class ContactList(GenericContactList):
-    """The contact list that is displayed on the left side."""
-
-    def __init__(self, host):
-        GenericContactList.__init__(self, host, handleClick=True)
-        self.menu_entries = {"blog": {"title": "Public blog..."}}
-        self.context_menu = PopupMenuPanel(entries=self.menu_entries,
-                                           hide=self.contextMenuHide,
-                                           callback=self.contextMenuCallback,
-                                           vertical=False, style={"selected": "menu-selected"})
-
-    def contextMenuHide(self, sender, key):
-        """Return True if the item for that sender should be hidden."""
-        # TODO: enable the blogs of users that are on another server
-        return JID(sender.jid).domain != self.host._defaultDomain
-
-    def contextMenuCallback(self, sender, key):
-        if key == "blog":
-            # TODO: use the bare when all blogs can be retrieved
-            node = JID(sender.jid).node
-            web_panel = WebPanel(self.host, "/blog/%s" % node)
-            self.host.addTab("%s's blog" % node, web_panel)
-        else:
-            sender.onClick(sender)
-
-    def add(self, jid, name=None):
-        def item_cb(item):
-            self.context_menu.registerRightClickSender(item)
-        GenericContactList.add(self, jid, name, item_cb)
-
-    def setState(self, jid, type_, state):
-        """Change the appearance of the contact, according to the state
-        @param jid: jid which need to change state
-        @param type_: one of availability, messageWaiting
-        @param state:
-            - for messageWaiting type:
-                True if message are waiting
-            - for availability type:
-                'unavailable' if not connected, else presence like RFC6121 #4.7.2.1"""
-        _item = self.getContactLabel(jid)
-        if _item:
-            if type_ == 'availability':
-                setPresenceStyle(_item, state)
-            elif type_ == 'messageWaiting':
-                _item.setMessageWaiting(state)
-
-
-class ContactTitleLabel(DragLabel, Label, ClickHandler):
-    def __init__(self, host, text):
-        Label.__init__(self, text) #, Element=DOM.createElement('div')
-        self.host = host
-        self.setStyleName('contactTitle')
-        DragLabel.__init__(self, text, "CONTACT_TITLE")
-        ClickHandler.__init__(self)
-        self.addClickListener(self)
-
-    def onClick(self, sender):
-        self.host.getOrCreateLiberviaWidget(MicroblogPanel, None)
-
-
-class ContactPanel(SimplePanel):
-    """Manage the contacts and groups"""
-
-    def __init__(self, host):
-        SimplePanel.__init__(self)
-
-        self.scroll_panel = ScrollPanel()
-
-        self.host = host
-        self.groups = {}
-        self.connected = {}  # jid connected as key and their status
-
-        self.vPanel = VerticalPanel()
-        _title = ContactTitleLabel(host, 'Contacts')
-        DOM.setStyleAttribute(_title.getElement(), "cursor", "pointer")
-
-        self._contact_list = ContactList(host)
-        self._contact_list.setStyleName('contactList')
-        self._groupList = GroupList(self)
-        self._groupList.setStyleName('groupList')
-
-        self.vPanel.add(_title)
-        self.vPanel.add(self._groupList)
-        self.vPanel.add(self._contact_list)
-        self.scroll_panel.add(self.vPanel)
-        self.add(self.scroll_panel)
-        self.setStyleName('contactBox')
-        Window.addWindowResizeListener(self)
-
-    def onWindowResized(self, width, height):
-        contact_panel_elt = self.getElement()
-        classname = 'widgetsPanel' if isinstance(self.getParent().getParent(), UniBoxPanel) else'gwt-TabBar'
-        _elts = doc().getElementsByClassName(classname)
-        if not _elts.length:
-            log.error("no element of class %s found, it should exist !" % classname)
-            tab_bar_h = height
-        else:
-            tab_bar_h = DOM.getAbsoluteTop(_elts.item(0)) or height  # getAbsoluteTop can be 0 if tabBar is hidden
-
-        ideal_height = tab_bar_h - DOM.getAbsoluteTop(contact_panel_elt) - 5
-        self.scroll_panel.setHeight("%s%s" % (ideal_height, "px"))
-
-    def updateContact(self, jid, attributes, groups):
-        """Add a contact to the panel if it doesn't exist, update it else
-        @param jid: jid userhost as unicode
-        @attributes: cf SàT Bridge API's newContact
-        @param groups: list of groups"""
-        _current_groups = self.getContactGroups(jid)
-        _new_groups = set(groups)
-        _key = "@%s: "
-
-        for group in _current_groups.difference(_new_groups):
-            # We remove the contact from the groups where he isn't anymore
-            self.groups[group].remove(jid)
-            if not self.groups[group]:
-                # The group is now empty, we must remove it
-                del self.groups[group]
-                self._groupList.remove(group)
-                if self.host.uni_box:
-                    self.host.uni_box.removeKey(_key % group)
-
-        for group in _new_groups.difference(_current_groups):
-            # We add the contact to the groups he joined
-            if not group in self.groups.keys():
-                self.groups[group] = set()
-                self._groupList.add(group)
-                if self.host.uni_box:
-                    self.host.uni_box.addKey(_key % group)
-            self.groups[group].add(jid)
-
-        # We add the contact to contact list, it will check if contact already exists
-        self._contact_list.add(jid)
-
-    def removeContact(self, jid):
-        """Remove contacts from groups where he is and contact list"""
-        self.updateContact(jid, {}, [])  # we remove contact from every group
-        self._contact_list.remove(jid)
-
-    def setConnected(self, jid, resource, availability, priority, statuses):
-        """Set connection status
-        @param jid: JID userhost as unicode
-        """
-        if availability == 'unavailable':
-            if jid in self.connected:
-                if resource in self.connected[jid]:
-                    del self.connected[jid][resource]
-                if not self.connected[jid]:
-                    del self.connected[jid]
-        else:
-            if not jid in self.connected:
-                self.connected[jid] = {}
-            self.connected[jid][resource] = (availability, priority, statuses)
-
-        # check if the contact is connected with another resource, use the one with highest priority
-        if jid in self.connected:
-            max_resource = max_priority = None
-            for tmp_resource in self.connected[jid]:
-                if max_priority is None or self.connected[jid][tmp_resource][1] >= max_priority:
-                    max_resource = tmp_resource
-                    max_priority = self.connected[jid][tmp_resource][1]
-            if availability == "unavailable":  # do not check the priority here, because 'unavailable' has a dummy one
-                priority = max_priority
-                availability = self.connected[jid][max_resource][0]
-        if jid not in self.connected or priority >= max_priority:
-            # case 1: jid not in self.connected means all resources are disconnected, update with 'unavailable'
-            # case 2: update (or confirm) with the values of the resource which takes precedence
-            self._contact_list.setState(jid, "availability", availability)
-
-        # update the connected contacts chooser live
-        if hasattr(self.host, "room_contacts_chooser") and self.host.room_contacts_chooser is not None:
-                self.host.room_contacts_chooser.resetContacts()
-
-    def setContactMessageWaiting(self, jid, waiting):
-        """Show an visual indicator that contact has send a message
-        @param jid: jid of the contact
-        @param waiting: True if message are waiting"""
-        self._contact_list.setState(jid, "messageWaiting", waiting)
-
-    def getConnected(self, filter_muc=False):
-        """return a list of all jid (bare jid) connected
-        @param filter_muc: if True, remove the groups from the list
-        """
-        contacts = self.connected.keys()
-        contacts.sort()
-        return contacts if not filter_muc else list(set(contacts).intersection(set(self.getContacts())))
-
-    def getContactGroups(self, contact_jid):
-        """Get groups where contact is
-       @param group: string of single group, or list of string
-       @param contact_jid: jid to test
-        """
-        result = set()
-        for group in self.groups:
-            if self.isContactInGroup(group, contact_jid):
-                result.add(group)
-        return result
-
-    def isContactInGroup(self, group, contact_jid):
-        """Test if the contact_jid is in the group
-        @param group: string of single group, or list of string
-        @param contact_jid: jid to test
-        @return: True if contact_jid is in on of the groups"""
-        if group in self.groups and contact_jid in self.groups[group]:
-            return True
-        return False
-
-    def isContactInRoster(self, contact_jid):
-        """Test if the contact is in our roster list"""
-        for _contact_label in self._contact_list:
-            if contact_jid == _contact_label.jid:
-                return True
-        return False
-
-    def getContacts(self):
-        return self._contact_list.getContacts()
-
-    def getGroups(self):
-        return self.groups.keys()
-
-    def onMouseMove(self, sender, x, y):
-        pass
-
-    def onMouseDown(self, sender, x, y):
-        pass
-
-    def onMouseUp(self, sender, x, y):
-        pass
-
-    def onMouseEnter(self, sender):
-        if isinstance(sender, GroupLabel):
-            for contact in self._contact_list:
-                if contact.jid in self.groups[sender.group]:
-                    contact.addStyleName("selected")
-
-    def onMouseLeave(self, sender):
-        if isinstance(sender, GroupLabel):
-            for contact in self._contact_list:
-                if contact.jid in self.groups[sender.group]:
-                    contact.removeStyleName("selected")
-
--- a/browser_side/contact_group.py	Fri May 16 11:51:10 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,236 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Libervia: a Salut à Toi frontend
-# Copyright (C) 2013, 2014 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
-from browser_side.dialog import ConfirmDialog, InfoDialog
-from list_manager import ListManager
-from browser_side import dialog
-from browser_side import contact
-
-
-class ContactGroupManager(ListManager):
-    """A manager for sub-panels to assign contacts to each group."""
-
-    def __init__(self, parent, keys_dict, contact_list, offsets, style):
-        ListManager.__init__(self, parent, keys_dict, contact_list, 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:
-                ListManager.removeContactKey(self, key)
-                self._parent.removeKeyFromAddGroupPanel(key)
-
-        _dialog = ConfirmDialog(confirm_cb, text="Do you really want to delete the group '%s'?" % key)
-        _dialog.show()
-
-    def removeFromRemainingList(self, contacts):
-        ListManager.removeFromRemainingList(self, contacts)
-        self._parent.updateContactList(contacts=contacts)
-
-    def addToRemainingList(self, contacts, ignore_key=None):
-        ListManager.addToRemainingList(self, contacts, ignore_key)
-        self._parent.updateContactList(contacts=contacts)
-
-
-class ContactGroupEditor(DockPanel):
-    """Panel for the contact groups manager."""
-
-    def __init__(self, host, parent=None, onCloseCallback=None):
-        DockPanel.__init__(self)
-        self.host = host
-
-        # eventually display in a popup
-        if parent is None:
-            parent = DialogBox(autoHide=False, centered=True)
-            parent.setHTML("Manage contact groups")
-        self._parent = parent
-        self._on_close_callback = onCloseCallback
-        self.all_contacts = self.host.contact_panel.getContacts()
-
-        groups_list = self.host.contact_panel.groups.keys()
-        groups_list.sort()
-
-        self.add_group_panel = self.getAddGroupPanel(groups_list)
-        south_panel = self.getCloseSaveButtons()
-        center_panel = self.getContactGroupManager(groups_list)
-        east_panel = self.getContactList()
-
-        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.setContacts(self.host.contact_panel.groups)
-        self.toggleContacts(showAll=True)
-
-        # Hide the contacts list from the main panel to not confuse the user
-        self.restore_contact_panel = False
-        if self.host.contact_panel.getVisible():
-            self.restore_contact_panel = True
-            self.host.panel._contactsSwitch()
-
-        parent.add(self)
-        parent.setVisible(True)
-        if isinstance(parent, DialogBox):
-            parent.center()
-
-    def getContactGroupManager(self, groups_list):
-        """Set the list manager for the groups"""
-        flex_table = FlexTable(len(groups_list), 2)
-        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"
-        }
-        self.groups = ContactGroupManager(flex_table, groups_list, self.all_contacts, style=style)
-        self.groups.createWidgets()  # widgets are automatically added to 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 getAddGroupPanel(self, groups_list):
-        """Add the 'Add group' panel to the FlexTable"""
-
-        def add_group_cb(text):
-            self.groups.addContactKey(text)
-            self.add_group_panel.textbox.setFocus(True)
-
-        add_group_panel = dialog.AddGroupPanel(groups_list, add_group_cb)
-        add_group_panel.addStyleName("addContactGroupPanel")
-        return add_group_panel
-
-    def getCloseSaveButtons(self):
-        """Add the buttons to close the dialog / 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 getContactList(self):
-        """Add the contact list to the DockPanel"""
-        self.toggle = Button("", self.toggleContacts)
-        self.toggle.addStyleName("toggleAssignedContacts")
-        self.contacts = contact.GenericContactList(self.host)
-        for contact_ in self.all_contacts:
-            self.contacts.add(contact_)
-        contact_panel = VerticalPanel()
-        contact_panel.add(self.toggle)
-        contact_panel.add(self.contacts)
-        return contact_panel
-
-    def toggleContacts(self, sender=None, showAll=None):
-        """Callback for the toggle button"""
-        if sender is None:
-            sender = self.toggle
-        sender.showAll = showAll if showAll is not None else not sender.showAll
-        if sender.showAll:
-            sender.setText("Hide assigned")
-        else:
-            sender.setText("Show assigned")
-        self.updateContactList(sender)
-
-    def updateContactList(self, sender=None, contacts=None):
-        """Update the contact list regarding the toggle button"""
-        if not hasattr(self, "toggle") or not hasattr(self.toggle, "showAll"):
-            return
-        sender = self.toggle
-        if contacts is not None:
-            if not isinstance(contacts, list):
-                contacts = [contacts]
-            for contact_ in contacts:
-                if contact_ not in self.all_contacts:
-                    contacts.remove(contact_)
-        else:
-            contacts = self.all_contacts
-        for contact_ in contacts:
-            if sender.showAll:
-                self.contacts.getContactLabel(contact_).setVisible(True)
-            else:
-                if contact_ in self.groups.remaining_list:
-                    self.contacts.getContactLabel(contact_).setVisible(True)
-                else:
-                    self.contacts.getContactLabel(contact_).setVisible(False)
-
-    def __close(self):
-        """Remove the widget from parent or close the popup."""
-        if isinstance(self._parent, DialogBox):
-            self._parent.hide()
-        self._parent.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 = 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"""
-        map_ = {}
-        for contact_ in self.all_contacts:
-            map_[contact_] = set()
-        contacts = self.groups.getContacts()
-        for group in contacts.keys():
-            for contact_ in contacts[group]:
-                try:
-                    map_[contact_].add(group)
-                except KeyError:
-                    InfoDialog("Invalid contact",
-                           "The contact '%s' is not your contact list but it has been assigned to the group '%s'." % (contact_, group) +
-                           "Your changes could not be saved: please check your assignments and save again.", Width="400px").center()
-                    return
-        for contact_ in map_.keys():
-            groups = map_[contact_]
-            current_groups = self.host.contact_panel.getContactGroups(contact_)
-            if groups != current_groups:
-                self.host.bridge.call('updateContact', None, contact_, '', list(groups))
-        self.__close()
--- a/browser_side/dialog.py	Fri May 16 11:51:10 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,546 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Libervia: a Salut à Toi frontend
-# Copyright (C) 2011, 2012, 2013, 2014 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 sat.core.log import getLogger
-log = getLogger(__name__)
-from pyjamas.ui.VerticalPanel import VerticalPanel
-from pyjamas.ui.Grid import Grid
-from pyjamas.ui.HorizontalPanel import HorizontalPanel
-from pyjamas.ui.PopupPanel import PopupPanel
-from pyjamas.ui.DialogBox import DialogBox
-from pyjamas.ui.ListBox import ListBox
-from pyjamas.ui.Button import Button
-from pyjamas.ui.TextBox import TextBox
-from pyjamas.ui.Label import Label
-from pyjamas.ui.HTML import HTML
-from pyjamas.ui.RadioButton import RadioButton
-from pyjamas.ui import HasAlignment
-from pyjamas.ui.KeyboardListener import KEY_ESCAPE, KEY_ENTER
-from pyjamas.ui.MouseListener import MouseWheelHandler
-from pyjamas import Window
-
-from base_panels import ToggleStackPanel
-
-from sat_frontends.tools.misc import DEFAULT_MUC
-
-# List here the patterns that are not allowed in contact group names
-FORBIDDEN_PATTERNS_IN_GROUP = ()
-
-
-class RoomChooser(Grid):
-    """Select a room from the rooms you already joined, or create a new one"""
-
-    GENERATE_MUC = "<use random name>"
-
-    def __init__(self, host, default_room=DEFAULT_MUC):
-        Grid.__init__(self, 2, 2, Width='100%')
-        self.host = host
-
-        self.new_radio = RadioButton("room", "Discussion room:")
-        self.new_radio.setChecked(True)
-        self.box = TextBox(Width='95%')
-        self.box.setText(self.GENERATE_MUC if default_room == "" else default_room)
-        self.exist_radio = RadioButton("room", "Already joined:")
-        self.rooms_list = ListBox(Width='95%')
-
-        self.add(self.new_radio, 0, 0)
-        self.add(self.box, 0, 1)
-        self.add(self.exist_radio, 1, 0)
-        self.add(self.rooms_list, 1, 1)
-
-        self.box.addFocusListener(self)
-        self.rooms_list.addFocusListener(self)
-
-        self.exist_radio.setVisible(False)
-        self.rooms_list.setVisible(False)
-        self.setRooms()
-
-    def onFocus(self, sender):
-        if sender == self.rooms_list:
-            self.exist_radio.setChecked(True)
-        elif sender == self.box:
-            if self.box.getText() == self.GENERATE_MUC:
-                self.box.setText("")
-            self.new_radio.setChecked(True)
-
-    def onLostFocus(self, sender):
-        if sender == self.box:
-            if self.box.getText() == "":
-                self.box.setText(self.GENERATE_MUC)
-
-    def setRooms(self):
-        for room in self.host.room_list:
-            self.rooms_list.addItem(room.bare)
-        if len(self.host.room_list) > 0:
-            self.exist_radio.setVisible(True)
-            self.rooms_list.setVisible(True)
-            self.exist_radio.setChecked(True)
-
-    def getRoom(self):
-        if self.exist_radio.getChecked():
-            values = self.rooms_list.getSelectedValues()
-            return "" if values == [] else values[0]
-        value = self.box.getText()
-        return "" if value == self.GENERATE_MUC else value
-
-
-class ContactsChooser(VerticalPanel):
-    """Select one or several connected contacts"""
-
-    def __init__(self, host, nb_contact=None, ok_button=None):
-        """
-        @param host: SatWebFrontend instance
-        @param nb_contact: number of contacts that have to be selected, None for no limit
-        If a tuple is given instead of an integer, nb_contact[0] is the minimal and
-        nb_contact[1] is the maximal number of contacts to be chosen.
-        """
-        self.host = host
-        if isinstance(nb_contact, tuple):
-            if len(nb_contact) == 0:
-                nb_contact = None
-            elif len(nb_contact) == 1:
-                nb_contact = (nb_contact[0], nb_contact[0])
-        elif nb_contact is not None:
-            nb_contact = (nb_contact, nb_contact)
-        if nb_contact is None:
-            log.warning("Need to select as many contacts as you want")
-        else:
-            log.warning("Need to select between %d and %d contacts" % nb_contact)
-        self.nb_contact = nb_contact
-        self.ok_button = ok_button
-        VerticalPanel.__init__(self, Width='100%')
-        self.contacts_list = ListBox()
-        self.contacts_list.setMultipleSelect(True)
-        self.contacts_list.setWidth("95%")
-        self.contacts_list.addStyleName('contactsChooser')
-        self.contacts_list.addChangeListener(self.onChange)
-        self.add(self.contacts_list)
-        self.setContacts()
-        self.onChange()
-
-    def onChange(self, sender=None):
-        if self.ok_button is None:
-            return
-        if self.nb_contact:
-            selected = len(self.contacts_list.getSelectedValues(True))
-            if  selected >= self.nb_contact[0] and selected <= self.nb_contact[1]:
-                self.ok_button.setEnabled(True)
-            else:
-                self.ok_button.setEnabled(False)
-
-    def setContacts(self, selected=[]):
-        """Fill the list with the connected contacts
-        @param select: list of the contacts to select by default
-        """
-        self.contacts_list.clear()
-        contacts = self.host.contact_panel.getConnected(filter_muc=True)
-        self.contacts_list.setVisibleItemCount(10 if len(contacts) > 5 else 5)
-        self.contacts_list.addItem("")
-        for contact in contacts:
-            if contact not in [room.bare for room in self.host.room_list]:
-                self.contacts_list.addItem(contact)
-        self.contacts_list.setItemTextSelection(selected)
-
-    def getContacts(self):
-        return self.contacts_list.getSelectedValues(True)
-
-
-class RoomAndContactsChooser(DialogBox):
-    """Select a room and some users to invite in"""
-
-    def __init__(self, host, callback, nb_contact=None, ok_button="OK", title="Discussion groups",
-                 title_room="Join room", title_invite="Invite contacts", visible=(True, True)):
-        DialogBox.__init__(self, centered=True)
-        self.host = host
-        self.callback = callback
-        self.title_room = title_room
-        self.title_invite = title_invite
-
-        button_panel = HorizontalPanel()
-        button_panel.addStyleName("marginAuto")
-        ok_button = Button("OK", self.onOK)
-        button_panel.add(ok_button)
-        button_panel.add(Button("Cancel", self.onCancel))
-
-        self.room_panel = RoomChooser(host, "" if visible == (False, True) else DEFAULT_MUC)
-        self.contact_panel = ContactsChooser(host, nb_contact, ok_button)
-
-        self.stack_panel = ToggleStackPanel(Width="100%")
-        self.stack_panel.add(self.room_panel, visible=visible[0])
-        self.stack_panel.add(self.contact_panel, visible=visible[1])
-        self.stack_panel.addStackChangeListener(self)
-        self.onStackChanged(self.stack_panel, 0, visible[0])
-        self.onStackChanged(self.stack_panel, 1, visible[1])
-
-        main_panel = VerticalPanel()
-        main_panel.setStyleName("room-contact-chooser")
-        main_panel.add(self.stack_panel)
-        main_panel.add(button_panel)
-
-        self.setWidget(main_panel)
-        self.setHTML(title)
-        self.show()
-
-        # needed to update the contacts list when someone logged in/out
-        self.host.room_contacts_chooser = self
-
-    def getRoom(self, asSuffix=False):
-        room = self.room_panel.getRoom()
-        if asSuffix:
-            return room if room == "" else ": %s" % room
-        else:
-            return room
-
-    def getContacts(self, asSuffix=False):
-        contacts = self.contact_panel.getContacts()
-        if asSuffix:
-            return "" if contacts == [] else ": %s" % ", ".join(contacts)
-        else:
-            return contacts
-
-    def onStackChanged(self, sender, index, visible=None):
-        if visible is None:
-            visible = sender.getWidget(index).getVisible()
-        if index == 0:
-            sender.setStackText(0, self.title_room + ("" if visible else self.getRoom(True)))
-        elif index == 1:
-            sender.setStackText(1, self.title_invite + ("" if visible else self.getContacts(True)))
-
-    def resetContacts(self):
-        """Called when someone log in/out to update the list"""
-        self.contact_panel.setContacts(self.getContacts())
-
-    def onOK(self, sender):
-        room_jid = self.getRoom()
-        if room_jid != "" and "@" not in room_jid:
-            Window.alert('You must enter a room jid in the form room@chat.%s' % self.host._defaultDomain)
-            return
-        self.hide()
-        self.callback(room_jid, self.getContacts())
-
-    def onCancel(self, sender):
-        self.hide()
-
-    def hide(self):
-        self.host.room_contacts_chooser = None
-        DialogBox.hide(self, autoClosed=True)
-
-
-class GenericConfirmDialog(DialogBox):
-
-    def __init__(self, widgets, callback, title='Confirmation', prompt=None, **kwargs):
-        """
-        Dialog to confirm an action
-        @param widgets: widgets to attach
-        @param callback: method to call when a button is clicked
-        @param title: title of the dialog
-        @param prompt: textbox from which to retrieve the string value to be passed to the callback when
-        OK button is pressed. If None, OK button will return "True". Cancel button always returns "False".
-        """
-        self.callback = callback
-        DialogBox.__init__(self, centered=True, **kwargs)
-
-        content = VerticalPanel()
-        content.setWidth('100%')
-        for wid in widgets:
-            content.add(wid)
-            if wid == prompt:
-                wid.setWidth('100%')
-        button_panel = HorizontalPanel()
-        button_panel.addStyleName("marginAuto")
-        self.confirm_button = Button("OK", self.onConfirm)
-        button_panel.add(self.confirm_button)
-        self.cancel_button = Button("Cancel", self.onCancel)
-        button_panel.add(self.cancel_button)
-        content.add(button_panel)
-        self.setHTML(title)
-        self.setWidget(content)
-        self.prompt = prompt
-
-    def onConfirm(self, sender):
-        self.hide()
-        self.callback(self.prompt.getText() if self.prompt else True)
-
-    def onCancel(self, sender):
-        self.hide()
-        self.callback(False)
-
-    def show(self):
-        DialogBox.show(self)
-        if self.prompt:
-            self.prompt.setFocus(True)
-
-
-class ConfirmDialog(GenericConfirmDialog):
-
-    def __init__(self, callback, text='Are you sure ?', title='Confirmation', **kwargs):
-        GenericConfirmDialog.__init__(self, [HTML(text)], callback, title, **kwargs)
-
-
-class GenericDialog(DialogBox):
-    """Dialog which just show a widget and a close button"""
-
-    def __init__(self, title, main_widget, callback=None, options=None, **kwargs):
-        """Simple notice dialog box
-        @param title: HTML put in the header
-        @param main_widget: widget put in the body
-        @param callback: method to call on closing
-        @param options: one or more of the following options:
-                        - NO_CLOSE: don't add a close button"""
-        DialogBox.__init__(self, centered=True, **kwargs)
-        self.callback = callback
-        if not options:
-            options = []
-        _body = VerticalPanel()
-        _body.setSize('100%','100%')
-        _body.setSpacing(4)
-        _body.add(main_widget)
-        _body.setCellWidth(main_widget, '100%')
-        _body.setCellHeight(main_widget, '100%')
-        if not 'NO_CLOSE' in options:
-            _close_button = Button("Close", self.onClose)
-            _body.add(_close_button)
-            _body.setCellHorizontalAlignment(_close_button, HasAlignment.ALIGN_CENTER)
-        self.setHTML(title)
-        self.setWidget(_body)
-        self.panel.setSize('100%', '100%') #Need this hack to have correct size in Gecko & Webkit
-
-    def close(self):
-        """Same effect as clicking the close button"""
-        self.onClose(None)
-
-    def onClose(self, sender):
-        self.hide()
-        if self.callback:
-            self.callback()
-
-
-class InfoDialog(GenericDialog):
-
-    def __init__(self, title, body, callback=None, options=None, **kwargs):
-        GenericDialog.__init__(self, title, HTML(body), callback, options, **kwargs)
-
-
-class PromptDialog(GenericConfirmDialog):
-
-    def __init__(self, callback, text='', title='User input', **kwargs):
-        prompt = TextBox()
-        prompt.setText(text)
-        GenericConfirmDialog.__init__(self, [prompt], callback, title, prompt, **kwargs)
-
-
-class PopupPanelWrapper(PopupPanel):
-    """This wrapper catch Escape event to avoid request cancellation by Firefox"""
-
-    def onEventPreview(self, event):
-        if event.type in ["keydown", "keypress", "keyup"] and event.keyCode == KEY_ESCAPE:
-            #needed to prevent request cancellation in Firefox
-            event.preventDefault()
-        return PopupPanel.onEventPreview(self, event)
-
-
-class ExtTextBox(TextBox):
-    """Extended TextBox"""
-
-    def __init__(self, *args, **kwargs):
-        if 'enter_cb' in kwargs:
-            self.enter_cb = kwargs['enter_cb']
-            del kwargs['enter_cb']
-        TextBox.__init__(self, *args, **kwargs)
-        self.addKeyboardListener(self)
-
-    def onKeyUp(self, sender, keycode, modifiers):
-        pass
-
-    def onKeyDown(self, sender, keycode, modifiers):
-        pass
-
-    def onKeyPress(self, sender, keycode, modifiers):
-        if self.enter_cb and keycode == KEY_ENTER:
-            self.enter_cb(self)
-
-
-class GroupSelector(DialogBox):
-
-    def __init__(self, top_widgets, initial_groups, selected_groups,
-                 ok_title="OK", ok_cb=None, cancel_cb=None):
-        DialogBox.__init__(self, centered=True)
-        main_panel = VerticalPanel()
-        self.ok_cb = ok_cb
-        self.cancel_cb = cancel_cb
-
-        for wid in top_widgets:
-            main_panel.add(wid)
-
-        main_panel.add(Label('Select in which groups your contact is:'))
-        self.list_box = ListBox()
-        self.list_box.setMultipleSelect(True)
-        self.list_box.setVisibleItemCount(5)
-        self.setAvailableGroups(initial_groups)
-        self.setGroupsSelected(selected_groups)
-        main_panel.add(self.list_box)
-
-        def cb(text):
-            self.list_box.addItem(text)
-            self.list_box.setItemSelected(self.list_box.getItemCount() - 1, "selected")
-
-        main_panel.add(AddGroupPanel(initial_groups, cb))
-
-        button_panel = HorizontalPanel()
-        button_panel.addStyleName("marginAuto")
-        button_panel.add(Button(ok_title, self.onOK))
-        button_panel.add(Button("Cancel", self.onCancel))
-        main_panel.add(button_panel)
-
-        self.setWidget(main_panel)
-
-    def getSelectedGroups(self):
-        """Return a list of selected groups"""
-        return self.list_box.getSelectedValues()
-
-    def setAvailableGroups(self, groups):
-        _groups = list(set(groups))
-        _groups.sort()
-        self.list_box.clear()
-        for group in _groups:
-            self.list_box.addItem(group)
-
-    def setGroupsSelected(self, selected_groups):
-        self.list_box.setItemTextSelection(selected_groups)
-
-    def onOK(self, sender):
-        self.hide()
-        if self.ok_cb:
-            self.ok_cb(self)
-
-    def onCancel(self, sender):
-        self.hide()
-        if self.cancel_cb:
-            self.cancel_cb(self)
-
-
-class AddGroupPanel(HorizontalPanel):
-    def __init__(self, groups, cb=None):
-        """
-        @param groups: list of the already existing groups
-        """
-        HorizontalPanel.__init__(self)
-        self.groups = groups
-        self.add(Label('Add group:'))
-        self.textbox = ExtTextBox(enter_cb=self.onGroupInput)
-        self.add(self.textbox)
-        self.add(Button("add", lambda sender: self.onGroupInput(self.textbox)))
-        self.cb = cb
-
-    def onGroupInput(self, sender):
-        text = sender.getText()
-        if text == "":
-            return
-        for group in self.groups:
-            if text == group:
-                Window.alert("The group '%s' already exists." % text)
-                return
-        for pattern in FORBIDDEN_PATTERNS_IN_GROUP:
-            if pattern in text:
-                Window.alert("The pattern '%s' is not allowed in group names." % pattern)
-                return
-        sender.setText('')
-        self.groups.append(text)
-        if self.cb is not None:
-            self.cb(text)
-
-
-class WheelTextBox(TextBox, MouseWheelHandler):
-
-    def __init__(self, *args, **kwargs):
-        TextBox.__init__(self, *args, **kwargs)
-        MouseWheelHandler.__init__(self)
-
-
-class IntSetter(HorizontalPanel):
-    """This class show a bar with button to set an int value"""
-
-    def __init__(self, label, value=0, value_max=None, visible_len=3):
-        """initialize the intSetter
-        @param label: text shown in front of the setter
-        @param value: initial value
-        @param value_max: limit value, None or 0 for unlimited"""
-        HorizontalPanel.__init__(self)
-        self.value = value
-        self.value_max = value_max
-        _label = Label(label)
-        self.add(_label)
-        self.setCellWidth(_label, "100%")
-        minus_button = Button("-", self.onMinus)
-        self.box = WheelTextBox()
-        self.box.setVisibleLength(visible_len)
-        self.box.setText(str(value))
-        self.box.addInputListener(self)
-        self.box.addMouseWheelListener(self)
-        plus_button = Button("+", self.onPlus)
-        self.add(minus_button)
-        self.add(self.box)
-        self.add(plus_button)
-        self.valueChangedListener = []
-
-    def addValueChangeListener(self, listener):
-        self.valueChangedListener.append(listener)
-
-    def removeValueChangeListener(self, listener):
-        if listener in self.valueChangedListener:
-            self.valueChangedListener.remove(listener)
-
-    def _callListeners(self):
-        for listener in self.valueChangedListener:
-            listener(self.value)
-
-    def setValue(self, value):
-        """Change the value and fire valueChange listeners"""
-        self.value = value
-        self.box.setText(str(value))
-        self._callListeners()
-
-    def onMinus(self, sender, step=1):
-        self.value = max(0, self.value - step)
-        self.box.setText(str(self.value))
-        self._callListeners()
-
-    def onPlus(self, sender, step=1):
-        self.value += step
-        if self.value_max:
-            self.value = min(self.value, self.value_max)
-        self.box.setText(str(self.value))
-        self._callListeners()
-
-    def onInput(self, sender):
-        """Accept only valid integer && normalize print (no leading 0)"""
-        try:
-            self.value = int(self.box.getText()) if self.box.getText() else 0
-        except ValueError:
-            pass
-        if self.value_max:
-            self.value = min(self.value, self.value_max)
-        self.box.setText(str(self.value))
-        self._callListeners()
-
-    def onMouseWheel(self, sender, velocity):
-        if velocity > 0:
-            self.onMinus(sender, 10)
-        else:
-            self.onPlus(sender, 10)
--- a/browser_side/file_tools.py	Fri May 16 11:51:10 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,148 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Libervia: a Salut à Toi frontend
-# Copyright (C) 2011, 2012, 2013, 2014 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 sat.core.log import getLogger
-log = getLogger(__name__)
-from pyjamas.ui.FileUpload import FileUpload
-from pyjamas.ui.FormPanel import FormPanel
-from pyjamas import Window
-from pyjamas import DOM
-from pyjamas.ui.VerticalPanel import VerticalPanel
-from pyjamas.ui.HTML import HTML
-from pyjamas.ui.HorizontalPanel import HorizontalPanel
-from pyjamas.ui.Button import Button
-from pyjamas.ui.Label import Label
-
-
-class FilterFileUpload(FileUpload):
-
-    def __init__(self, name, max_size, types=None):
-        """
-        @param name: the input element name and id
-        @param max_size: maximum file size in MB
-        @param types: allowed types as a list of couples (x, y, z):
-        - x: MIME content type e.g. "audio/ogg"
-        - y: file extension e.g. "*.ogg"
-        - z: description for the user e.g. "Ogg Vorbis Audio"
-        If types is None, all file format are accepted
-        """
-        FileUpload.__init__(self)
-        self.setName(name)
-        while DOM.getElementById(name):
-            name = "%s_" % name
-        self.setID(name)
-        self._id = name
-        self.max_size = max_size
-        self.types = types
-
-    def getFileInfo(self):
-        from __pyjamas__ import JS
-        JS("var file = top.document.getElementById(this._id).files[0]; return [file.size, file.type]")
-
-    def check(self):
-        if self.getFilename() == "":
-            return False
-        (size, filetype) = self.getFileInfo()
-        if self.types and filetype not in [x for (x, y, z) in self.types]:
-            types = []
-            for type_ in ["- %s (%s)" % (z, y) for (x, y, z) in self.types]:
-                if type_ not in types:
-                    types.append(type_)
-            Window.alert('This file type is not accepted.\nAccepted file types are:\n\n%s' % "\n".join(types))
-            return False
-        if size > self.max_size * pow(2, 20):
-            Window.alert('This file is too big!\nMaximum file size: %d MB.' % self.max_size)
-            return False
-        return True
-
-
-class FileUploadPanel(FormPanel):
-
-    def __init__(self, action_url, input_id, max_size, texts=None, close_cb=None):
-        """Build a form panel to upload a file.
-        @param action_url: the form action URL
-        @param input_id: the input element name and id
-        @param max_size: maximum file size in MB
-        @param texts: a dict to ovewrite the default textual values
-        @param close_cb: the close button callback method
-        """
-        FormPanel.__init__(self)
-        self.texts = {'ok_button': 'Upload file',
-                     'cancel_button': 'Cancel',
-                     'body': 'Please select a file.',
-                     'submitting': '<strong>Submitting, please wait...</strong>',
-                     'errback': "Your file has been rejected...",
-                     'body_errback': 'Please select another file.',
-                     'callback': "Your file has been accepted!"}
-        if isinstance(texts, dict):
-            self.texts.update(texts)
-        self.close_cb = close_cb
-        self.setEncoding(FormPanel.ENCODING_MULTIPART)
-        self.setMethod(FormPanel.METHOD_POST)
-        self.setAction(action_url)
-        self.vPanel = VerticalPanel()
-        self.message = HTML(self.texts['body'])
-        self.vPanel.add(self.message)
-
-        hPanel = HorizontalPanel()
-        hPanel.setSpacing(5)
-        hPanel.setStyleName('marginAuto')
-        self.file_upload = FilterFileUpload(input_id, max_size)
-        self.vPanel.add(self.file_upload)
-
-        self.upload_btn = Button(self.texts['ok_button'], getattr(self, "onSubmitBtnClick"))
-        hPanel.add(self.upload_btn)
-        hPanel.add(Button(self.texts['cancel_button'], getattr(self, "onCloseBtnClick")))
-
-        self.status = Label()
-        hPanel.add(self.status)
-
-        self.vPanel.add(hPanel)
-
-        self.add(self.vPanel)
-        self.addFormHandler(self)
-
-    def setCloseCb(self, close_cb):
-        self.close_cb = close_cb
-
-    def onCloseBtnClick(self):
-        if self.close_cb:
-            self.close_cb()
-        else:
-            log.warning("no close method defined")
-
-    def onSubmitBtnClick(self):
-        if not self.file_upload.check():
-            return
-        self.message.setHTML(self.texts['submitting'])
-        self.upload_btn.setEnabled(False)
-        self.submit()
-
-    def onSubmit(self, event):
-        pass
-
-    def onSubmitComplete(self, event):
-        result = event.getResults()
-        if result != "OK":
-            Window.alert(self.texts['errback'])
-            self.message.setHTML(self.texts['body_errback'])
-            self.upload_btn.setEnabled(True)
-        else:
-            Window.alert(self.texts['callback'])
-            self.close_cb()
--- a/browser_side/html_tools.py	Fri May 16 11:51:10 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Libervia: a Salut à Toi frontend
-# Copyright (C) 2011, 2012, 2013, 2014 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 nativedom import NativeDOM
-from sat_frontends.tools import xmltools
-import re
-
-dom = NativeDOM()
-
-
-def html_sanitize(html):
-    """Naive sanitization of HTML"""
-    return html.replace('<', '&lt;').replace('>', '&gt;')
-
-
-def html_strip(html):
-    """Strip leading/trailing white spaces, HTML line breaks and &nbsp; sequences."""
-    cleaned = re.sub(r"^(<br/?>|&nbsp;|\s)+", "", html)
-    cleaned = re.sub(r"(<br/?>|&nbsp;|\s)+$", "", cleaned)
-    return cleaned
-
-
-def inlineRoot(xhtml):
-    """ make root element inline """
-    doc = dom.parseString(xhtml)
-    return xmltools.inlineRoot(doc)
-
-
-def convertNewLinesToXHTML(text):
-    return text.replace('\n', '<br/>')
--- a/browser_side/jid.py	Fri May 16 11:51:10 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Libervia: a Salut à Toi frontend
-# Copyright (C) 2011, 2012, 2013, 2014 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/>.
-
-
-class JID(object):
-    """This class help manage JID (Node@Domaine/Resource)"""
-
-    def __init__(self, jid):
-        self.__raw = str(jid)
-        self.__parse()
-
-    def __parse(self):
-        """find node domaine and resource"""
-        node_end = self.__raw.find('@')
-        if node_end < 0:
-            node_end = 0
-        domain_end = self.__raw.find('/')
-        if domain_end < 1:
-            domain_end = len(self.__raw)
-        self.node = self.__raw[:node_end]
-        self.domain = self.__raw[(node_end + 1) if node_end else 0:domain_end]
-        self.resource = self.__raw[domain_end + 1:]
-        if not node_end:
-            self.bare = self.__raw
-        else:
-            self.bare = self.node + '@' + self.domain
-
-    def __str__(self):
-        return self.__raw.__str__()
-
-    def is_valid(self):
-        """return True if the jid is xmpp compliant"""
-        #FIXME: always return True for the moment
-        return True
-
-    def __eq__(self, other):
-        """Redefine equality operator to implement the naturally expected test"""
-        return self.node == other.node and self.domain == other.domain and self.resource == other.resource
--- a/browser_side/list_manager.py	Fri May 16 11:51:10 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,608 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Libervia: a Salut à Toi frontend
-# Copyright (C) 2013, 2014 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 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
-from pyjamas.ui.DropWidget import DropWidget
-from pyjamas.Timer import Timer
-from pyjamas import DOM
-
-from base_panels import PopupMenuPanel
-from base_widget import DragLabel
-
-# HTML content for the removal button (image or text)
-REMOVE_BUTTON = '<span class="recipientRemoveIcon">x</span>'
-
-# 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 = ""
-
-
-class ListManager():
-    """A manager for sub-panels to assign elements to lists."""
-
-    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
-        """
-        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
-
-        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)
-
-        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)
-
-    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)
-
-    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)
-
-    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)
-
-    def _addChild(self, entry, title_format):
-        """Add a button and FlowPanel for the corresponding map entry."""
-        button = Button(title_format % entry["title"])
-        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"]
-        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"])
-
-        _child = ListPanel(self, entry, self.style)
-        self._parent.setWidget(y, x + 1, _child)
-
-        self.__children[entry["title"]] = {}
-        self.__children[entry["title"]]["button"] = button
-        self.__children[entry["title"]]["panel"] = _child
-
-        if hasattr(self, "popup_menu"):
-            # this is done if 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:
-            return
-        _map = self.getContacts()
-        for key in _map:
-            if len(_map[key]) > 0 or not self.__keys_dict[key]["optional"]:
-                self.setContactPanelVisible(key, True)
-
-    def setVisible(self, visible):
-        self._refresh(visible)
-
-    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
-
-    @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 setRemainingListUnsorted(self):
-        """Mark a change (deletion) so the list will be sorted before it's used."""
-        self.__remaining_list_sorted = False
-
-    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_)
-
-    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:
-                continue
-            assigned_contacts.update(assigned_map[key_])
-        for contact_ in contacts:
-            if contact_ not in self.__list or contact_ in self.__remaining_list:
-                continue
-            if contact_ in assigned_contacts:
-                continue  # the contact is assigned somewhere else
-            self.__remaining_list.append(contact_)
-            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])
-            else:
-                self.__children[key]["panel"].setContacts([])
-        self._refresh()
-
-    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
-
-    @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
-
-    def registerPopupMenuPanel(self, entries, hide, callback):
-        "Register a popup menu panel that will be bound to all contact keys elements."
-        self.popup_menu = PopupMenuPanel(entries=entries, hide=hide, callback=callback, style={"item": self.style["popupMenuItem"]})
-
-
-class DragAutoCompleteTextBox(AutoCompleteTextBox, 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.
-    """
-
-    def __init__(self, parent, event_cbs, style):
-        AutoCompleteTextBox.__init__(self)
-        DragLabel.__init__(self, '', 'CONTACT_TEXTBOX')  # The group prefix "@" is already in text so we use only the "CONTACT_TEXTBOX" type
-        self._parent = parent
-        self.event_cbs = event_cbs
-        self.style = style
-        self.addMouseListener(self)
-        self.addFocusListener(self)
-        self.addChangeListener(self)
-        self.addStyleName(style["textBox"])
-        self.reset()
-
-    def reset(self):
-        self.setText("")
-        self.setValid()
-
-    def setValid(self, valid=True):
-        if self.getText() == "":
-            valid = True
-        if valid:
-            self.removeStyleName(self.style["textBox-invalid"])
-        else:
-            self.addStyleName(self.style["textBox-invalid"])
-        self.valid = valid
-
-    def onDragStart(self, event):
-        self._text = self.getText()
-        DragLabel.onDragStart(self, event)
-        self._parent.setTargetDropCell(None)
-        self.setSelectionRange(len(self.getText()), 0)
-
-    def onDragEnd(self, event):
-        target = self._parent.target_drop_cell  # parent or another ListPanel
-        if self.getText() == "" or target is None:
-            return
-        self.event_cbs["drop"](self, target)
-
-    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)
-            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)
-
-    def removeOrReset(self):
-        if hasattr(self, "remove_btn"):
-            self.remove_btn.click()
-        else:
-            self.reset()
-
-    def onMouseMove(self, sender):
-        """Mouse enters the area of a DragAutoCompleteTextBox."""
-        if hasattr(sender, "remove_btn"):
-            sender.remove_btn.setVisible(True)
-
-    def onMouseLeave(self, sender):
-        """Mouse leaves the area of a DragAutoCompleteTextBox."""
-        if hasattr(sender, "remove_btn"):
-            Timer(1500, lambda timer: sender.remove_btn.setVisible(False))
-
-    def onFocus(self, sender):
-        sender.setSelectionRange(0, len(self.getText()))
-        self.event_cbs["focus"](sender)
-
-    def validate(self):
-        self.setSelectionRange(len(self.getText()), 0)
-        self.event_cbs["validate"](self)
-
-    def onChange(self, sender):
-        """The textbox or list selection is changed"""
-        if isinstance(sender, ListBox):
-            AutoCompleteTextBox.onChange(self, sender)
-        self.validate()
-
-    def onClick(self, sender):
-        """The list is clicked"""
-        AutoCompleteTextBox.onClick(self, sender)
-        self.validate()
-
-    def onKeyUp(self, sender, keycode, modifiers):
-        """Listen for ENTER key stroke"""
-        AutoCompleteTextBox.onKeyUp(self, sender, keycode, modifiers)
-        if keycode == KEY_ENTER:
-            self.validate()
-
-
-class DropCell(DropWidget):
-    """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.
-    """
-
-    def __init__(self, drop_cbs):
-        DropWidget.__init__(self)
-        self.drop_cbs = drop_cbs
-
-    def onDragEnter(self, event):
-        self.addStyleName(self.style["dragoverPanel"])
-        DOM.eventPreventDefault(event)
-
-    def onDragLeave(self, event):
-        if event.clientX <= self.getAbsoluteLeft() or event.clientY <= self.getAbsoluteTop()\
-            or event.clientX >= self.getAbsoluteLeft() + self.getOffsetWidth() - 1\
-            or event.clientY >= self.getAbsoluteTop() + self.getOffsetHeight() - 1:
-            # We check that we are inside widget's box, and we don't remove the style in this case because
-            # if the mouse is over a widget inside the DropWidget, we don't want the style to be removed
-            self.removeStyleName(self.style["dragoverPanel"])
-
-    def onDragOver(self, event):
-        DOM.eventPreventDefault(event)
-
-    def onDrop(self, event):
-        DOM.eventPreventDefault(event)
-        dt = event.dataTransfer
-        # 'text', 'text/plain', and 'Text' are equivalent.
-        item, item_type = dt.getData("text/plain").split('\n')  # Workaround for webkit, only text/plain seems to be managed
-        if item_type and item_type[-1] == '\0':  # Workaround for what looks like a pyjamas bug: the \0 should not be there, and
-            item_type = item_type[:-1]           # .strip('\0') and .replace('\0','') don't work. TODO: check this and fill a bug report
-        if item_type in self.drop_cbs.keys():
-            self.drop_cbs[item_type](self, item)
-        self.removeStyleName(self.style["dragoverPanel"])
-
-
-VALID = 1
-INVALID = 2
-DELETE = 3
-
-
-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."""
-
-    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)
-                    }
-        DropCell.__init__(self, drop_cbs)
-        self.style = style
-        self.addStyleName(self.style["keyPanel"])
-        self._parent = parent
-        self.key = entry["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.
-        """
-        if hasattr(self, "_last_textbox"):
-            if self._last_textbox.getText() == "":
-                return
-            self._last_textbox.setRemoveButton()
-        else:
-            switchPrevious = False
-
-        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)
-
-        def remove_cb(sender):
-            """Callback for the button to remove this contact."""
-            self._parent.addToRemainingList(sender.getText())
-            self._parent.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()):
-                sender.removeOrReset()
-            else:
-                parent._parent.removeFromRemainingList(sender.getText())
-
-        events_cbs = {"focus": focus_cb, "validate": self.addContact, "remove": remove_cb, "drop": drop_cb}
-        textbox = DragAutoCompleteTextBox(self, events_cbs, self.style)
-        self.add(textbox)
-        if switchPrevious:
-            textbox.setText(self._last_textbox.getText())
-            textbox.setValid(self._last_textbox.valid)
-            self._last_textbox.reset()
-            previous = self._last_textbox
-        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 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 == "":
-            return DELETE
-        if countItemInList(self.getContacts(), contact) > (1 if modify else 0):
-            return DELETE
-        return VALID if contact in self._parent.list else INVALID
-
-    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
-        """
-        if isinstance(contact, DragAutoCompleteTextBox):
-            sender = contact
-            contact = sender.getText()
-        valid = self._checkContact(contact, sender is not None)
-        if sender is None:
-            # method has been called to modify but to add a contact
-            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)
-        else:
-            sender.setValid(valid == VALID)
-        if valid != VALID:
-            if sender is not None and valid == DELETE:
-                sender.removeOrReset()
-            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)
-        self._last_textbox.setFocus(True)
-        return True
-
-    def emptyContacts(self):
-        """Empty the list of contacts."""
-        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")
-
-        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)
-
-        container.add(grid)
-        container.add(buttons)
-
-        self.add(container)
-        self.center()
-
-    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()
--- a/browser_side/logging.py	Fri May 16 11:51:10 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Libervia: a Salut à Toi frontend
-# Copyright (C) 2011, 2012, 2013, 2014 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/>.
-"""This module configure logs for Libervia browser side"""
-
-from __pyjamas__ import console
-from constants import Const as C
-from sat.core import log # XXX: we don't use core.log_config here to avoid the impossible imports in pyjamas
-
-
-class LiberviaLogger(log.Logger):
-
-    def out(self, message, level=None):
-        if level == C.LOG_LVL_DEBUG:
-            console.debug(message)
-        elif level == C.LOG_LVL_INFO:
-            console.info(message)
-        elif level == C.LOG_LVL_WARNING:
-            console.warn(message)
-        else:
-            console.error(message)
-
-
-def configure():
-    fmt = '[%(name)s] %(message)s'
-    log.configure(C.LOG_BACKEND_CUSTOM,
-                  logger_class = LiberviaLogger,
-                  level = C.LOG_LVL_DEBUG,
-                  fmt = fmt,
-                  output = None,
-                  logger = None,
-                  colors = False,
-                  force_colors = False)
-    # FIXME: workaround for Pyjamas, need to be removed when Pyjamas is fixed
-    LiberviaLogger.fmt = fmt
--- a/browser_side/menu.py	Fri May 16 11:51:10 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,267 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Libervia: a Salut à Toi frontend
-# Copyright (C) 2011, 2012, 2013, 2014 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/>.
-
-import pyjd  # this is dummy in pyjs
-from sat.core.log import getLogger
-log = getLogger(__name__)
-from pyjamas.ui.SimplePanel import SimplePanel
-from pyjamas.ui.MenuBar import MenuBar
-from pyjamas.ui.MenuItem import MenuItem
-from pyjamas.ui.HTML import HTML
-from pyjamas.ui.Frame import Frame
-from pyjamas import Window
-from jid import JID
-from file_tools import FileUploadPanel
-from xmlui import XMLUI
-from browser_side import panels
-from browser_side import dialog
-from contact_group import ContactGroupEditor
-from sat.core.i18n import _
-
-
-class MenuCmd:
-
-    def __init__(self, object_, handler):
-        self._object = object_
-        self._handler = handler
-
-    def execute(self):
-        handler = getattr(self._object, self._handler)
-        handler()
-
-
-class PluginMenuCmd:
-
-    def __init__(self, host, action_id):
-        self.host = host
-        self.action_id = action_id
-
-    def execute(self):
-        self.host.launchAction(self.action_id, None)
-
-
-class LiberviaMenuBar(MenuBar):
-
-    def __init__(self):
-        MenuBar.__init__(self, vertical=False)
-        self.setStyleName('gwt-MenuBar-horizontal') # XXX: workaround for the Pyjamas' class name fix (it's now "gwt-MenuBar gwt-MenuBar-horizontal")
-                                                    # TODO: properly adapt CSS to the new class name
-
-    def doItemAction(self, item, fireCommand):
-        MenuBar.doItemAction(self, item, fireCommand)
-        if item == self.items[-1] and self.popup:
-            self.popup.setPopupPosition(Window.getClientWidth() -
-                                        self.popup.getOffsetWidth() - 22,
-                                        self.getAbsoluteTop() +
-                                        self.getOffsetHeight() - 1)
-            self.popup.addStyleName('menuLastPopup')
-
-
-class AvatarUpload(FileUploadPanel):
-    def __init__(self):
-        texts = {'ok_button': 'Upload avatar',
-                 'body': 'Please select an image to show as your avatar...<br>Your picture must be a square and will be resized to 64x64 pixels if necessary.',
-                 'errback': "Can't open image... did you actually submit an image?",
-                 'body_errback': 'Please select another image file.',
-                 'callback': "Your new profile picture has been set!"}
-        FileUploadPanel.__init__(self, 'upload_avatar', 'avatar_path', 2, texts)
-
-
-class Menu(SimplePanel):
-
-    def __init__(self, host):
-        self.host = host
-        SimplePanel.__init__(self)
-        self.setStyleName('menuContainer')
-
-    def createMenus(self, add_menus):
-        _item_tpl = "<img src='media/icons/menu/%s_menu_red.png' />%s"
-        menus_dict = {}
-        menus_order = []
-
-        def addMenu(menu_name, menu_name_i18n, item_name_i18n, icon, menu_cmd):
-            """ add a menu to menu_dict """
-            log.info("addMenu: %s %s %s %s %s" % (menu_name, menu_name_i18n, item_name_i18n, icon, menu_cmd))
-            try:
-                menu_bar = menus_dict[menu_name]
-            except KeyError:
-                menu_bar = menus_dict[menu_name] = MenuBar(vertical=True)
-                menus_order.append((menu_name, menu_name_i18n, icon))
-            if item_name_i18n and menu_cmd:
-                menu_bar.addItem(item_name_i18n, menu_cmd)
-
-        addMenu("General", _("General"), _("Web widget"), 'home', MenuCmd(self, "onWebWidget"))
-        addMenu("General", _("General"), _("Disconnect"), 'home', MenuCmd(self, "onDisconnect"))
-        addMenu("Contacts", _("Contacts"), None, 'social', None)
-        addMenu("Groups", _("Groups"), _("Discussion"), 'social', MenuCmd(self, "onJoinRoom"))
-        addMenu("Groups", _("Groups"), _("Collective radio"), 'social', MenuCmd(self, "onCollectiveRadio"))
-        addMenu("Games", _("Games"), _("Tarot"), 'games', MenuCmd(self, "onTarotGame"))
-        addMenu("Games", _("Games"), _("Xiangqi"), 'games', MenuCmd(self, "onXiangqiGame"))
-
-        # additional menus
-        for action_id, type_, path, path_i18n in add_menus:
-            if not path:
-                log.warning("skipping menu without path")
-                continue
-            if len(path) != len(path_i18n):
-                log.error("inconsistency between menu paths")
-                continue
-            menu_name = path[0]
-            menu_name_i18n = path_i18n[0]
-            item_name = path[1:]
-            if not item_name:
-                log.warning("skipping menu with a path of lenght 1 [%s]" % path[0])
-                continue
-            item_name_i18n = ' | '.join(path_i18n[1:])
-            addMenu(menu_name, menu_name_i18n, item_name_i18n, 'plugins', PluginMenuCmd(self.host, action_id))
-
-        # menu items that should be displayed after the automatically added ones
-        addMenu("Contacts", _("Contacts"), _("Manage groups"), 'social', MenuCmd(self, "onManageContactGroups"))
-
-        menus_order.append(None)  # we add separator
-
-        addMenu("Help", _("Help"), _("Social contract"), 'help', MenuCmd(self, "onSocialContract"))
-        addMenu("Help", _("Help"), _("About"), 'help', MenuCmd(self, "onAbout"))
-        addMenu("Settings", _("Settings"), _("Account"), 'settings', MenuCmd(self, "onAccount"))
-        addMenu("Settings", _("Settings"), _("Parameters"), 'settings', MenuCmd(self, "onParameters"))
-
-        # XXX: temporary, will change when a full profile will be managed in SàT
-        addMenu("Settings", _("Settings"), _("Upload avatar"), 'settings', MenuCmd(self, "onAvatarUpload"))
-
-        menubar = LiberviaMenuBar()
-
-        for menu_data in menus_order:
-            if menu_data is None:
-                _separator = MenuItem('', None)
-                _separator.setStyleName('menuSeparator')
-                menubar.addItem(_separator, None)
-            else:
-                menu_name, menu_name_i18n, icon = menu_data
-                menubar.addItem(MenuItem(_item_tpl % (icon, menu_name_i18n), True, menus_dict[menu_name]))
-
-        self.add(menubar)
-
-    #General menu
-    def onWebWidget(self):
-        web_panel = panels.WebPanel(self.host, "http://www.goffi.org")
-        self.host.addWidget(web_panel)
-        self.host.setSelected(web_panel)
-
-    def onDisconnect(self):
-        def confirm_cb(answer):
-            if answer:
-                log.info("disconnection")
-                self.host.bridge.call('disconnect', None)
-        _dialog = dialog.ConfirmDialog(confirm_cb, text="Do you really want to disconnect ?")
-        _dialog.show()
-
-    def onSocialContract(self):
-        _frame = Frame('contrat_social.html')
-        _frame.setStyleName('infoFrame')
-        _dialog = dialog.GenericDialog("Contrat Social", _frame)
-        _dialog.setSize('80%', '80%')
-        _dialog.show()
-
-    def onAbout(self):
-        _about = HTML("""<b>Libervia</b>, a Salut &agrave; Toi project<br />
-<br />
-You can contact the author at <a href="mailto:goffi@goffi.org">goffi@goffi.org</a><br />
-Blog available (mainly in french) at <a href="http://www.goffi.org" target="_blank">http://www.goffi.org</a><br />
-Project page: <a href="http://sat.goffi.org"target="_blank">http://sat.goffi.org</a><br />
-<br />
-Any help welcome :)
-<p style='font-size:small;text-align:center'>This project is dedicated to Roger Poisson</p>
-""")
-        _dialog = dialog.GenericDialog("About", _about)
-        _dialog.show()
-
-    #Contact menu
-    def onManageContactGroups(self):
-        """Open the contact groups manager."""
-
-        def onCloseCallback():
-            pass
-
-        ContactGroupEditor(self.host, None, onCloseCallback)
-
-    #Group menu
-    def onJoinRoom(self):
-
-        def invite(room_jid, contacts):
-            for contact in contacts:
-                self.host.bridge.call('inviteMUC', None, contact, room_jid)
-
-        def join(room_jid, contacts):
-            if self.host.whoami:
-                nick = self.host.whoami.node
-                if room_jid not in [room.bare for room in self.host.room_list]:
-                    self.host.bridge.call('joinMUC', lambda room_jid: invite(room_jid, contacts), room_jid, nick)
-                else:
-                    self.host.getOrCreateLiberviaWidget(panels.ChatPanel, (room_jid, "group"), True, JID(room_jid).bare)
-                    invite(room_jid, contacts)
-
-        dialog.RoomAndContactsChooser(self.host, join, ok_button="Join", visible=(True, False))
-
-    def onCollectiveRadio(self):
-        def callback(room_jid, contacts):
-            self.host.bridge.call('launchRadioCollective', None, contacts, room_jid)
-        dialog.RoomAndContactsChooser(self.host, callback, ok_button="Choose", title="Collective Radio", visible=(False, True))
-
-    #Game menu
-    def onTarotGame(self):
-        def onPlayersSelected(room_jid, other_players):
-            self.host.bridge.call('launchTarotGame', None, other_players, room_jid)
-        dialog.RoomAndContactsChooser(self.host, onPlayersSelected, 3, title="Tarot", title_invite="Please select 3 other players", visible=(False, True))
-
-    def onXiangqiGame(self):
-        Window.alert("A Xiangqi game is planed, but not available yet")
-
-    #Settings menu
-
-    def onAccount(self):
-        def gotUI(xmlui):
-            if not xmlui:
-                return
-            body = XMLUI(self.host, xmlui)
-            _dialog = dialog.GenericDialog("Manage your XMPP account", body, options=['NO_CLOSE'])
-            body.setCloseCb(_dialog.close)
-            _dialog.show()
-        self.host.bridge.call('getAccountDialogUI', gotUI)
-
-    def onParameters(self):
-        def gotParams(xmlui):
-            if not xmlui:
-                return
-            body = XMLUI(self.host, xmlui)
-            _dialog = dialog.GenericDialog("Parameters", body, options=['NO_CLOSE'])
-            body.setCloseCb(_dialog.close)
-            _dialog.setSize('80%', '80%')
-            _dialog.show()
-        self.host.bridge.call('getParamsUI', gotParams)
-
-    def removeItemParams(self):
-        """Remove the Parameters item from the Settings menu bar."""
-        self.menu_settings.removeItem(self.item_params)
-
-    def onAvatarUpload(self):
-        body = AvatarUpload()
-        _dialog = dialog.GenericDialog("Avatar upload", body, options=['NO_CLOSE'])
-        body.setCloseCb(_dialog.close)
-        _dialog.setWidth('40%')
-        _dialog.show()
--- a/browser_side/nativedom.py	Fri May 16 11:51:10 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,108 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Libervia: a Salut à Toi frontend
-# Copyright (C) 2011, 2012, 2013, 2014 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/>.
-
-"""
-This class provide basic DOM parsing based on native javascript parser
-__init__ code comes from Tim Down at http://stackoverflow.com/a/8412989
-"""
-
-from __pyjamas__ import JS
-
-
-class Node():
-
-    def __init__(self, js_node):
-        self._node = js_node
-
-    def _jsNodesList2List(self, js_nodes_list):
-        ret=[]
-        for i in range(len(js_nodes_list)):
-            #ret.append(Element(js_nodes_list.item(i)))
-            ret.append(self.__class__(js_nodes_list.item(i))) # XXX: Ugly, but used to word around a Pyjamas's bug
-        return ret
-
-    @property
-    def nodeName(self):
-        return self._node.nodeName
-
-    @property
-    def wholeText(self):
-        return self._node.wholeText
-
-    @property
-    def childNodes(self):
-        return self._jsNodesList2List(self._node.childNodes)
-
-    def getAttribute(self, attr):
-        return self._node.getAttribute(attr)
-
-    def setAttribute(self, attr, value):
-        return self._node.setAttribute(attr, value)
-
-    def hasAttribute(self, attr):
-        return self._node.hasAttribute(attr)
-
-    def toxml(self):
-        return JS("""this._node.outerHTML || new XMLSerializer().serializeToString(this._node);""")
-
-
-class Element(Node):
-
-    def __init__(self, js_node):
-        Node.__init__(self, js_node)
-
-    def getElementsByTagName(self, tagName):
-        return self._jsNodesList2List(self._node.getElementsByTagName(tagName))
-
-
-class Document(Node):
-
-    def __init__(self, js_document):
-        Node.__init__(self, js_document)
-
-    @property
-    def documentElement(self):
-        return Element(self._node.documentElement)
-
-
-class NativeDOM:
-
-    def __init__(self):
-        JS("""
-
-        if (typeof window.DOMParser != "undefined") {
-            this.parseXml = function(xmlStr) {
-                return ( new window.DOMParser() ).parseFromString(xmlStr, "text/xml");
-            };
-        } else if (typeof window.ActiveXObject != "undefined" &&
-               new window.ActiveXObject("Microsoft.XMLDOM")) {
-            this.parseXml = function(xmlStr) {
-                var xmlDoc = new window.ActiveXObject("Microsoft.XMLDOM");
-                xmlDoc.async = "false";
-                xmlDoc.loadXML(xmlStr);
-                return xmlDoc;
-            };
-        } else {
-            throw new Error("No XML parser found");
-        }
-        """)
-
-    def parseString(self, xml):
-        return Document(self.parseXml(xml))
-
--- a/browser_side/notification.py	Fri May 16 11:51:10 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,120 +0,0 @@
-from __pyjamas__ import JS, wnd
-from sat.core.log import getLogger
-log = getLogger(__name__)
-from pyjamas import Window
-from pyjamas.Timer import Timer
-from browser_side import dialog
-from sat.core.i18n import _
-
-TIMER_DELAY = 5000
-
-
-class Notification(object):
-    """
-    If the browser supports it, the user allowed it to and the tab is in the
-    background, send desktop notifications on messages.
-
-    Requires both Web Notifications and Page Visibility API.
-    """
-
-    def __init__(self):
-        self.enabled = False
-        user_agent = None
-        notif_permission = None
-        JS("""
-        if (!('hidden' in document))
-            document.hidden = false;
-
-        user_agent = navigator.userAgent
-
-        if (!('Notification' in window))
-            return;
-
-        notif_permission = Notification.permission
-
-        if (Notification.permission === 'granted')
-            this.enabled = true;
-
-        else if (Notification.permission === 'default') {
-            Notification.requestPermission(function(permission){
-                if (permission !== 'granted')
-                    return;
-
-                self.enabled = true; //need to use self instead of this
-            });
-        }
-        """)
-
-        if "Chrome" in user_agent and notif_permission not in ['granted', 'denied']:
-            self.user_agent = user_agent
-            self._installChromiumWorkaround()
-
-        wnd().onfocus = self.onFocus
-        # wnd().onblur = self.onBlur
-        self._notif_count = 0
-        self._orig_title = Window.getTitle()
-
-    def _installChromiumWorkaround(self):
-        # XXX: Workaround for Chromium behaviour, it's doens't manage requestPermission on onLoad event
-        # see https://code.google.com/p/chromium/issues/detail?id=274284
-        # FIXME: need to be removed if Chromium behaviour changes
-        try:
-            version_full = [s for s in self.user_agent.split() if "Chrome" in s][0].split('/')[1]
-            version = int(version_full.split('.')[0])
-        except (IndexError, ValueError):
-            log.warning("Can't find Chromium version")
-            version = 0
-        log.info("Chromium version: %d" % (version,))
-        if version < 22:
-            log.info("Notification use the old prefixed version or are unmanaged")
-            return
-        if version < 32:
-            dialog.InfoDialog(_("Notifications activation for Chromium"), _('You need to activate notifications manually for your Chromium version.<br/>To activate notifications, click on the favicon on the left of the address bar')).show()
-            return
-
-        log.info("==> Installing Chromium notifications request workaround <==")
-        self._old_click = wnd().onclick
-        wnd().onclick = self._chromiumWorkaround
-
-    def _chromiumWorkaround(self):
-        log.info("Activating workaround")
-        JS("""
-            Notification.requestPermission(function(permission){
-                if (permission !== 'granted')
-                    return;
-                self.enabled = true; //need to use self instead of this
-            });
-        """)
-        wnd().onclick = self._old_click
-
-    def onFocus(self):
-        Window.setTitle(self._orig_title)
-        self._notif_count = 0
-
-    # def onBlur(self):
-    #     pass
-
-    def isHidden(self):
-        JS("""return document.hidden;""")
-
-    def _notify(self, title, body, icon):
-        if not self.enabled:
-            return
-        notification = None
-        JS("""
-           notification = new Notification(title, {body: body, icon: icon});
-           // Probably won’t work, but it doesn’t hurt to try.
-           notification.addEventListener('click', function() {
-               window.focus();
-           });
-           """)
-        notification.onshow = lambda: Timer(TIMER_DELAY, lambda timer: notification.close())
-
-    def highlightTab(self):
-        self._notif_count += 1
-        Window.setTitle("%s (%d)" % (self._orig_title, self._notif_count))
-
-    def notify(self, title, body, icon='/media/icons/apps/48/sat.png'):
-        if self.isHidden():
-            self._notify(title, body, icon)
-            self.highlightTab()
--- a/browser_side/panels.py	Fri May 16 11:51:10 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1408 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Libervia: a Salut à Toi frontend
-# Copyright (C) 2011, 2012, 2013, 2014 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/>.
-
-import pyjd  # this is dummy in pyjs
-from sat.core.log import getLogger
-log = getLogger(__name__)
-from pyjamas.ui.SimplePanel import SimplePanel
-from pyjamas.ui.AbsolutePanel import AbsolutePanel
-from pyjamas.ui.VerticalPanel import VerticalPanel
-from pyjamas.ui.HorizontalPanel import HorizontalPanel
-from pyjamas.ui.HTMLPanel import HTMLPanel
-from pyjamas.ui.Frame import Frame
-from pyjamas.ui.TextArea import TextArea
-from pyjamas.ui.Label import Label
-from pyjamas.ui.Button import Button
-from pyjamas.ui.HTML import HTML
-from pyjamas.ui.Image import Image
-from pyjamas.ui.ClickListener import ClickHandler
-from pyjamas.ui.FlowPanel import FlowPanel
-from pyjamas.ui.KeyboardListener import KEY_ENTER, KEY_UP, KEY_DOWN, KeyboardHandler
-from pyjamas.ui.MouseListener import MouseHandler
-from pyjamas.ui.FocusListener import FocusHandler
-from pyjamas.Timer import Timer
-from pyjamas import DOM
-from pyjamas import Window
-from __pyjamas__ import doc
-
-from datetime import datetime
-from time import time
-from jid import JID
-
-from html_tools import html_sanitize
-from base_panels import ChatText, OccupantsList, PopupMenuPanel, BaseTextEditor, LightTextEditor, HTMLTextEditor
-from card_game import CardPanel
-from radiocol import RadioColPanel
-from menu import Menu
-from browser_side import dialog
-from browser_side import base_widget
-from browser_side import richtext
-from browser_side import contact
-
-from constants import Const as C
-from plugin_xep_0085 import ChatStateMachine
-from sat_frontends.tools.strings import addURLToText
-from sat_frontends.tools.games import SYMBOLS
-from sat.core.i18n import _
-
-
-# TODO: at some point we should decide which behaviors to keep and remove these two constants
-TOGGLE_EDITION_USE_ICON = False  # set to True to use an icon inside the "toggle syntax" button
-NEW_MESSAGE_USE_BUTTON = False  # set to True to display the "New message" button instead of an empty entry
-
-
-class UniBoxPanel(HorizontalPanel):
-    """Panel containing the UniBox"""
-
-    def __init__(self, host):
-        HorizontalPanel.__init__(self)
-        self.host = host
-        self.setStyleName('uniBoxPanel')
-        self.unibox = None
-
-    def refresh(self):
-        """Enable or disable this panel. Contained widgets are created when necessary."""
-        enable = self.host.params_ui['unibox']['value']
-        self.setVisible(enable)
-        if enable and not self.unibox:
-            self.button = Button('<img src="media/icons/tango/actions/32/format-text-italic.png" class="richTextIcon"/>')
-            self.button.setTitle('Open the rich text editor')
-            self.button.addStyleName('uniBoxButton')
-            self.add(self.button)
-            self.unibox = UniBox(self.host)
-            self.add(self.unibox)
-            self.setCellWidth(self.unibox, '100%')
-            self.button.addClickListener(self.openRichMessageEditor)
-            self.unibox.addKey("@@: ")
-            self.unibox.onSelectedChange(self.host.getSelected())
-
-    def openRichMessageEditor(self):
-        """Open the rich text editor."""
-        self.button.setVisible(False)
-        self.unibox.setVisible(False)
-        self.setCellWidth(self.unibox, '0px')
-        self.host.panel._contactsMove(self)
-
-        def afterEditCb():
-            Window.removeWindowResizeListener(self)
-            self.host.panel._contactsMove(self.host.panel._hpanel)
-            self.setCellWidth(self.unibox, '100%')
-            self.button.setVisible(True)
-            self.unibox.setVisible(True)
-            self.host.resize()
-
-        richtext.RichMessageEditor.getOrCreate(self.host, self, afterEditCb)
-        Window.addWindowResizeListener(self)
-        self.host.resize()
-
-    def onWindowResized(self, width, height):
-        right = self.host.panel.menu.getAbsoluteLeft() + self.host.panel.menu.getOffsetWidth()
-        left = self.host.panel._contacts.getAbsoluteLeft() + self.host.panel._contacts.getOffsetWidth()
-        ideal_width = right - left - 40
-        self.host.richtext.setWidth("%spx" % ideal_width)
-
-
-class MessageBox(TextArea):
-    """A basic text area for entering messages"""
-
-    def __init__(self, host):
-        TextArea.__init__(self)
-        self.host = host
-        self.__size = (0, 0)
-        self.setStyleName('messageBox')
-        self.addKeyboardListener(self)
-        MouseHandler.__init__(self)
-        self.addMouseListener(self)
-        self._selected_cache = None
-
-    def onBrowserEvent(self, event):
-        # XXX: woraroung a pyjamas bug: self.currentEvent is not set
-        #     so the TextBox's cancelKey doens't work. This is a workaround
-        #     FIXME: fix the bug upstream
-        self.currentEvent = event
-        TextArea.onBrowserEvent(self, event)
-
-    def onKeyPress(self, sender, keycode, modifiers):
-        _txt = self.getText()
-
-        def history_cb(text):
-            self.setText(text)
-            Timer(5, lambda timer: self.setCursorPos(len(text)))
-
-        if keycode == KEY_ENTER:
-            if _txt:
-                self._selected_cache.onTextEntered(_txt)
-                self.host._updateInputHistory(_txt)
-            self.setText('')
-            sender.cancelKey()
-        elif keycode == KEY_UP:
-            self.host._updateInputHistory(_txt, -1, history_cb)
-        elif keycode == KEY_DOWN:
-            self.host._updateInputHistory(_txt, +1, history_cb)
-        else:
-            self.__onComposing()
-
-    def __onComposing(self):
-        """Callback when the user is composing a text."""
-        if hasattr(self._selected_cache, "target"):
-            self._selected_cache.state_machine._onEvent("composing")
-
-    def onMouseUp(self, sender, x, y):
-        size = (self.getOffsetWidth(), self.getOffsetHeight())
-        if size != self.__size:
-            self.__size = size
-            self.host.resize()
-
-    def onSelectedChange(self, selected):
-        self._selected_cache = selected
-
-
-class UniBox(MessageBox, MouseHandler): #AutoCompleteTextBox):
-    """This text box is used as a main typing point, for message, microblog, etc"""
-
-    def __init__(self, host):
-        MessageBox.__init__(self, host)
-        #AutoCompleteTextBox.__init__(self)
-        self.setStyleName('uniBox')
-        host.addSelectedListener(self.onSelectedChange)
-
-    def addKey(self, key):
-        return
-        #self.getCompletionItems().completions.append(key)
-
-    def removeKey(self, key):
-        return
-        # TODO: investigate why AutoCompleteTextBox doesn't work here,
-        # maybe it can work on a TextBox but no TextArea. Remove addKey
-        # and removeKey methods if they don't serve anymore.
-        try:
-            self.getCompletionItems().completions.remove(key)
-        except KeyError:
-            log.warning("trying to remove an unknown key")
-
-    def _getTarget(self, txt):
-        """ Say who will receive the messsage
-        @return: a tuple (selected, target_type, target info) with:
-            - target_hook: None if we use the selected widget, (msg, data) if we have a hook (e.g. "@@: " for a public blog), where msg is the parsed message (i.e. without the "hook key: "@@: bla" become ("bla", None))
-            - target_type: one of PUBLIC, GROUP, ONE2ONE, STATUS, MISC
-            - msg: HTML message which will appear in the privacy warning banner """
-        target = self._selected_cache
-
-        def getSelectedOrStatus():
-            if target and target.isSelectable():
-                _type, msg = target.getWarningData()
-                target_hook = None  # we use the selected widget, not a hook
-            else:
-                _type, msg = "STATUS", "This will be your new status message"
-                target_hook = (txt, None)
-            return (target_hook, _type, msg)
-
-        if not txt.startswith('@'):
-            target_hook, _type, msg = getSelectedOrStatus()
-        elif txt.startswith('@@: '):
-            _type = "PUBLIC"
-            msg = MicroblogPanel.warning_msg_public
-            target_hook = (txt[4:], None)
-        elif txt.startswith('@'):
-            _end = txt.find(': ')
-            if _end == -1:
-                target_hook, _type, msg = getSelectedOrStatus()
-            else:
-                group = txt[1:_end]  # only one target group is managed for the moment
-                if not group or not group in self.host.contact_panel.getGroups():
-                    # the group doesn't exists, we ignore the key
-                    group = None
-                    target_hook, _type, msg = getSelectedOrStatus()
-                else:
-                    _type = "GROUP"
-                    msg = MicroblogPanel.warning_msg_group % group
-                    target_hook = (txt[_end + 2:], group)
-        else:
-            log.error("Unknown target")
-            target_hook, _type, msg = getSelectedOrStatus()
-
-        return (target_hook, _type, msg)
-
-    def onKeyPress(self, sender, keycode, modifiers):
-        _txt = self.getText()
-        target_hook, type_, msg = self._getTarget(_txt)
-
-        if keycode == KEY_ENTER:
-            if _txt:
-                if target_hook:
-                    parsed_txt, data = target_hook
-                    self.host.send([(type_, data)], parsed_txt)
-                    self.host._updateInputHistory(_txt)
-                    self.setText('')
-            self.host.showWarning(None, None)
-        else:
-            self.host.showWarning(type_, msg)
-        MessageBox.onKeyPress(self, sender, keycode, modifiers)
-
-    def getTargetAndData(self):
-        """For external use, to get information about the (hypothetical) message
-        that would be sent if we press Enter right now in the unibox.
-        @return a tuple (target, data) with:
-          - data: what would be the content of the message (body)
-          - target: JID, group with the prefix "@" or the public entity "@@"
-        """
-        _txt = self.getText()
-        target_hook, _type, _msg = self._getTarget(_txt)
-        if target_hook:
-            data, target = target_hook
-            if target is None:
-                return target_hook
-            return (data, "@%s" % (target if target != "" else "@"))
-        if isinstance(self._selected_cache, MicroblogPanel):
-            groups = self._selected_cache.accepted_groups
-            target = "@%s" % (groups[0] if len(groups) > 0 else "@")
-            if len(groups) > 1:
-                Window.alert("Sole the first group of the selected panel is taken in consideration: '%s'" % groups[0])
-        elif isinstance(self._selected_cache, ChatPanel):
-            target = self._selected_cache.target
-        else:
-            target = None
-        return (_txt, target)
-
-    def onWidgetClosed(self, lib_wid):
-        """Called when a libervia widget is closed"""
-        if self._selected_cache == lib_wid:
-            self.onSelectedChange(None)
-
-    """def complete(self):
-
-        #self.visible=False #XXX: self.visible is not unset in pyjamas when ENTER is pressed and a completion is done
-        #XXX: fixed directly on pyjamas, if the patch is accepted, no need to walk around this
-        return AutoCompleteTextBox.complete(self)"""
-
-
-class WarningPopup():
-
-    def __init__(self):
-        self._popup = None
-        self._timer = Timer(notify=self._timeCb)
-
-    def showWarning(self, type_=None, msg=None, duration=2000):
-        """Display a popup information message, e.g. to notify the recipient of a message being composed.
-        If type_ is None, a popup being currently displayed will be hidden.
-        @type_: a type determining the CSS style to be applied (see __showWarning)
-        @msg: message to be displayed
-        """
-        if type_ is None:
-            self.__removeWarning()
-            return
-        if not self._popup:
-            self.__showWarning(type_, msg)
-        elif (type_, msg) != self._popup.target_data:
-            self._timeCb(None)  # we remove the popup
-            self.__showWarning(type_, msg)
-
-        self._timer.schedule(duration)
-
-    def __showWarning(self, type_, msg):
-        """Display a popup information message, e.g. to notify the recipient of a message being composed.
-        @type_: a type determining the CSS style to be applied. For now the defined styles are
-        "NONE" (will do nothing), "PUBLIC", "GROUP", "STATUS" and "ONE2ONE".
-        @msg: message to be displayed
-        """
-        if type_ == "NONE":
-            return
-        if not msg:
-            log.warning("no msg set uniBox warning")
-            return
-        if type_ == "PUBLIC":
-            style = "targetPublic"
-        elif type_ == "GROUP":
-            style = "targetGroup"
-        elif type_ == "STATUS":
-            style = "targetStatus"
-        elif type_ == "ONE2ONE":
-            style = "targetOne2One"
-        else:
-            log.error("unknown message type")
-            return
-        contents = HTML(msg)
-
-        self._popup = dialog.PopupPanelWrapper(autoHide=False, modal=False)
-        self._popup.target_data = (type_, msg)
-        self._popup.add(contents)
-        self._popup.setStyleName("warningPopup")
-        if style:
-            self._popup.addStyleName(style)
-
-        left = 0
-        top = 0  # max(0, self.getAbsoluteTop() - contents.getOffsetHeight() - 2)
-        self._popup.setPopupPosition(left, top)
-        self._popup.show()
-
-    def _timeCb(self, timer):
-        if self._popup:
-            self._popup.hide()
-            del self._popup
-            self._popup = None
-
-    def __removeWarning(self):
-        """Remove the popup"""
-        self._timeCb(None)
-
-
-class MicroblogItem():
-    # XXX: should be moved in a separated module
-
-    def __init__(self, data):
-        self.id = data['id']
-        self.type = data.get('type', 'main_item')
-        self.empty = data.get('new', False)
-        self.title = data.get('title', '')
-        self.title_xhtml = data.get('title_xhtml', '')
-        self.content = data.get('content', '')
-        self.content_xhtml = data.get('content_xhtml', '')
-        self.author = data['author']
-        self.updated = float(data.get('updated', 0))  # XXX: int doesn't work here
-        self.published = float(data.get('published', self.updated))  # XXX: int doesn't work here
-        self.service = data.get('service', '')
-        self.node = data.get('node', '')
-        self.comments = data.get('comments', False)
-        self.comments_service = data.get('comments_service', '')
-        self.comments_node = data.get('comments_node', '')
-
-
-class MicroblogEntry(SimplePanel, ClickHandler, FocusHandler, KeyboardHandler):
-
-    def __init__(self, blog_panel, data):
-        """
-        @param blog_panel: the parent panel
-        @param data: dict containing the blog item data, or a MicroblogItem instance.
-        """
-        self._base_item = data if isinstance(data, MicroblogItem) else MicroblogItem(data)
-        for attr in ['id', 'type', 'empty', 'title', 'title_xhtml', 'content', 'content_xhtml',
-                     'author', 'updated', 'published', 'comments', 'service', 'node',
-                     'comments_service', 'comments_node']:
-            getter = lambda attr: lambda inst: getattr(inst._base_item, attr)
-            setter = lambda attr: lambda inst, value: setattr(inst._base_item, attr, value)
-            setattr(MicroblogEntry, attr, property(getter(attr), setter(attr)))
-
-        SimplePanel.__init__(self)
-        self._blog_panel = blog_panel
-
-        self.panel = FlowPanel()
-        self.panel.setStyleName('mb_entry')
-
-        self.header = HTMLPanel('')
-        self.panel.add(self.header)
-
-        self.entry_actions = VerticalPanel()
-        self.entry_actions.setStyleName('mb_entry_actions')
-        self.panel.add(self.entry_actions)
-
-        entry_avatar = SimplePanel()
-        entry_avatar.setStyleName('mb_entry_avatar')
-        self.avatar = Image(self._blog_panel.host.getAvatar(self.author))
-        entry_avatar.add(self.avatar)
-        self.panel.add(entry_avatar)
-
-        if TOGGLE_EDITION_USE_ICON:
-            self.entry_dialog = HorizontalPanel()
-        else:
-            self.entry_dialog = VerticalPanel()
-        self.entry_dialog.setStyleName('mb_entry_dialog')
-        self.panel.add(self.entry_dialog)
-
-        self.add(self.panel)
-        ClickHandler.__init__(self)
-        self.addClickListener(self)
-
-        self.__pub_data = (self.service, self.node, self.id)
-        self.__setContent()
-
-    def __setContent(self):
-        """Actually set the entry content (header, icons, bubble...)"""
-        self.delete_label = self.update_label = self.comment_label = None
-        self.bubble = self._current_comment = None
-        self.__setHeader()
-        self.__setBubble()
-        self.__setIcons()
-
-    def __setHeader(self):
-        """Set the entry header"""
-        if self.empty:
-            return
-        update_text = u" — ✍ " + "<span class='mb_entry_timestamp'>%s</span>" % datetime.fromtimestamp(self.updated)
-        self.header.setHTML("""<div class='mb_entry_header'>
-                                   <span class='mb_entry_author'>%(author)s</span> on
-                                   <span class='mb_entry_timestamp'>%(published)s</span>%(updated)s
-                               </div>""" % {'author': html_sanitize(self.author),
-                                            'published': datetime.fromtimestamp(self.published),
-                                            'updated': update_text if self.published != self.updated else ''
-                                            }
-                            )
-
-    def __setIcons(self):
-        """Set the entry icons (delete, update, comment)"""
-        if self.empty:
-            return
-
-        def addIcon(label, title):
-            label = Label(label)
-            label.setTitle(title)
-            label.addClickListener(self)
-            self.entry_actions.add(label)
-            return label
-
-        if self.comments:
-            self.comment_label = addIcon(u"↶", "Comment this message")
-            self.comment_label.setStyleName('mb_entry_action_larger')
-        is_publisher = self.author == self._blog_panel.host.whoami.bare
-        if is_publisher:
-            self.update_label = addIcon(u"✍", "Edit this message")
-        if is_publisher or str(self.node).endswith(self._blog_panel.host.whoami.bare):
-            self.delete_label = addIcon(u"✗", "Delete this message")
-
-    def updateAvatar(self, new_avatar):
-        """Change the avatar of the entry
-        @param new_avatar: path to the new image"""
-        self.avatar.setUrl(new_avatar)
-
-    def onClick(self, sender):
-        if sender == self:
-            try:  # prevent re-selection of the main entry after a comment has been focused
-                if self.__ignoreNextEvent:
-                    self.__ignoreNextEvent = False
-                    return
-            except AttributeError:
-                pass
-            self._blog_panel.setSelectedEntry(self)
-        elif sender == self.delete_label:
-            self._delete()
-        elif sender == self.update_label:
-            self.edit(True)
-        elif sender == self.comment_label:
-            self.__ignoreNextEvent = True
-            self._comment()
-
-    def __modifiedCb(self, content):
-        """Send the new content to the backend
-        @return: False to restore the original content if a deletion has been cancelled
-        """
-        if not content['text']:  # previous content has been emptied
-            self._delete(True)
-            return False
-        extra = {'published': str(self.published)}
-        if isinstance(self.bubble, richtext.RichTextEditor):
-            # TODO: if the user change his parameters after the message edition started,
-            # the message syntax could be different then the current syntax: pass the
-            # message syntax in extra for the frontend to use it instead of current syntax.
-            extra.update({'content_rich': content['text'], 'title': content['title']})
-        if self.empty:
-            if self.type == 'main_item':
-                self._blog_panel.host.bridge.call('sendMblog', None, None, self._blog_panel.accepted_groups, content['text'], extra)
-            else:
-                self._blog_panel.host.bridge.call('sendMblogComment', None, self._parent_entry.comments, content['text'], extra)
-        else:
-            self._blog_panel.host.bridge.call('updateMblog', None, self.__pub_data, self.comments, content['text'], extra)
-        return True
-
-    def __afterEditCb(self, content):
-        """Remove the entry if it was an empty one (used for creating a new blog post).
-        Data for the actual new blog post will be received from the bridge"""
-        if self.empty:
-            self._blog_panel.removeEntry(self.type, self.id)
-            if self.type == 'main_item':  # restore the "New message" button
-                self._blog_panel.refresh()
-            else:  # allow to create a new comment
-                self._parent_entry._current_comment = None
-        self.entry_dialog.setWidth('auto')
-        try:
-            self.toggle_syntax_button.removeFromParent()
-        except TypeError:
-            pass
-
-    def __setBubble(self, edit=False):
-        """Set the bubble displaying the initial content."""
-        content = {'text': self.content_xhtml if self.content_xhtml else self.content,
-                   'title': self.title_xhtml if self.title_xhtml else self.title}
-        if self.content_xhtml:
-            content.update({'syntax': C.SYNTAX_XHTML})
-            if self.author != self._blog_panel.host.whoami.bare:
-                options = ['read_only']
-            else:
-                options = [] if self.empty else ['update_msg']
-            self.bubble = richtext.RichTextEditor(self._blog_panel.host, content, self.__modifiedCb, self.__afterEditCb, options)
-        else:  # assume raw text message have no title
-            self.bubble = LightTextEditor(content, self.__modifiedCb, self.__afterEditCb, options={'no_xhtml': True})
-        self.bubble.addStyleName("bubble")
-        try:
-            self.toggle_syntax_button.removeFromParent()
-        except TypeError:
-            pass
-        self.entry_dialog.add(self.bubble)
-        self.edit(edit)
-        self.bubble.addEditListener(self.__showWarning)
-
-    def __showWarning(self, sender, keycode):
-        if keycode == KEY_ENTER:
-            self._blog_panel.host.showWarning(None, None)
-        else:
-            self._blog_panel.host.showWarning(*self._blog_panel.getWarningData(self.type == 'comment'))
-
-    def _delete(self, empty=False):
-        """Ask confirmation for deletion.
-        @return: False if the deletion has been cancelled."""
-        def confirm_cb(answer):
-            if answer:
-                self._blog_panel.host.bridge.call('deleteMblog', None, self.__pub_data, self.comments)
-            else:  # restore the text if it has been emptied during the edition
-                self.bubble.setContent(self.bubble._original_content)
-
-        if self.empty:
-            text = _("New ") + (_("message") if self.comments else _("comment")) + _(" without body has been cancelled.")
-            dialog.InfoDialog(_("Information"), text).show()
-            return
-        text = ""
-        if empty:
-            text = (_("Message") if self.comments else _("Comment")) + _(" body has been emptied.<br/>")
-        target = _('message and all its comments') if self.comments else _('comment')
-        text += _("Do you really want to delete this %s?") % target
-        dialog.ConfirmDialog(confirm_cb, text=text).show()
-
-    def _comment(self):
-        """Add an empty entry for a new comment"""
-        if self._current_comment:
-            self._current_comment.bubble.setFocus(True)
-            self._blog_panel.setSelectedEntry(self._current_comment)
-            return
-        data = {'id': str(time()),
-                'new': True,
-                'type': 'comment',
-                'author': self._blog_panel.host.whoami.bare,
-                'service': self.comments_service,
-                'node': self.comments_node
-                }
-        entry = self._blog_panel.addEntry(data)
-        if entry is None:
-            log.info("The entry of id %s can not be commented" % self.id)
-            return
-        entry._parent_entry = self
-        self._current_comment = entry
-        self.edit(True, entry)
-        self._blog_panel.setSelectedEntry(entry)
-
-    def edit(self, edit, entry=None):
-        """Toggle the bubble between display and edit mode
-        @edit: boolean value
-        @entry: MicroblogEntry instance, or None to use self
-        """
-        if entry is None:
-            entry = self
-        try:
-            entry.toggle_syntax_button.removeFromParent()
-        except TypeError:
-            pass
-        entry.bubble.edit(edit)
-        if edit:
-            if isinstance(entry.bubble, richtext.RichTextEditor):
-                image = '<a class="richTextIcon">A</a>'
-                html = '<a style="color: blue;">raw text</a>'
-                title = _('Switch to raw text edition')
-            else:
-                image = '<img src="media/icons/tango/actions/32/format-text-italic.png" class="richTextIcon"/>'
-                html = '<a style="color: blue;">rich text</a>'
-                title = _('Switch to rich text edition')
-            if TOGGLE_EDITION_USE_ICON:
-                entry.entry_dialog.setWidth('80%')
-                entry.toggle_syntax_button = Button(image, entry.toggleContentSyntax)
-                entry.toggle_syntax_button.setTitle(title)
-                entry.entry_dialog.add(entry.toggle_syntax_button)
-            else:
-                entry.toggle_syntax_button = HTML(html)
-                entry.toggle_syntax_button.addClickListener(entry.toggleContentSyntax)
-                entry.toggle_syntax_button.addStyleName('mb_entry_toggle_syntax')
-                entry.entry_dialog.add(entry.toggle_syntax_button)
-                entry.toggle_syntax_button.setStyleAttribute('top', '-20px')  # XXX: need to force CSS
-                entry.toggle_syntax_button.setStyleAttribute('left', '-20px')
-
-    def toggleContentSyntax(self):
-        """Toggle the editor between raw and rich text"""
-        original_content = self.bubble.getOriginalContent()
-        rich = not isinstance(self.bubble, richtext.RichTextEditor)
-        if rich:
-            original_content['syntax'] = C.SYNTAX_XHTML
-
-        def setBubble(text):
-            self.content = text
-            self.content_xhtml = text if rich else ''
-            self.content_title = self.content_title_xhtml = ''
-            self.bubble.removeFromParent()
-            self.__setBubble(True)
-            self.bubble.setOriginalContent(original_content)
-            if rich:
-                self.bubble.setDisplayContent()  # needed in case the edition is aborted, to not end with an empty bubble
-
-        text = self.bubble.getContent()['text']
-        if not text:
-            setBubble(' ')  # something different than empty string is needed to initialize the rich text editor
-            return
-        if not rich:
-            def confirm_cb(answer):
-                if answer:
-                    self._blog_panel.host.bridge.call('syntaxConvert', setBubble, text, C.SYNTAX_CURRENT, C.SYNTAX_TEXT)
-            dialog.ConfirmDialog(confirm_cb, text=_("Do you really want to lose the title and text formatting?")).show()
-        else:
-            self._blog_panel.host.bridge.call('syntaxConvert', setBubble, text, C.SYNTAX_TEXT, C.SYNTAX_XHTML)
-
-
-class MicroblogPanel(base_widget.LiberviaWidget):
-    warning_msg_public = "This message will be PUBLIC and everybody will be able to see it, even people you don't know"
-    warning_msg_group = "This message will be published for all the people of the group <span class='warningTarget'>%s</span>"
-
-    def __init__(self, host, accepted_groups):
-        """Panel used to show microblog
-        @param accepted_groups: groups displayed in this panel, if empty, show all microblogs from all contacts
-        """
-        base_widget.LiberviaWidget.__init__(self, host, ", ".join(accepted_groups), selectable=True)
-        self.setAcceptedGroup(accepted_groups)
-        self.host = host
-        self.entries = {}
-        self.comments = {}
-        self.selected_entry = None
-        self.vpanel = VerticalPanel()
-        self.vpanel.setStyleName('microblogPanel')
-        self.setWidget(self.vpanel)
-
-    def refresh(self):
-        """Refresh the display of this widget. If the unibox is disabled,
-        display the 'New message' button or an empty bubble on top of the panel"""
-        if hasattr(self, 'new_button'):
-            self.new_button.setVisible(self.host.uni_box is None)
-            return
-        if self.host.uni_box is None:
-            def addBox():
-                if hasattr(self, 'new_button'):
-                    self.new_button.setVisible(False)
-                data = {'id': str(time()),
-                        'new': True,
-                        'author': self.host.whoami.bare,
-                        }
-                entry = self.addEntry(data)
-                entry.edit(True)
-            if NEW_MESSAGE_USE_BUTTON:
-                self.new_button = Button("New message", listener=addBox)
-                self.new_button.setStyleName("microblogNewButton")
-                self.vpanel.insert(self.new_button, 0)
-            else:
-                addBox()
-
-    @classmethod
-    def registerClass(cls):
-        base_widget.LiberviaWidget.addDropKey("GROUP", cls.createPanel)
-        base_widget.LiberviaWidget.addDropKey("CONTACT_TITLE", cls.createMetaPanel)
-
-    @classmethod
-    def createPanel(cls, host, item):
-        """Generic panel creation for one, several or all groups (meta).
-        @parem host: the SatWebFrontend instance
-        @param item: single group as a string, list of groups
-         (as an array) or None (for the meta group = "all groups")
-        @return: the created MicroblogPanel
-        """
-        _items = item if isinstance(item, list) else ([] if item is None else [item])
-        _type = 'ALL' if _items == [] else 'GROUP'
-        # XXX: pyjamas doesn't support use of cls directly
-        _new_panel = MicroblogPanel(host, _items)
-        host.FillMicroblogPanel(_new_panel)
-        host.bridge.call('getMassiveLastMblogs', _new_panel.massiveInsert, _type, _items, 10)
-        host.setSelected(_new_panel)
-        _new_panel.refresh()
-        return _new_panel
-
-    @classmethod
-    def createMetaPanel(cls, host, item):
-        """Needed for the drop keys to not be mixed between meta panel and panel for "Contacts" group"""
-        return MicroblogPanel.createPanel(host, None)
-
-    @property
-    def accepted_groups(self):
-        return self._accepted_groups
-
-    def matchEntity(self, entity):
-        """
-        @param entity: single group as a string, list of groups
-        (as an array) or None (for the meta group = "all groups")
-        @return: True if self matches the given entity
-        """
-        entity = entity if isinstance(entity, list) else ([] if entity is None else [entity])
-        entity.sort()  # sort() do not return the sorted list: do it here, not on the "return" line
-        return self.accepted_groups == entity
-
-    def getWarningData(self, comment=None):
-        """
-        @param comment: True if the composed message is a comment. If None, consider we are
-        composing from the unibox and guess the message type from self.selected_entry
-        @return: a couple (type, msg) for calling self.host.showWarning"""
-        if comment is None:  # composing from the unibox
-            if self.selected_entry and not self.selected_entry.comments:
-                log.error("an item without comment is selected")
-                return ("NONE", None)
-            comment = self.selected_entry is not None
-        if comment:
-            return ("PUBLIC", "This is a <span class='warningTarget'>comment</span> and keep the initial post visibility, so it is potentialy public")
-        elif not self._accepted_groups:
-            # we have a meta MicroblogPanel, we publish publicly
-            return ("PUBLIC", self.warning_msg_public)
-        else:
-            # we only accept one group at the moment
-            # FIXME: manage several groups
-            return ("GROUP", self.warning_msg_group % self._accepted_groups[0])
-
-    def onTextEntered(self, text):
-        if self.selected_entry:
-            # we are entering a comment
-            comments_url = self.selected_entry.comments
-            if not comments_url:
-                raise Exception("ERROR: the comments URL is empty")
-            target = ("COMMENT", comments_url)
-        elif not self._accepted_groups:
-            # we are entering a public microblog
-            target = ("PUBLIC", None)
-        else:
-            # we are entering a microblog restricted to a group
-            # FIXME: manage several groups
-            target = ("GROUP", self._accepted_groups[0])
-        self.host.send([target], text)
-
-    def accept_all(self):
-        return not self._accepted_groups  # we accept every microblog only if we are not filtering by groups
-
-    def getEntries(self):
-        """Ask all the entries for the currenly accepted groups,
-        and fill the panel"""
-
-    def massiveInsert(self, mblogs):
-        """Insert several microblogs at once
-        @param mblogs: dictionary of microblogs, as the result of getMassiveLastGroupBlogs
-        """
-        log.debug("Massive insertion of %d microblogs" % len(mblogs))
-        for publisher in mblogs:
-            log.debug("adding blogs for [%s]" % publisher)
-            for mblog in mblogs[publisher]:
-                if not "content" in mblog:
-                    log.warning("No content found in microblog [%s]", mblog)
-                    continue
-                self.addEntry(mblog)
-
-    def mblogsInsert(self, mblogs):
-        """ Insert several microblogs at once
-        @param mblogs: list of microblogs
-        """
-        for mblog in mblogs:
-            if not "content" in mblog:
-                log.warning("No content found in microblog [%s]", mblog)
-                continue
-            self.addEntry(mblog)
-
-    def _chronoInsert(self, vpanel, entry, reverse=True):
-        """ Insert an entry in chronological order
-        @param vpanel: VerticalPanel instance
-        @param entry: MicroblogEntry
-        @param reverse: more recent entry on top if True, chronological order else"""
-        if entry.empty:
-            entry.published = time()
-        # we look for the right index to insert our entry:
-        # if reversed, we insert the entry above the first entry
-        # in the past
-        idx = 0
-
-        for child in vpanel.children:
-            if not isinstance(child, MicroblogEntry):
-                idx += 1
-                continue
-            if reverse:
-                if child.published < entry.published:
-                    break
-            else:
-                if child.published > entry.published:
-                    break
-            idx += 1
-
-        vpanel.insert(entry, idx)
-
-    def addEntry(self, data):
-        """Add an entry to the panel
-        @param data: dict containing the item data
-        @return: the added entry, or None
-        """
-        _entry = MicroblogEntry(self, data)
-        if _entry.type == "comment":
-            comments_hash = (_entry.service, _entry.node)
-            if not comments_hash in self.comments:
-                # The comments node is not known in this panel
-                return None
-            parent = self.comments[comments_hash]
-            parent_idx = self.vpanel.getWidgetIndex(parent)
-            # we find or create the panel where the comment must be inserted
-            try:
-                sub_panel = self.vpanel.getWidget(parent_idx + 1)
-            except IndexError:
-                sub_panel = None
-            if not sub_panel or not isinstance(sub_panel, VerticalPanel):
-                sub_panel = VerticalPanel()
-                sub_panel.setStyleName('microblogPanel')
-                sub_panel.addStyleName('subPanel')
-                self.vpanel.insert(sub_panel, parent_idx + 1)
-            for idx in xrange(0, len(sub_panel.getChildren())):
-                comment = sub_panel.getIndexedChild(idx)
-                if comment.id == _entry.id:
-                    # update an existing comment
-                    sub_panel.remove(comment)
-                    sub_panel.insert(_entry, idx)
-                    return _entry
-            # we want comments to be inserted in chronological order
-            self._chronoInsert(sub_panel, _entry, reverse=False)
-            return _entry
-
-        if _entry.id in self.entries:  # update
-            idx = self.vpanel.getWidgetIndex(self.entries[_entry.id])
-            self.vpanel.remove(self.entries[_entry.id])
-            self.vpanel.insert(_entry, idx)
-        else:  # new entry
-            self._chronoInsert(self.vpanel, _entry)
-        self.entries[_entry.id] = _entry
-
-        if _entry.comments:
-            # entry has comments, we keep the comments service/node as a reference
-            comments_hash = (_entry.comments_service, _entry.comments_node)
-            self.comments[comments_hash] = _entry
-            self.host.bridge.call('getMblogComments', self.mblogsInsert, _entry.comments_service, _entry.comments_node)
-
-        return _entry
-
-    def removeEntry(self, type_, id_):
-        """Remove an entry from the panel
-        @param type_: entry type ('main_item' or 'comment')
-        @param id_: entry id
-        """
-        for child in self.vpanel.getChildren():
-            if isinstance(child, MicroblogEntry) and type_ == 'main_item':
-                if child.id == id_:
-                    main_idx = self.vpanel.getWidgetIndex(child)
-                    try:
-                        sub_panel = self.vpanel.getWidget(main_idx + 1)
-                        if isinstance(sub_panel, VerticalPanel):
-                            sub_panel.removeFromParent()
-                    except IndexError:
-                        pass
-                    child.removeFromParent()
-                    self.selected_entry = None
-                    break
-            elif isinstance(child, VerticalPanel) and type_ == 'comment':
-                for comment in child.getChildren():
-                    if comment.id == id_:
-                        comment.removeFromParent()
-                        self.selected_entry = None
-                        break
-
-    def setSelectedEntry(self, entry):
-        try:
-            self.vpanel.getParent().ensureVisible(entry)  # scroll to the clicked entry
-        except AttributeError:
-            log.warning("FIXME: MicroblogPanel.vpanel should be wrapped in a ScrollPanel!")
-        removeStyle = lambda entry: entry.removeStyleName('selected_entry')
-        if not self.host.uni_box or not entry.comments:
-            entry.addStyleName('selected_entry')  # blink the clicked entry
-            clicked_entry = entry  # entry may be None when the timer is done
-            Timer(500, lambda timer: removeStyle(clicked_entry))
-        if not self.host.uni_box:
-            return  # unibox is disabled
-        # from here the previous behavior (toggle main item selection) is conserved
-        entry = entry if entry.comments else None
-        if self.selected_entry == entry:
-            entry = None
-        if self.selected_entry:
-            removeStyle(self.selected_entry)
-        if entry:
-            log.debug("microblog entry selected (author=%s)" % entry.author)
-            entry.addStyleName('selected_entry')
-        self.selected_entry = entry
-
-    def updateValue(self, type_, jid, value):
-        """Update a jid value in entries
-        @param type_: one of 'avatar', 'nick'
-        @param jid: jid concerned
-        @param value: new value"""
-        def updateVPanel(vpanel):
-            for child in vpanel.children:
-                if isinstance(child, MicroblogEntry) and child.author == jid:
-                    child.updateAvatar(value)
-                elif isinstance(child, VerticalPanel):
-                    updateVPanel(child)
-        if type_ == 'avatar':
-            updateVPanel(self.vpanel)
-
-    def setAcceptedGroup(self, group):
-        """Add one or more group(s) which can be displayed in this panel.
-        Prevent from duplicate values and keep the list sorted.
-        @param group: string of the group, or list of string
-        """
-        if not hasattr(self, "_accepted_groups"):
-            self._accepted_groups = []
-        groups = group if isinstance(group, list) else [group]
-        for _group in groups:
-            if _group not in self._accepted_groups:
-                self._accepted_groups.append(_group)
-        self._accepted_groups.sort()
-
-    def isJidAccepted(self, jid):
-        """Tell if a jid is actepted and shown in this panel
-        @param jid: jid
-        @return: True if the jid is accepted"""
-        if self.accept_all():
-            return True
-        for group in self._accepted_groups:
-            if self.host.contact_panel.isContactInGroup(group, jid):
-                return True
-        return False
-
-
-class StatusPanel(HTMLTextEditor):
-
-    EMPTY_STATUS = '&lt;click to set a status&gt;'
-
-    def __init__(self, host, status=''):
-        self.host = host
-        modifiedCb = lambda content: self.host.bridge.call('setStatus', None, self.host.status_panel.presence, content['text']) or True
-        HTMLTextEditor.__init__(self, {'text': status}, modifiedCb, options={'no_xhtml': True, 'listen_focus': True, 'listen_click': True})
-        self.edit(False)
-        self.setStyleName('statusPanel')
-
-    @property
-    def status(self):
-        return self._original_content['text']
-
-    def __cleanContent(self, content):
-        status = content['text']
-        if status == self.EMPTY_STATUS or status in C.PRESENCE.values():
-            content['text'] = ''
-        return content
-
-    def getContent(self):
-        return self.__cleanContent(HTMLTextEditor.getContent(self))
-
-    def setContent(self, content):
-        content = self.__cleanContent(content)
-        BaseTextEditor.setContent(self, content)
-
-    def setDisplayContent(self):
-        status = self._original_content['text']
-        try:
-            presence = self.host.status_panel.presence
-        except AttributeError:  # during initialization
-            presence = None
-        if not status:
-            if presence and presence in C.PRESENCE:
-                status = C.PRESENCE[presence]
-            else:
-                status = self.EMPTY_STATUS
-        self.display.setHTML(addURLToText(status))
-
-
-class PresenceStatusPanel(HorizontalPanel, ClickHandler):
-
-    def __init__(self, host, presence="", status=""):
-        self.host = host
-        HorizontalPanel.__init__(self, Width='100%')
-        self.presence_button = Label(u"◉")
-        self.presence_button.setStyleName("presence-button")
-        self.status_panel = StatusPanel(host, status=status)
-        self.setPresence(presence)
-        entries = {}
-        for value in C.PRESENCE.keys():
-            entries.update({C.PRESENCE[value]: {"value": value}})
-
-        def callback(sender, key):
-            self.setPresence(entries[key]["value"])  # order matters
-            self.host.send([("STATUS", None)], self.status_panel.status)
-
-        self.presence_list = PopupMenuPanel(entries, callback=callback, style={"menu": "gwt-ListBox"})
-        self.presence_list.registerClickSender(self.presence_button)
-
-        panel = HorizontalPanel()
-        panel.add(self.presence_button)
-        panel.add(self.status_panel)
-        panel.setCellVerticalAlignment(self.presence_button, 'baseline')
-        panel.setCellVerticalAlignment(self.status_panel, 'baseline')
-        panel.setStyleName("marginAuto")
-        self.add(panel)
-
-        self.status_panel.edit(False)
-
-        ClickHandler.__init__(self)
-        self.addClickListener(self)
-
-    @property
-    def presence(self):
-        return self._presence
-
-    @property
-    def status(self):
-        return self.status_panel._original_content['text']
-
-    def setPresence(self, presence):
-        self._presence = presence
-        contact.setPresenceStyle(self.presence_button, self._presence)
-
-    def setStatus(self, status):
-        self.status_panel.setContent({'text': status})
-        self.status_panel.setDisplayContent()
-
-    def onClick(self, sender):
-        # As status is the default target of uniBar, we don't want to select anything if click on it
-        self.host.setSelected(None)
-
-
-class ChatPanel(base_widget.LiberviaWidget):
-
-    def __init__(self, host, target, type_='one2one'):
-        """Panel used for conversation (one 2 one or group chat)
-        @param host: SatWebFrontend instance
-        @param target: entity (JID) with who we have a conversation (contact's jid for one 2 one chat, or MUC room)
-        @param type: one2one for simple conversation, group for MUC"""
-        base_widget.LiberviaWidget.__init__(self, host, title=target.bare, selectable=True)
-        self.vpanel = VerticalPanel()
-        self.vpanel.setSize('100%', '100%')
-        self.type = type_
-        self.nick = None
-        if not target:
-            log.error("Empty target !")
-            return
-        self.target = target
-        self.__body = AbsolutePanel()
-        self.__body.setStyleName('chatPanel_body')
-        chat_area = HorizontalPanel()
-        chat_area.setStyleName('chatArea')
-        if type_ == 'group':
-            self.occupants_list = OccupantsList()
-            chat_area.add(self.occupants_list)
-        self.__body.add(chat_area)
-        self.content = AbsolutePanel()
-        self.content.setStyleName('chatContent')
-        self.content_scroll = base_widget.ScrollPanelWrapper(self.content)
-        chat_area.add(self.content_scroll)
-        chat_area.setCellWidth(self.content_scroll, '100%')
-        self.vpanel.add(self.__body)
-        self.vpanel.setCellHeight(self.__body, '100%')
-        self.addStyleName('chatPanel')
-        self.setWidget(self.vpanel)
-        self.state_machine = ChatStateMachine(self.host, str(self.target))
-        self._state = None
-
-    @classmethod
-    def registerClass(cls):
-        base_widget.LiberviaWidget.addDropKey("CONTACT", cls.createPanel)
-
-    @classmethod
-    def createPanel(cls, host, item):
-        _contact = item if isinstance(item, JID) else JID(item)
-        host.contact_panel.setContactMessageWaiting(_contact.bare, False)
-        _new_panel = ChatPanel(host, _contact)  # XXX: pyjamas doesn't seems to support creating with cls directly
-        _new_panel.historyPrint()
-        host.setSelected(_new_panel)
-        _new_panel.refresh()
-        return _new_panel
-
-    def refresh(self):
-        """Refresh the display of this widget. If the unibox is disabled,
-        add a message box at the bottom of the panel"""
-        self.host.contact_panel.setContactMessageWaiting(self.target.bare, False)
-        self.content_scroll.scrollToBottom()
-
-        enable_box = self.host.uni_box is None
-        if hasattr(self, 'message_box'):
-            self.message_box.setVisible(enable_box)
-            return
-        if enable_box:
-            self.message_box = MessageBox(self.host)
-            self.message_box.onSelectedChange(self)
-            self.vpanel.add(self.message_box)
-
-    def matchEntity(self, entity):
-        """
-        @param entity: target jid as a string or JID instance.
-        Could also be a couple with a type in the second element.
-        @return: True if self matches the given entity
-        """
-        if isinstance(entity, tuple):
-            entity, type_ = entity if len(entity) > 1 else (entity[0], self.type)
-        else:
-            type_ = self.type
-        entity = entity if isinstance(entity, JID) else JID(entity)
-        try:
-            return self.target.bare == entity.bare and self.type == type_
-        except AttributeError as e:
-            e.include_traceback()
-            return False
-
-    def getWarningData(self):
-        if self.type not in ["one2one", "group"]:
-            raise Exception("Unmanaged type !")
-        if self.type == "one2one":
-            msg = "This message will be sent to your contact <span class='warningTarget'>%s</span>" % self.target
-        elif self.type == "group":
-            msg = "This message will be sent to all the participants of the multi-user room <span class='warningTarget'>%s</span>" % self.target
-        return ("ONE2ONE" if self.type == "one2one" else "GROUP", msg)
-
-    def onTextEntered(self, text):
-        self.host.send([("groupchat" if self.type == 'group' else "chat", str(self.target))], text)
-        self.state_machine._onEvent("active")
-
-    def onQuit(self):
-        base_widget.LiberviaWidget.onQuit(self)
-        if self.type == 'group':
-            self.host.bridge.call('mucLeave', None, self.target.bare)
-
-    def setUserNick(self, nick):
-        """Set the nick of the user, usefull for e.g. change the color of the user"""
-        self.nick = nick
-
-    def setPresents(self, nicks):
-        """Set the users presents in this room
-        @param occupants: list of nicks (string)"""
-        self.occupants_list.clear()
-        for nick in nicks:
-            self.occupants_list.addOccupant(nick)
-
-    def userJoined(self, nick, data):
-        self.occupants_list.addOccupant(nick)
-        self.printInfo("=> %s has joined the room" % nick)
-
-    def userLeft(self, nick, data):
-        self.occupants_list.removeOccupant(nick)
-        self.printInfo("<= %s has left the room" % nick)
-
-    def changeUserNick(self, old_nick, new_nick):
-        assert(self.type == "group")
-        self.occupants_list.removeOccupant(old_nick)
-        self.occupants_list.addOccupant(new_nick)
-        self.printInfo(_("%(old_nick)s is now known as %(new_nick)s") % {'old_nick': old_nick, 'new_nick': new_nick})
-
-    def historyPrint(self, size=20):
-        """Print the initial history"""
-        def getHistoryCB(history):
-            # display day change
-            day_format = "%A, %d %b %Y"
-            previous_day = datetime.now().strftime(day_format)
-            for line in history:
-                timestamp, from_jid, to_jid, message, mess_type, extra = line
-                message_day = datetime.fromtimestamp(float(timestamp or time())).strftime(day_format)
-                if previous_day != message_day:
-                    self.printInfo("* " + message_day)
-                    previous_day = message_day
-                self.printMessage(from_jid, message, extra, timestamp)
-        self.host.bridge.call('getHistory', getHistoryCB, self.host.whoami.bare, self.target.bare, size, True)
-
-    def printInfo(self, msg, type_='normal', link_cb=None):
-        """Print general info
-        @param msg: message to print
-        @param type_: one of:
-            "normal": general info like "toto has joined the room"
-            "link": general info that is clickable like "click here to join the main room"
-            "me": "/me" information like "/me clenches his fist" ==> "toto clenches his fist"
-        @param link_cb: method to call when the info is clicked, ignored if type_ is not 'link'
-        """
-        _wid = HTML(msg) if type_ == 'link' else Label(msg)
-        if type_ == 'normal':
-            _wid.setStyleName('chatTextInfo')
-        elif type_ == 'link':
-            _wid.setStyleName('chatTextInfo-link')
-            if link_cb:
-                _wid.addClickListener(link_cb)
-        elif type_ == 'me':
-            _wid.setStyleName('chatTextMe')
-        else:
-            _wid.setStyleName('chatTextInfo')
-        self.content.add(_wid)
-
-    def printMessage(self, from_jid, msg, extra, timestamp=None):
-        """Print message in chat window. Must be implemented by child class"""
-        _jid = JID(from_jid)
-        nick = _jid.node if self.type == 'one2one' else _jid.resource
-        mymess = _jid.resource == self.nick if self.type == "group" else _jid.bare == self.host.whoami.bare  # mymess = True if message comes from local user
-        if msg.startswith('/me '):
-            self.printInfo('* %s %s' % (nick, msg[4:]), type_='me')
-            return
-        self.content.add(ChatText(timestamp, nick, mymess, msg, extra.get('xhtml')))
-        self.content_scroll.scrollToBottom()
-
-    def startGame(self, game_type, waiting, referee, players, *args):
-        """Configure the chat window to start a game"""
-        classes = {"Tarot": CardPanel, "RadioCol": RadioColPanel}
-        if game_type not in classes.keys():
-            return  # unknown game
-        attr = game_type.lower()
-        self.occupants_list.updateSpecials(players, SYMBOLS[attr])
-        if waiting or not self.nick in players:
-            return  # waiting for player or not playing
-        attr = "%s_panel" % attr
-        if hasattr(self, attr):
-            return
-        log.info("%s Game Started \o/" % game_type)
-        panel = classes[game_type](self, referee, self.nick, players, *args)
-        setattr(self, attr, panel)
-        self.vpanel.insert(panel, 0)
-        self.vpanel.setCellHeight(panel, panel.getHeight())
-
-    def getGame(self, game_type):
-        """Return class managing the game type"""
-        # TODO: check that the game is launched, and manage errors
-        if game_type == "Tarot":
-            return self.tarot_panel
-        elif game_type == "RadioCol":
-            return self.radiocol_panel
-
-    def setState(self, state, nick=None):
-        """Set the chat state (XEP-0085) of the contact. Leave nick to None
-        to set the state for a one2one conversation, or give a nickname or
-        C.ALL_OCCUPANTS to set the state of a participant within a MUC.
-        @param state: the new chat state
-        @param nick: None for one2one, the MUC user nick or ALL_OCCUPANTS
-        """
-        if nick:
-            assert(self.type == 'group')
-            occupants = self.occupants_list.occupants_list.keys() if nick == C.ALL_OCCUPANTS else [nick]
-            for occupant in occupants:
-                self.occupants_list.occupants_list[occupant].setState(state)
-        else:
-            assert(self.type == 'one2one')
-            self._state = state
-            self.refreshTitle()
-        self.state_machine.started = not not state  # start to send "composing" state from now
-
-    def refreshTitle(self):
-        """Refresh the title of this ChatPanel dialog"""
-        if self._state:
-            self.setTitle(self.target.bare + " (" + self._state + ")")
-        else:
-            self.setTitle(self.target.bare)
-
-
-class WebPanel(base_widget.LiberviaWidget):
-    """ (mini)browser like widget """
-
-    def __init__(self, host, url=None):
-        """
-        @param host: SatWebFrontend instance
-        """
-        base_widget.LiberviaWidget.__init__(self, host)
-        self._vpanel = VerticalPanel()
-        self._vpanel.setSize('100%', '100%')
-        self._url = dialog.ExtTextBox(enter_cb=self.onUrlClick)
-        self._url.setText(url or "")
-        self._url.setWidth('100%')
-        hpanel = HorizontalPanel()
-        hpanel.add(self._url)
-        btn = Button("Go", self.onUrlClick)
-        hpanel.setCellWidth(self._url, "100%")
-        #self.setCellWidth(btn, "10%")
-        hpanel.add(self._url)
-        hpanel.add(btn)
-        self._vpanel.add(hpanel)
-        self._vpanel.setCellHeight(hpanel, '20px')
-        self._frame = Frame(url or "")
-        self._frame.setSize('100%', '100%')
-        DOM.setStyleAttribute(self._frame.getElement(), "position", "relative")
-        self._vpanel.add(self._frame)
-        self.setWidget(self._vpanel)
-
-    def onUrlClick(self, sender):
-        self._frame.setUrl(self._url.getText())
-
-
-class MainPanel(AbsolutePanel):
-
-    def __init__(self, host):
-        self.host = host
-        AbsolutePanel.__init__(self)
-
-        # menu
-        self.menu = Menu(host)
-
-        # unibox
-        self.unibox_panel = UniBoxPanel(host)
-        self.unibox_panel.setVisible(False)
-
-        # contacts
-        self._contacts = HorizontalPanel()
-        self._contacts.addStyleName('globalLeftArea')
-        self.contacts_switch = Button(u'«', self._contactsSwitch)
-        self.contacts_switch.addStyleName('contactsSwitch')
-        self._contacts.add(self.contacts_switch)
-        self._contacts.add(self.host.contact_panel)
-
-        # tabs
-        self.tab_panel = base_widget.MainTabPanel(host)
-        self.discuss_panel = base_widget.WidgetsPanel(self.host, locked=True)
-        self.tab_panel.add(self.discuss_panel, "Discussions")
-        self.tab_panel.selectTab(0)
-
-        self.header = AbsolutePanel()
-        self.header.add(self.menu)
-        self.header.add(self.unibox_panel)
-        self.header.add(self.host.status_panel)
-        self.header.setStyleName('header')
-        self.add(self.header)
-
-        self._hpanel = HorizontalPanel()
-        self._hpanel.add(self._contacts)
-        self._hpanel.add(self.tab_panel)
-        self.add(self._hpanel)
-
-        self.setWidth("100%")
-        Window.addWindowResizeListener(self)
-
-    def _contactsSwitch(self, btn=None):
-        """ (Un)hide contacts panel """
-        if btn is None:
-            btn = self.contacts_switch
-        cpanel = self.host.contact_panel
-        cpanel.setVisible(not cpanel.getVisible())
-        btn.setText(u"«" if cpanel.getVisible() else u"»")
-        self.host.resize()
-
-    def _contactsMove(self, parent):
-        """Move the contacts container (containing the contact list and
-        the "hide/show" button) to another parent, but always as the
-        first child position (insert at index 0).
-        """
-        if self._contacts.getParent():
-            if self._contacts.getParent() == parent:
-                return
-            self._contacts.removeFromParent()
-        parent.insert(self._contacts, 0)
-
-    def onWindowResized(self, width, height):
-        _elts = doc().getElementsByClassName('gwt-TabBar')
-        if not _elts.length:
-            tab_bar_h = 0
-        else:
-            tab_bar_h = _elts.item(0).offsetHeight
-        ideal_height = Window.getClientHeight() - tab_bar_h
-        self.setHeight("%s%s" % (ideal_height, "px"))
-
-    def refresh(self):
-        """Refresh the main panel"""
-        self.unibox_panel.refresh()
--- a/browser_side/plugin_xep_0085.py	Fri May 16 11:51:10 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,82 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# SAT plugin for Chat State Notifications Protocol (xep-0085)
-# Copyright (C) 2013, 2014 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.Timer import Timer
-
-
-# Copy of the map from sat/src/plugins/plugin_xep_0085
-TRANSITIONS = {"active": {"next_state": "inactive", "delay": 120},
-               "inactive": {"next_state": "gone", "delay": 480},
-               "gone": {"next_state": "", "delay": 0},
-               "composing": {"next_state": "paused", "delay": 30},
-               "paused": {"next_state": "inactive", "delay": 450}
-               }
-
-
-class ChatStateMachine:
-    """This is an adapted version of the ChatStateMachine from sat/src/plugins/plugin_xep_0085
-    which manage a timer on the web browser and keep it synchronized with the timer that runs
-    on the backend. This is only needed to avoid calling the bridge method chatStateComposing
-    too often ; accuracy is not needed so we can ignore the delay of the communication between
-    the web browser and the backend (the timer on the web browser always starts a bit before).
-    /!\ Keep this file up to date if you modify the one in the sat plugins directory.
-    """
-    def __init__(self, host, target_s):
-
-        self.host = host
-        self.target_s = target_s
-        self.started = False
-        self.state = None
-        self.timer = None
-
-    def _onEvent(self, state):
-        """Pyjamas callback takes no extra argument so we need this trick"""
-        # Here we should check the value of the parameter "Send chat state notifications"
-        # but this costs two messages. It's even better to call chatStateComposing
-        # with a doubt, it will be checked by the back-end anyway before sending
-        # the actual notifications to the other client.
-        if state == "composing" and not self.started:
-            return
-        self.started = True
-        self.next_state = state
-        self.__onEvent(None)
-
-    def __onEvent(self, timer):
-        # print "on event %s" % self.next_state
-        state = self.next_state
-        self.next_state = ""
-        if state != self.state and state == "composing":
-            self.host.bridge.call('chatStateComposing', None, self.target_s)
-        self.state = state
-        if not self.timer is None:
-            self.timer.cancel()
-
-        if not state in TRANSITIONS:
-            return
-        if not "next_state" in TRANSITIONS[state]:
-            return
-        if not "delay" in TRANSITIONS[state]:
-            return
-        next_state = TRANSITIONS[state]["next_state"]
-        delay = TRANSITIONS[state]["delay"]
-        if next_state == "" or delay < 0:
-            return
-        self.next_state = next_state
-        # pyjamas timer in milliseconds
-        self.timer = Timer(delay * 1000, self.__onEvent)
--- a/browser_side/radiocol.py	Fri May 16 11:51:10 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,321 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Libervia: a Salut à Toi frontend
-# Copyright (C) 2011, 2012, 2013, 2014 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/>.
-
-import pyjd  # this is dummy in pyjs
-from sat.core.log import getLogger
-log = getLogger(__name__)
-from pyjamas.ui.VerticalPanel import VerticalPanel
-from pyjamas.ui.HorizontalPanel import HorizontalPanel
-from pyjamas.ui.FlexTable import FlexTable
-from pyjamas.ui.FormPanel import FormPanel
-from pyjamas.ui.Label import Label
-from pyjamas.ui.Button import Button
-from pyjamas.ui.ClickListener import ClickHandler
-from pyjamas.ui.Hidden import Hidden
-from pyjamas.ui.CaptionPanel import CaptionPanel
-from pyjamas.media.Audio import Audio
-from pyjamas import Window
-from pyjamas.Timer import Timer
-
-from html_tools import html_sanitize
-from file_tools import FilterFileUpload
-from sat_frontends.tools.misc import DEFAULT_MUC
-from sat.core.i18n import _
-
-
-class MetadataPanel(FlexTable):
-
-    def __init__(self):
-        FlexTable.__init__(self)
-        title_lbl = Label("title:")
-        title_lbl.setStyleName('radiocol_metadata_lbl')
-        artist_lbl = Label("artist:")
-        artist_lbl.setStyleName('radiocol_metadata_lbl')
-        album_lbl = Label("album:")
-        album_lbl.setStyleName('radiocol_metadata_lbl')
-        self.title = Label("")
-        self.title.setStyleName('radiocol_metadata')
-        self.artist = Label("")
-        self.artist.setStyleName('radiocol_metadata')
-        self.album = Label("")
-        self.album.setStyleName('radiocol_metadata')
-        self.setWidget(0, 0, title_lbl)
-        self.setWidget(1, 0, artist_lbl)
-        self.setWidget(2, 0, album_lbl)
-        self.setWidget(0, 1, self.title)
-        self.setWidget(1, 1, self.artist)
-        self.setWidget(2, 1, self.album)
-        self.setStyleName("radiocol_metadata_pnl")
-
-    def setTitle(self, title):
-        self.title.setText(title)
-
-    def setArtist(self, artist):
-        self.artist.setText(artist)
-
-    def setAlbum(self, album):
-        self.album.setText(album)
-
-
-class ControlPanel(FormPanel):
-    """Panel used to show controls to add a song, or vote for the current one"""
-
-    def __init__(self, parent):
-        FormPanel.__init__(self)
-        self.setEncoding(FormPanel.ENCODING_MULTIPART)
-        self.setMethod(FormPanel.METHOD_POST)
-        self.setAction("upload_radiocol")
-        self.timer_on = False
-        self._parent = parent
-        vPanel = VerticalPanel()
-
-        types = [('audio/ogg', '*.ogg', 'Ogg Vorbis Audio'),
-                 ('video/ogg', '*.ogv', 'Ogg Vorbis Video'),
-                 ('application/ogg', '*.ogx', 'Ogg Vorbis Multiplex'),
-                 ('audio/mpeg', '*.mp3', 'MPEG-Layer 3'),
-                 ('audio/mp3', '*.mp3', 'MPEG-Layer 3'),
-                 ]
-        self.file_upload = FilterFileUpload("song", 10, types)
-        vPanel.add(self.file_upload)
-
-        hPanel = HorizontalPanel()
-        self.upload_btn = Button("Upload song", getattr(self, "onBtnClick"))
-        hPanel.add(self.upload_btn)
-        self.status = Label()
-        self.updateStatus()
-        hPanel.add(self.status)
-        #We need to know the filename and the referee
-        self.filename_field = Hidden('filename', '')
-        hPanel.add(self.filename_field)
-        referee_field = Hidden('referee', self._parent.referee)
-        hPanel.add(self.filename_field)
-        hPanel.add(referee_field)
-        vPanel.add(hPanel)
-
-        self.add(vPanel)
-        self.addFormHandler(self)
-
-    def updateStatus(self):
-        if self.timer_on:
-            return
-        # TODO: the status should be different if a song is being played or not
-        queue = self._parent.getQueueSize()
-        queue_data = self._parent.queue_data
-        if queue < queue_data[0]:
-            left = queue_data[0] - queue
-            self.status.setText("[we need %d more song%s]" % (left, "s" if left > 1 else ""))
-        elif queue < queue_data[1]:
-            left = queue_data[1] - queue
-            self.status.setText("[%d available spot%s]" % (left, "s" if left > 1 else ""))
-        elif queue >= queue_data[1]:
-                self.status.setText("[The queue is currently full]")
-        self.status.setStyleName('radiocol_status')
-
-    def onBtnClick(self):
-        if self.file_upload.check():
-            self.status.setText('[Submitting, please wait...]')
-            self.filename_field.setValue(self.file_upload.getFilename())
-            if self.file_upload.getFilename().lower().endswith('.mp3'):
-                self._parent._parent.host.showWarning('STATUS', 'For a better support, it is recommended to submit Ogg Vorbis file instead of MP3. You can convert your files easily, ask for help if needed!', 5000)
-            self.submit()
-            self.file_upload.setFilename("")
-
-    def onSubmit(self, event):
-        pass
-
-    def blockUpload(self):
-        self.file_upload.setVisible(False)
-        self.upload_btn.setEnabled(False)
-
-    def unblockUpload(self):
-        self.file_upload.setVisible(True)
-        self.upload_btn.setEnabled(True)
-
-    def setTemporaryStatus(self, text, style):
-        self.status.setText(text)
-        self.status.setStyleName('radiocol_upload_status_%s' % style)
-        self.timer_on = True
-
-        def cb(timer):
-            self.timer_on = False
-            self.updateStatus()
-
-        Timer(5000, cb)
-
-    def onSubmitComplete(self, event):
-        result = event.getResults()
-        if result == "OK":
-            # the song can still be rejected (not readable, full queue...)
-            self.setTemporaryStatus('[Your song has been submitted to the radio]', "ok")
-        elif result == "KO":
-            self.setTemporaryStatus('[Something went wrong during your song upload]', "ko")
-            self._parent.radiocolSongRejected(_("The uploaded file has been rejected, only Ogg Vorbis and MP3 songs are accepted."))
-            # TODO: would be great to re-use the original Exception class and message
-            # but it is lost in the middle of the traceback and encapsulated within
-            # a DBusException instance --> extract the data from the traceback?
-        else:
-            Window.alert('Submit error: %s' % result)
-            self.status.setText('')
-
-
-class Player(Audio):
-
-    def __init__(self, player_id, metadata_panel):
-        Audio.__init__(self)
-        self._id = player_id
-        self.metadata = metadata_panel
-        self.timestamp = ""
-        self.title = ""
-        self.artist = ""
-        self.album = ""
-        self.filename = None
-        self.played = False  # True when the song is playing/has played, becomes False on preload
-        self.setAutobuffer(True)
-        self.setAutoplay(False)
-        self.setVisible(False)
-
-
-    def preload(self, timestamp, filename, title, artist, album):
-        """preload the song but doesn't play it"""
-        self.timestamp = timestamp
-        self.filename = filename
-        self.title = title
-        self.artist = artist
-        self.album = album
-        self.played = False
-        self.setSrc("radiocol/%s" % html_sanitize(filename))
-        log.debug("preloading %s in %s" % (title, self._id))
-
-    def play(self, play=True):
-        """Play or pause the song
-        @param play: set to True to play or to False to pause
-        """
-        if play:
-            self.played = True
-            self.metadata.setTitle(self.title)
-            self.metadata.setArtist(self.artist)
-            self.metadata.setAlbum(self.album)
-            Audio.play(self)
-        else:
-            self.pause()
-
-
-class RadioColPanel(HorizontalPanel, ClickHandler):
-
-    def __init__(self, parent, referee, player_nick, players, queue_data):
-        """
-        @param parent
-        @param referee
-        @param player_nick
-        @param players
-        @param queue_data: list of integers (queue to start, queue limit)
-        """
-        # We need to set it here and not in the CSS :(
-        HorizontalPanel.__init__(self, Height="90px")
-        ClickHandler.__init__(self)
-        self._parent = parent
-        self.referee = referee
-        self.queue_data = queue_data
-        self.setStyleName("radiocolPanel")
-
-        # Now we set up the layout
-        self.metadata_panel = MetadataPanel()
-        self.add(CaptionPanel("Now playing", self.metadata_panel))
-        self.playlist_panel = VerticalPanel()
-        self.add(CaptionPanel("Songs queue", self.playlist_panel))
-        self.control_panel = ControlPanel(self)
-        self.add(CaptionPanel("Controls", self.control_panel))
-
-        self.next_songs = []
-        self.players = [Player("player_%d" % i, self.metadata_panel) for i in xrange(queue_data[1] + 1)]
-        self.current_player = None
-        for player in self.players:
-            self.add(player)
-        self.addClickListener(self)
-
-        help_msg = """Accepted file formats: Ogg Vorbis (recommended), MP3.<br />
-        Please do not submit files that are protected by copyright.<br />
-        Click <a style="color: red;">here</a> if you need some support :)"""
-        link_cb = lambda: self._parent.host.bridge.call('joinMUC', None, DEFAULT_MUC, self._parent.nick)
-        self._parent.printInfo(help_msg, type_='link', link_cb=link_cb)
-
-    def pushNextSong(self, title):
-        """Add a song to the left panel's next songs queue"""
-        next_song = Label(title)
-        next_song.setStyleName("radiocol_next_song")
-        self.next_songs.append(next_song)
-        self.playlist_panel.append(next_song)
-        self.control_panel.updateStatus()
-
-    def popNextSong(self):
-        """Remove the first song of next songs list
-        should be called when the song is played"""
-        #FIXME: should check that the song we remove is the one we play
-        next_song = self.next_songs.pop(0)
-        self.playlist_panel.remove(next_song)
-        self.control_panel.updateStatus()
-
-    def getQueueSize(self):
-        return len(self.playlist_panel.getChildren())
-
-    def radiocolCheckPreload(self, timestamp):
-        for player in self.players:
-            if player.timestamp == timestamp:
-                return False
-        return True
-
-    def radiocolPreload(self, timestamp, filename, title, artist, album, sender):
-        if not self.radiocolCheckPreload(timestamp):
-            return  # song already preloaded
-        preloaded = False
-        for player in self.players:
-            if not player.filename or \
-               (player.played and player != self.current_player):
-                #if player has no file loaded, or it has already played its song
-                #we use it to preload the next one
-                player.preload(timestamp, filename, title, artist, album)
-                preloaded = True
-                break
-        if not preloaded:
-            log.warning("Can't preload song, we are getting too many songs to preload, we shouldn't have more than %d at once" % self.queue_data[1])
-        else:
-            self.pushNextSong(title)
-            self._parent.printInfo(_('%(user)s uploaded %(artist)s - %(title)s') % {'user': sender, 'artist': artist, 'title': title})
-
-    def radiocolPlay(self, filename):
-        found = False
-        for player in self.players:
-            if not found and player.filename == filename:
-                player.play()
-                self.popNextSong()
-                self.current_player = player
-                found = True
-            else:
-                player.play(False)  # in case the previous player was not sync
-        if not found:
-            log.error("Song not found in queue, can't play it. This should not happen")
-
-    def radiocolNoUpload(self):
-        self.control_panel.blockUpload()
-
-    def radiocolUploadOk(self):
-        self.control_panel.unblockUpload()
-
-    def radiocolSongRejected(self, reason):
-        Window.alert("Song rejected: %s" % reason)
--- a/browser_side/register.py	Fri May 16 11:51:10 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,249 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Libervia: a Salut à Toi frontend
-# Copyright (C) 2011, 2012, 2013, 2014 Jérôme Poisson <goffi@goffi.org>
-# Copyright (C) 2011, 2012  Adrien Vigneron <adrienvigneron@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/>.
-
-#This page manage subscription and new account creation
-import pyjd # this is dummy in pyjs
-
-from constants import Const as C
-from sat.core.i18n import _
-from pyjamas.ui.SimplePanel import SimplePanel
-from pyjamas.ui.VerticalPanel import VerticalPanel
-from pyjamas.ui.HorizontalPanel import HorizontalPanel
-from pyjamas.ui.TabPanel import TabPanel
-from pyjamas.ui.TabBar import TabBar
-from pyjamas.ui.PasswordTextBox import PasswordTextBox
-from pyjamas.ui.TextBox import TextBox
-from pyjamas.ui.FormPanel import FormPanel
-from pyjamas.ui.Button import Button
-from pyjamas.ui.Label import Label
-from pyjamas.ui.HTML import HTML
-from pyjamas.ui.PopupPanel import PopupPanel
-from pyjamas.ui.Image import Image
-from pyjamas.ui.Hidden import Hidden
-from pyjamas import Window
-from pyjamas.ui.KeyboardListener import KEY_ENTER
-import re
-from pyjamas.Timer import Timer
-
-
-class RegisterPanel(FormPanel):
-
-    def __init__(self, callback):
-        """
-        @param callback: method to call if login successful
-        """
-        FormPanel.__init__(self)
-        self.setSize('600px', '350px')
-        self.callback = callback
-        self.setMethod(FormPanel.METHOD_POST)
-        main_panel = HorizontalPanel()
-        main_panel.setStyleName('registerPanel_main')
-        left_side = Image("media/libervia/register_left.png")
-        main_panel.add(left_side)
-
-        ##TabPanel##
-        tab_bar = TabBar()
-        tab_bar.setStyleName('registerPanel_tabs')
-        self.right_side = TabPanel(tab_bar)
-        self.right_side.setStyleName('registerPanel_right_side')
-        main_panel.add(self.right_side)
-        main_panel.setCellWidth(self.right_side, '100%')
-
-        ##Login tab##
-        login_tab = SimplePanel()
-        login_tab.setStyleName('registerPanel_content')
-        login_vpanel = VerticalPanel()
-        login_tab.setWidget(login_vpanel)
-
-        self.login_warning_msg = Label('')
-        self.login_warning_msg.setVisible(False)
-        self.login_warning_msg.setStyleName('formWarning')
-        login_vpanel.add(self.login_warning_msg)
-
-        login_label = Label('Login:')
-        self.login_box = TextBox()
-        self.login_box.setName("login")
-        self.login_box.addKeyboardListener(self)
-        login_pass_label = Label('Password:')
-        self.login_pass_box = PasswordTextBox()
-        self.login_pass_box.setName("login_password")
-        self.login_pass_box.addKeyboardListener(self)
-
-        login_vpanel.add(login_label)
-        login_vpanel.add(self.login_box)
-        login_vpanel.add(login_pass_label)
-        login_vpanel.add(self.login_pass_box)
-        login_but = Button("Log in", getattr(self, "onLogin"))
-        login_but.setStyleName('button')
-        login_but.addStyleName('red')
-        login_vpanel.add(login_but)
-
-        #The hidden submit_type field
-        self.submit_type = Hidden('submit_type')
-        login_vpanel.add(self.submit_type)
-
-        ##Register tab##
-        register_tab = SimplePanel()
-        register_tab.setStyleName('registerPanel_content')
-        register_vpanel = VerticalPanel()
-        register_tab.setWidget(register_vpanel)
-
-        self.register_warning_msg = HTML('')
-        self.register_warning_msg.setVisible(False)
-        self.register_warning_msg.setStyleName('formWarning')
-        register_vpanel.add(self.register_warning_msg)
-
-        register_login_label = Label('Login:')
-        self.register_login_box = TextBox()
-        self.register_login_box.setName("register_login")
-        self.register_login_box.addKeyboardListener(self)
-        email_label = Label('E-mail:')
-        self.email_box = TextBox()
-        self.email_box.setName("email")
-        self.email_box.addKeyboardListener(self)
-        register_pass_label = Label('Password:')
-        self.register_pass_box = PasswordTextBox()
-        self.register_pass_box.setName("register_password")
-        self.register_pass_box.addKeyboardListener(self)
-        register_vpanel.add(register_login_label)
-        register_vpanel.add(self.register_login_box)
-        register_vpanel.add(email_label)
-        register_vpanel.add(self.email_box)
-        register_vpanel.add(register_pass_label)
-        register_vpanel.add(self.register_pass_box)
-
-        register_but = Button("Register", getattr(self, "onRegister"))
-        register_but.setStyleName('button')
-        register_but.addStyleName('red')
-        register_vpanel.add(register_but)
-
-        self.right_side.add(login_tab, 'Login')
-        self.right_side.add(register_tab, 'Register')
-        self.right_side.addTabListener(self)
-        self.right_side.selectTab(1)
-        login_tab.setWidth(None)
-        register_tab.setWidth(None)
-
-        self.add(main_panel)
-        self.addFormHandler(self)
-        self.setAction('register_api/login')
-
-    def onBeforeTabSelected(self, sender, tabIndex):
-        return True
-
-    def onTabSelected(self, sender, tabIndex):
-        if tabIndex == 0:
-            self.login_box.setFocus(True)
-        elif tabIndex == 1:
-            self.register_login_box.setFocus(True)
-
-    def onKeyPress(self, sender, keycode, modifiers):
-        if keycode == KEY_ENTER:
-            # Browsers offer an auto-completion feature to any
-            # text box, but the selected value is not set when
-            # the widget looses the focus. Using a timer with
-            # any delay value > 0 would do the trick.
-            if sender == self.login_box:
-                Timer(5, lambda timer: self.login_pass_box.setFocus(True))
-            elif sender == self.login_pass_box:
-                self.onLogin(None)
-            elif sender == self.register_login_box:
-                Timer(5, lambda timer: self.email_box.setFocus(True))
-            elif sender == self.email_box:
-                Timer(5, lambda timer: self.register_pass_box.setFocus(True))
-            elif sender == self.register_pass_box:
-                self.onRegister(None)
-
-    def onKeyUp(self, sender, keycode, modifiers):
-        pass
-
-    def onKeyDown(self, sender, keycode, modifiers):
-        pass
-
-    def onLogin(self, button):
-        if not re.match(r'^[a-z0-9_-]+$', self.login_box.getText(), re.IGNORECASE):
-            self.login_warning_msg.setText('Invalid login, valid characters are a-z A-Z 0-9 _ -')
-            self.login_warning_msg.setVisible(True)
-        else:
-            self.submit_type.setValue('login')
-            self.submit()
-
-    def onRegister(self, button):
-        if not re.match(r'^[a-z0-9_-]+$', self.register_login_box.getText(), re.IGNORECASE):
-            self.register_warning_msg.setHTML(_('Invalid login, valid characters<br>are a-z A-Z 0-9 _ -'))
-            self.register_warning_msg.setVisible(True)
-        elif not re.match(r'^.+@.+\..+', self.email_box.getText(), re.IGNORECASE):
-            self.register_warning_msg.setHTML(_('Invalid email address'))
-            self.register_warning_msg.setVisible(True)
-        elif len(self.register_pass_box.getText()) < C.PASSWORD_MIN_LENGTH:
-            self.register_warning_msg.setHTML(_('Your password must contain<br>at least %d characters') % C.PASSWORD_MIN_LENGTH)
-            self.register_warning_msg.setVisible(True)
-        else:
-            self.register_warning_msg.setVisible(False)
-            self.submit_type.setValue('register')
-            self.submit()
-
-    def onSubmit(self, event):
-        pass
-
-    def onSubmitComplete(self, event):
-        result = event.getResults()
-        if result == "AUTH ERROR":
-            Window.alert('Your login and/or password is incorrect. Please try again')
-        elif result == "LOGGED":
-            self.callback()
-        elif result == "SESSION_ACTIVE":
-            Window.alert('Session already active, this should not happen, please contact the author to fix it')
-        elif result == "ALREADY EXISTS":
-            self.register_warning_msg.setHTML('This login already exists,<br>please choose another one')
-            self.register_warning_msg.setVisible(True)
-        elif result == "INTERNAL":
-            self.register_warning_msg.setHTML('SERVER ERROR: something went wrong during registration process, please contact the server administrator')
-            self.register_warning_msg.setVisible(True)
-        elif result == "REGISTRATION":
-            self.login_warning_msg.setVisible(False)
-            self.register_warning_msg.setVisible(False)
-            self.login_box.setText(self.register_login_box.getText())
-            self.login_pass_box.setText('')
-            self.register_login_box.setText('')
-            self.register_pass_box.setText('')
-            self.email_box.setText('')
-            self.right_side.selectTab(0)
-            self.login_pass_box.setFocus(True)
-            Window.alert('An email has been sent to you with your login informations\nPlease remember that this is ONLY A TECHNICAL DEMO')
-        else:
-            Window.alert('Submit error: %s' % result)
-
-
-class RegisterBox(PopupPanel):
-
-    def __init__(self, callback, *args, **kwargs):
-        PopupPanel.__init__(self, *args, **kwargs)
-        self._form = RegisterPanel(callback)
-        self.setWidget(self._form)
-
-    def onWindowResized(self, width, height):
-        super(RegisterBox, self).onWindowResized(width, height)
-        self.centerBox()
-
-    def show(self):
-        super(RegisterBox, self).show()
-        self.centerBox()
-        self._form.login_box.setFocus(True)
--- a/browser_side/richtext.py	Fri May 16 11:51:10 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,536 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Libervia: a Salut à Toi frontend
-# Copyright (C) 2013, 2014 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.TextArea import TextArea
-from pyjamas.ui.Button import Button
-from pyjamas.ui.CheckBox import CheckBox
-from pyjamas.ui.DialogBox import DialogBox
-from pyjamas.ui.Label import Label
-from pyjamas.ui.HTML import HTML
-from pyjamas.ui.FlexTable import FlexTable
-from pyjamas.ui.HorizontalPanel import HorizontalPanel
-from pyjamas import Window
-from pyjamas.ui.KeyboardListener import KeyboardHandler
-from __pyjamas__ import doc
-
-from constants import Const as C
-from dialog import ConfirmDialog, InfoDialog
-from base_panels import TitlePanel, BaseTextEditor, HTMLTextEditor
-from list_manager import ListManager
-from html_tools import html_sanitize
-from browser_side import panels
-
-from sat_frontends.tools import composition
-from sat.core.i18n import _
-
-
-class RichTextEditor(BaseTextEditor, FlexTable):
-    """Panel for the rich text editor."""
-
-    def __init__(self, host, content=None, modifiedCb=None, afterEditCb=None, options=None, style=None):
-        """
-        @param host: the SatWebFrontend instance
-        @param content: dict with at least a 'text' key
-        @param modifiedCb: method to be called when the text has been modified
-        @param afterEditCb: method to be called when the edition is done
-        @param options: list of UI options (see self.readOptions)
-        """
-        self.host = host
-        self._debug = False  # TODO: don't forget to set  it False before commit
-        self.wysiwyg = False
-        self.__readOptions(options)
-        self.style = {'main': 'richTextEditor',
-                      'title': 'richTextTitle',
-                      'toolbar': 'richTextToolbar',
-                      'textarea': 'richTextArea'}
-        if isinstance(style, dict):
-            self.style.update(style)
-        self._prepareUI()
-        BaseTextEditor.__init__(self, content, None, modifiedCb, afterEditCb)
-
-    def __readOptions(self, options):
-        """Set the internal flags according to the given options."""
-        if options is None:
-            options = []
-        self.read_only = 'read_only' in options
-        self.update_msg = 'update_msg' in options
-        self.no_title = 'no_title' in options or self.read_only
-        self.no_command = 'no_command' in options or self.read_only
-
-    def _prepareUI(self, y_offset=0):
-        """Prepare the UI to host title panel, toolbar, text area...
-        @param y_offset: Y offset to start from (extra rows on top)"""
-        if not self.read_only:
-            self.title_offset = y_offset
-            self.toolbar_offset = self.title_offset + (0 if self.no_title else 1)
-            self.content_offset = self.toolbar_offset + (len(composition.RICH_SYNTAXES) if self._debug else 1)
-            self.command_offset = self.content_offset + 1
-        else:
-            self.title_offset = self.toolbar_offset = self.content_offset = y_offset
-            self.command_offset = self.content_offset + 1
-        FlexTable.__init__(self, self.command_offset + (0 if self.no_command else 1), 2)
-        self.addStyleName(self.style['main'])
-
-    def addEditListener(self, listener):
-        """Add a method to be called whenever the text is edited.
-        @param listener: method taking two arguments: sender, keycode"""
-        BaseTextEditor.addEditListener(self, listener)
-        if hasattr(self, 'display'):
-            self.display.addEditListener(listener)
-
-    def refresh(self, edit=None):
-        """Refresh the UI for edition/display mode
-        @param edit: set to True to display the edition mode"""
-        if edit is None:
-            edit = hasattr(self, 'textarea') and self.textarea.getVisible()
-
-        for widget in ['title_panel', 'command']:
-            if hasattr(self, widget):
-                getattr(self, widget).setVisible(edit)
-
-        if hasattr(self, 'toolbar'):
-            self.toolbar.setVisible(False)
-        if not hasattr(self, 'display'):
-            self.display = HTMLTextEditor(options={'enhance_display': False, 'listen_keyboard': False})  # for display mode
-            for listener in self.edit_listeners:
-                self.display.addEditListener(listener)
-        if not self.read_only and not hasattr(self, 'textarea'):
-            self.textarea = EditTextArea(self)  # for edition mode
-            self.textarea.addStyleName(self.style['textarea'])
-
-        self.getFlexCellFormatter().setColSpan(self.content_offset, 0, 2)
-        if edit and not self.wysiwyg:
-            self.textarea.setWidth('100%')  # CSS width doesn't do it, don't know why
-            self.setWidget(self.content_offset, 0, self.textarea)
-        else:
-            self.setWidget(self.content_offset, 0, self.display)
-        if not edit:
-            return
-
-        if not self.no_title and not hasattr(self, 'title_panel'):
-            self.title_panel = TitlePanel()
-            self.title_panel.addStyleName(self.style['title'])
-            self.getFlexCellFormatter().setColSpan(self.title_offset, 0, 2)
-            self.setWidget(self.title_offset, 0, self.title_panel)
-
-        if not self.no_command and not hasattr(self, 'command'):
-            self.command = HorizontalPanel()
-            self.command.addStyleName("marginAuto")
-            self.command.add(Button("Cancel", lambda: self.edit(True, True)))
-            self.command.add(Button("Update" if self.update_msg else "Send message", lambda: self.edit(False)))
-            self.getFlexCellFormatter().setColSpan(self.command_offset, 0, 2)
-            self.setWidget(self.command_offset, 0, self.command)
-
-    def setToolBar(self, syntax):
-        """This method is called asynchronously after the parameter
-        holding the rich text syntax is retrieved. It is called at
-        each call of self.edit(True) because the user may
-        have change his setting since the last time."""
-        if syntax is None or syntax not in composition.RICH_SYNTAXES.keys():
-            syntax = composition.RICH_SYNTAXES.keys()[0]
-        if hasattr(self, "toolbar") and self.toolbar.syntax == syntax:
-            self.toolbar.setVisible(True)
-            return
-        count = 0
-        for syntax in composition.RICH_SYNTAXES.keys() if self._debug else [syntax]:
-            self.toolbar = HorizontalPanel()
-            self.toolbar.syntax = syntax
-            self.toolbar.addStyleName(self.style['toolbar'])
-            for key in composition.RICH_SYNTAXES[syntax].keys():
-                self.addToolbarButton(syntax, key)
-            self.wysiwyg_button = CheckBox(_('WYSIWYG edition'))
-            wysiywgCb = lambda sender: self.setWysiwyg(sender.getChecked())
-            self.wysiwyg_button.addClickListener(wysiywgCb)
-            self.toolbar.add(self.wysiwyg_button)
-            self.syntax_label = Label(_("Syntax: %s") % syntax)
-            self.syntax_label.addStyleName("richTextSyntaxLabel")
-            self.toolbar.add(self.syntax_label)
-            self.toolbar.setCellWidth(self.syntax_label, "100%")
-            self.getFlexCellFormatter().setColSpan(self.toolbar_offset + count, 0, 2)
-            self.setWidget(self.toolbar_offset + count, 0, self.toolbar)
-            count += 1
-
-    def setWysiwyg(self, wysiwyg, init=False):
-        """Toggle the edition mode between rich content syntax and wysiwyg.
-        @param wysiwyg: boolean value
-        @param init: set to True to re-init without switching the widgets."""
-        def setWysiwyg():
-            self.wysiwyg = wysiwyg
-            try:
-                self.wysiwyg_button.setChecked(wysiwyg)
-            except TypeError:
-                pass
-            try:
-                if wysiwyg:
-                    self.syntax_label.addStyleName('transparent')
-                else:
-                    self.syntax_label.removeStyleName('transparent')
-            except TypeError:
-                pass
-            if not wysiwyg:
-                self.display.removeStyleName('richTextWysiwyg')
-
-        if init:
-            setWysiwyg()
-            return
-
-        self.getFlexCellFormatter().setColSpan(self.content_offset, 0, 2)
-        if wysiwyg:
-            def syntaxConvertCb(text):
-                self.display.setContent({'text': text})
-                self.textarea.removeFromParent()  # XXX: force as it is not always done...
-                self.setWidget(self.content_offset, 0, self.display)
-                self.display.addStyleName('richTextWysiwyg')
-                self.display.edit(True)
-            content = self.getContent()
-            if content['text'] and content['syntax'] != C.SYNTAX_XHTML:
-                self.host.bridge.call('syntaxConvert', syntaxConvertCb, content['text'], content['syntax'], C.SYNTAX_XHTML)
-            else:
-                syntaxConvertCb(content['text'])
-        else:
-            syntaxConvertCb = lambda text: self.textarea.setText(text)
-            text = self.display.getContent()['text']
-            if text and self.toolbar.syntax != C.SYNTAX_XHTML:
-                self.host.bridge.call('syntaxConvert', syntaxConvertCb, text)
-            else:
-                syntaxConvertCb(text)
-            self.setWidget(self.content_offset, 0, self.textarea)
-            self.textarea.setWidth('100%')  # CSS width doesn't do it, don't know why
-
-        setWysiwyg()  # do it in the end because it affects self.getContent
-
-    def addToolbarButton(self, syntax, key):
-        """Add a button with the defined parameters."""
-        button = Button('<img src="%s" class="richTextIcon" />' %
-                        composition.RICH_BUTTONS[key]["icon"])
-        button.setTitle(composition.RICH_BUTTONS[key]["tip"])
-        button.addStyleName('richTextToolButton')
-        self.toolbar.add(button)
-
-        def buttonCb():
-            """Generic callback for a toolbar button."""
-            text = self.textarea.getText()
-            cursor_pos = self.textarea.getCursorPos()
-            selection_length = self.textarea.getSelectionLength()
-            data = composition.RICH_SYNTAXES[syntax][key]
-            if selection_length == 0:
-                middle_text = data[1]
-            else:
-                middle_text = text[cursor_pos:cursor_pos + selection_length]
-            self.textarea.setText(text[:cursor_pos]
-                                  + data[0]
-                                  + middle_text
-                                  + data[2]
-                                  + text[cursor_pos + selection_length:])
-            self.textarea.setCursorPos(cursor_pos + len(data[0]) + len(middle_text))
-            self.textarea.setFocus(True)
-            self.textarea.onKeyDown()
-
-        def wysiwygCb():
-            """Callback for a toolbar button while wysiwyg mode is enabled."""
-            data = composition.COMMANDS[key]
-
-            def execCommand(command, arg):
-                self.display.setFocus(True)
-                doc().execCommand(command, False, arg.strip() if arg else '')
-            # use Window.prompt instead of dialog.PromptDialog to not loose the focus
-            prompt = lambda command, text: execCommand(command, Window.prompt(text))
-            if isinstance(data, tuple) or isinstance(data, list):
-                if data[1]:
-                    prompt(data[0], data[1])
-                else:
-                    execCommand(data[0], data[2])
-            else:
-                execCommand(data, False, '')
-            self.textarea.onKeyDown()
-
-        button.addClickListener(lambda: wysiwygCb() if self.wysiwyg else buttonCb())
-
-    def getContent(self):
-        assert(hasattr(self, 'textarea'))
-        assert(hasattr(self, 'toolbar'))
-        if self.wysiwyg:
-            content = {'text': self.display.getContent()['text'], 'syntax': C.SYNTAX_XHTML}
-        else:
-            content = {'text': self.strproc(self.textarea.getText()), 'syntax': self.toolbar.syntax}
-        if hasattr(self, 'title_panel'):
-            content.update({'title': self.strproc(self.title_panel.getText())})
-        return content
-
-    def edit(self, edit=False, abort=False, sync=False):
-        """
-        Remark: the editor must be visible before you call this method.
-        @param edit: set to True to edit the content or False to only display it
-        @param abort: set to True to cancel the edition and loose the changes.
-        If edit and abort are both True, self.abortEdition can be used to ask for a
-        confirmation. When edit is False and abort is True, abortion is actually done.
-        @param sync: set to True to cancel the edition after the content has been saved somewhere else
-        """
-        if not (edit and abort):
-            self.refresh(edit)  # not when we are asking for a confirmation
-        BaseTextEditor.edit(self, edit, abort, sync)  # after the UI has been refreshed
-        if (edit and abort):
-            return  # self.abortEdition is called by BaseTextEditor.edit
-        self.setWysiwyg(False, init=True)  # after BaseTextEditor (it affects self.getContent)
-        if sync:
-            return
-        # the following must NOT be done at each UI refresh!
-        content = self._original_content
-        if edit:
-            def getParamCb(syntax):
-                # set the editable text in the current user-selected syntax
-                def syntaxConvertCb(text=None):
-                    if text is not None:
-                        # Important: this also update self._original_content
-                        content.update({'text': text})
-                    content.update({'syntax': syntax})
-                    self.textarea.setText(content['text'])
-                    if hasattr(self, 'title_panel') and 'title' in content:
-                        self.title_panel.setText(content['title'])
-                        self.title_panel.setStackVisible(0, content['title'] != '')
-                    self.setToolBar(syntax)
-                if content['text'] and content['syntax'] != syntax:
-                    self.host.bridge.call('syntaxConvert', syntaxConvertCb, content['text'], content['syntax'])
-                else:
-                    syntaxConvertCb()
-            self.host.bridge.call('asyncGetParamA', getParamCb, composition.PARAM_NAME_SYNTAX, composition.PARAM_KEY_COMPOSITION)
-        else:
-            if not self.initialized:
-                # set the display text in XHTML only during init because a new MicroblogEntry instance is created after each modification
-                self.setDisplayContent()
-            self.display.edit(False)
-
-    def setDisplayContent(self):
-        """Set the content of the HTMLTextEditor which is used for display/wysiwyg"""
-        content = self._original_content
-        text = content['text']
-        if 'title' in content and content['title']:
-            text = '<h1>%s</h1>%s' % (html_sanitize(content['title']), content['text'])
-        self.display.setContent({'text': text})
-
-    def setFocus(self, focus):
-        self.textarea.setFocus(focus)
-
-    def abortEdition(self, content):
-        """Ask for confirmation before closing the dialog."""
-        def confirm_cb(answer):
-            if answer:
-                self.edit(False, True)
-        _dialog = ConfirmDialog(confirm_cb, text="Do you really want to %s?" % ("cancel your changes" if self.update_msg else "cancel this message"))
-        _dialog.cancel_button.setText(_("No"))
-        _dialog.show()
-
-
-class RichMessageEditor(RichTextEditor):
-    """Use the rich text editor for sending messages with extended addressing.
-    Recipient panels are on top and data may be synchronized from/to the unibox."""
-
-    @classmethod
-    def getOrCreate(cls, host, parent=None, callback=None):
-        """Get or create the message editor associated to that host.
-        Add it to parent if parent is not None, otherwise display it
-        in a popup dialog.
-        @param host: the host
-        @param parent: parent panel (or None to display in a popup).
-        @return: the RichTextEditor instance if parent is not None,
-        otherwise a popup DialogBox containing the RichTextEditor.
-        """
-        if not hasattr(host, 'richtext'):
-            modifiedCb = lambda content: True
-
-            def afterEditCb(content):
-                if hasattr(host.richtext, 'popup'):
-                    host.richtext.popup.hide()
-                else:
-                    host.richtext.setVisible(False)
-                callback()
-            options = ['no_title']
-            style = {'main': 'richMessageEditor', 'textarea': 'richMessageArea'}
-            host.richtext = RichMessageEditor(host, None, modifiedCb, afterEditCb, options, style)
-
-        def add(widget, parent):
-            if widget.getParent() is not None:
-                if widget.getParent() != parent:
-                    widget.removeFromParent()
-                    parent.add(widget)
-            else:
-                parent.add(widget)
-            widget.setVisible(True)
-            widget.initialized = False  # fake a new creation
-            widget.edit(True)
-
-        if parent is None:
-            if not hasattr(host.richtext, 'popup'):
-                host.richtext.popup = DialogBox(autoHide=False, centered=True)
-                host.richtext.popup.setHTML("Compose your message")
-                host.richtext.popup.add(host.richtext)
-            add(host.richtext, host.richtext.popup)
-            host.richtext.popup.center()
-        else:
-            add(host.richtext, parent)
-        return host.richtext.popup if parent is None else host.richtext
-
-    def _prepareUI(self, y_offset=0):
-        """Prepare the UI to host recipients panel, toolbar, text area...
-        @param y_offset: Y offset to start from (extra rows on top)"""
-        self.recipient_offset = y_offset
-        self.recipient_spacer_offset = self.recipient_offset + len(composition.RECIPIENT_TYPES)
-        RichTextEditor._prepareUI(self, self.recipient_spacer_offset + 1)
-
-    def refresh(self, edit=None):
-        """Refresh the UI between edition/display mode
-        @param edit: set to True to display the edition mode"""
-        if edit is None:
-            edit = hasattr(self, 'textarea') and self.textarea.getVisible()
-        RichTextEditor.refresh(self, edit)
-
-        for widget in ['recipient', 'recipient_spacer']:
-            if hasattr(self, widget):
-                getattr(self, widget).setVisible(edit)
-
-        if not edit:
-            return
-
-        if not hasattr(self, 'recipient'):
-            # recipient types sub-panels are automatically added by the manager
-            self.recipient = RecipientManager(self, self.recipient_offset)
-            self.recipient.createWidgets(title_format="%s: ")
-            self.recipient_spacer = HTML('')
-            self.recipient_spacer.setStyleName('recipientSpacer')
-            self.getFlexCellFormatter().setColSpan(self.recipient_spacer_offset, 0, 2)
-            self.setWidget(self.recipient_spacer_offset, 0, self.recipient_spacer)
-
-        if not hasattr(self, 'sync_button'):
-            self.sync_button = Button("Back to quick box", lambda: self.edit(True, sync=True))
-            self.command.insert(self.sync_button, 1)
-
-    def syncToEditor(self):
-        """Synchronize from unibox."""
-        def setContent(target, data):
-            if hasattr(self, 'recipient'):
-                self.recipient.setContacts({"To": [target]} if target else {})
-            self.setContent({'text': data if data else '', 'syntax': ''})
-            self.textarea.setText(data if data else '')
-        data, target = self.host.uni_box.getTargetAndData() if self.host.uni_box else (None, None)
-        setContent(target, data)
-
-    def __syncToUniBox(self, recipients=None, emptyText=False):
-        """Synchronize to unibox if a maximum of one recipient is set.
-        @return True if the sync could be done, False otherwise"""
-        if not self.host.uni_box:
-            return
-        setText = lambda: self.host.uni_box.setText("" if emptyText else self.getContent()['text'])
-        if not hasattr(self, 'recipient'):
-            setText()
-            return True
-        if recipients is None:
-            recipients = self.recipient.getContacts()
-        target = ""
-        # we could eventually allow more in the future
-        allowed = 1
-        for key in recipients:
-            count = len(recipients[key])
-            if count == 0:
-                continue
-            allowed -= count
-            if allowed < 0:
-                return False
-            # TODO: change this if later more then one recipients are allowed
-            target = recipients[key][0]
-        setText()
-        if target == "":
-            return True
-        if target.startswith("@"):
-            _class = panels.MicroblogPanel
-            target = None if target == "@@" else target[1:]
-        else:
-            _class = panels.ChatPanel
-        self.host.getOrCreateLiberviaWidget(_class, target)
-        return True
-
-    def syncFromEditor(self, content):
-        """Synchronize to unibox and close the dialog afterward. Display
-        a message and leave the dialog open if the sync was not possible."""
-        if self.__syncToUniBox():
-            self._afterEditCb(content)
-            return
-        InfoDialog("Too many recipients",
-                   "A message with more than one direct recipient (To)," +
-                   " or with any special recipient (Cc or Bcc), could not be" +
-                   " stored in the quick box.\n\nPlease finish your composing" +
-                   " in the rich text editor, and send your message directly" +
-                   " from here.", Width="400px").center()
-
-    def edit(self, edit=True, abort=False, sync=False):
-        if not edit and not abort and not sync:  # force sending message even when the text has not been modified
-            if not self.__sendMessage():  # message has not been sent (missing information), do nothing
-                return
-        RichTextEditor.edit(self, edit, abort, sync)
-
-    def __sendMessage(self):
-        """Send the message."""
-        recipients = self.recipient.getContacts()
-        targets = []
-        for addr in recipients:
-            for recipient in recipients[addr]:
-                if recipient.startswith("@"):
-                    targets.append(("PUBLIC", None, addr) if recipient == "@@" else ("GROUP", recipient[1:], addr))
-                else:
-                    targets.append(("chat", recipient, addr))
-        # check that we actually have a message target and data
-        content = self.getContent()
-        if content['text'] == "" or len(targets) == 0:
-            InfoDialog("Missing information",
-                       "Some information are missing and the message hasn't been sent.", Width="400px").center()
-            return None
-        self.__syncToUniBox(recipients, emptyText=True)
-        extra = {'content_rich': content['text']}
-        if hasattr(self, 'title_panel'):
-            extra.update({'title': content['title']})
-        self.host.send(targets, content['text'], extra=extra)
-        return True
-
-
-class RecipientManager(ListManager):
-    """A manager for sub-panels to set the recipients for each recipient type."""
-
-    def __init__(self, parent, y_offset=0):
-        # TODO: be sure we also display empty groups and disconnected contacts + their groups
-        # store the full list of potential recipients (groups and contacts)
-        list_ = []
-        list_.append("@@")
-        list_.extend("@%s" % group for group in parent.host.contact_panel.getGroups())
-        list_.extend(contact for contact in parent.host.contact_panel.getContacts())
-        ListManager.__init__(self, parent, composition.RECIPIENT_TYPES, list_, {'y': y_offset})
-
-        self.registerPopupMenuPanel(entries=composition.RECIPIENT_TYPES,
-                                    hide=lambda sender, key: self.__children[key]["panel"].isVisible(),
-                                    callback=self.setContactPanelVisible)
-
-
-class EditTextArea(TextArea, KeyboardHandler):
-    def __init__(self, _parent):
-        TextArea.__init__(self)
-        self._parent = _parent
-        KeyboardHandler.__init__(self)
-        self.addKeyboardListener(self)
-
-    def onKeyDown(self, sender=None, keycode=None, modifiers=None):
-        for listener in self._parent.edit_listeners:
-            listener(self, keycode)
--- a/browser_side/xmlui.py	Fri May 16 11:51:10 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,418 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Libervia: a Salut à Toi frontend
-# Copyright (C) 2011, 2012, 2013, 2014 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 sat.core.log import getLogger
-log = getLogger(__name__)
-from pyjamas.ui.VerticalPanel import VerticalPanel
-from pyjamas.ui.HorizontalPanel import HorizontalPanel
-from pyjamas.ui.TabPanel import TabPanel
-from pyjamas.ui.Grid import Grid
-from pyjamas.ui.Label import Label
-from pyjamas.ui.TextBox import TextBox
-from pyjamas.ui.PasswordTextBox import PasswordTextBox
-from pyjamas.ui.TextArea import TextArea
-from pyjamas.ui.CheckBox import CheckBox
-from pyjamas.ui.ListBox import ListBox
-from pyjamas.ui.Button import Button
-from pyjamas.ui.HTML import HTML
-from nativedom import NativeDOM
-from sat_frontends.tools import xmlui
-
-
-class EmptyWidget(xmlui.EmptyWidget, Label):
-
-    def __init__(self, parent):
-        Label.__init__(self, '')
-
-
-class TextWidget(xmlui.TextWidget, Label):
-
-    def __init__(self, parent, value):
-        Label.__init__(self, value)
-
-
-class LabelWidget(xmlui.LabelWidget, TextWidget):
-
-    def __init__(self, parent, value):
-        TextWidget.__init__(self, parent, value+": ")
-
-
-class JidWidget(xmlui.JidWidget, TextWidget):
-
-    def __init__(self, parent, value):
-        TextWidget.__init__(self, parent, value)
-
-
-class DividerWidget(xmlui.DividerWidget, HTML):
-
-    def __init__(self, parent, style='line'):
-        """Add a divider
-
-        @param parent
-        @param style (string): one of:
-                               - line: a simple line
-                               - dot: a line of dots
-                               - dash: a line of dashes
-                               - plain: a full thick line
-                               - blank: a blank line/space
-        """
-        HTML.__init__(self, "<hr/>")
-        self.addStyleName(style)
-
-
-class StringWidget(xmlui.StringWidget, TextBox):
-
-    def __init__(self, parent, value):
-        TextBox.__init__(self)
-        self.setText(value)
-
-    def _xmluiSetValue(self, value):
-        self.setText(value)
-
-    def _xmluiGetValue(self):
-        return self.getText()
-
-    def _xmluiOnChange(self, callback):
-        self.addChangeListener(callback)
-
-
-class PasswordWidget(xmlui.PasswordWidget, PasswordTextBox):
-
-    def __init__(self, parent, value):
-        PasswordTextBox.__init__(self)
-        self.setText(value)
-
-    def _xmluiSetValue(self, value):
-        self.setText(value)
-
-    def _xmluiGetValue(self):
-        return self.getText()
-
-    def _xmluiOnChange(self, callback):
-        self.addChangeListener(callback)
-
-
-class TextBoxWidget(xmlui.TextBoxWidget, TextArea):
-
-    def __init__(self, parent, value):
-        TextArea.__init__(self)
-        self.setText(value)
-
-    def _xmluiSetValue(self, value):
-        self.setText(value)
-
-    def _xmluiGetValue(self):
-        return self.getText()
-
-    def _xmluiOnChange(self, callback):
-        self.addChangeListener(callback)
-
-
-class BoolWidget(xmlui.BoolWidget, CheckBox):
-
-    def __init__(self, parent, state):
-        CheckBox.__init__(self)
-        self.setChecked(state)
-
-    def _xmluiSetValue(self, value):
-        self.setChecked(value == "true")
-
-    def _xmluiGetValue(self):
-        return "true" if self.isChecked() else "false"
-
-    def _xmluiOnChange(self, callback):
-        self.addClickListener(callback)
-
-
-class ButtonWidget(xmlui.ButtonWidget, Button):
-
-    def __init__(self, parent, value, click_callback):
-        Button.__init__(self, value, click_callback)
-
-    def _xmluiOnClick(self, callback):
-        self.addClickListener(callback)
-
-
-class ListWidget(xmlui.ListWidget, ListBox):
-
-    def __init__(self, parent, options, selected, flags):
-        ListBox.__init__(self)
-        multi_selection = 'single' not in flags
-        self.setMultipleSelect(multi_selection)
-        if multi_selection:
-            self.setVisibleItemCount(5)
-        for option in options:
-            self.addItem(option[1])
-        self._xmlui_attr_map = {label: value for value, label in options}
-        self._xmluiSelectValues(selected)
-
-    def _xmluiSelectValue(self, value):
-        """Select a value checking its item"""
-        try:
-            label = [label for label, _value in self._xmlui_attr_map.items() if _value == value][0]
-        except IndexError:
-            log.warning("Can't find value [%s] to select" % value)
-            return
-        self.selectItem(label)
-
-    def _xmluiSelectValues(self, values):
-        """Select multiple values, ignore the items"""
-        self.setValueSelection(values)
-
-    def _xmluiGetSelectedValues(self):
-        ret = []
-        for label in self.getSelectedItemText():
-            ret.append(self._xmlui_attr_map[label])
-        return ret
-
-    def _xmluiOnChange(self, callback):
-        self.addChangeListener(callback)
-
-    def _xmluiAddValues(self, values, select=True):
-        selected = self._xmluiGetSelectedValues()
-        for value in values:
-            if value not in self._xmlui_attr_map.values():
-                self.addItem(value)
-                self._xmlui_attr_map[value] = value
-            if value not in selected:
-                selected.append(value)
-        self._xmluiSelectValues(selected)
-
-
-class LiberviaContainer(object):
-
-    def _xmluiAppend(self, widget):
-        self.append(widget)
-
-
-class AdvancedListContainer(xmlui.AdvancedListContainer, Grid):
-
-    def __init__(self, parent, columns, selectable='no'):
-        Grid.__init__(self, 0, columns)
-        self.columns = columns
-        self.row = -1
-        self.col = 0
-        self._xmlui_rows_idx = []
-        self._xmlui_selectable = selectable != 'no'
-        self._xmlui_selected_row = None
-        self.addTableListener(self)
-        if self._xmlui_selectable:
-            self.addStyleName('AdvancedListSelectable')
-
-    def onCellClicked(self, grid, row, col):
-        if not self._xmlui_selectable:
-            return
-        self._xmlui_selected_row = row
-        try:
-            self._xmlui_select_cb(self)
-        except AttributeError:
-            log.warning("no select callback set")
-
-
-    def _xmluiAppend(self, widget):
-        self.setWidget(self.row, self.col, widget)
-        self.col += 1
-
-    def _xmluiAddRow(self, idx):
-        self.row += 1
-        self.col = 0
-        self._xmlui_rows_idx.insert(self.row, idx)
-        self.resizeRows(self.row+1)
-
-    def _xmluiGetSelectedWidgets(self):
-        return [self.getWidget(self._xmlui_selected_row, col) for col in range(self.columns)]
-
-    def _xmluiGetSelectedIndex(self):
-        try:
-            return self._xmlui_rows_idx[self._xmlui_selected_row]
-        except TypeError:
-            return None
-
-    def _xmluiOnSelect(self, callback):
-        self._xmlui_select_cb = callback
-
-
-class PairsContainer(xmlui.PairsContainer, Grid):
-
-    def __init__(self, parent):
-        Grid.__init__(self, 0, 0)
-        self.row = 0
-        self.col = 0
-
-    def _xmluiAppend(self, widget):
-        if self.col == 0:
-            self.resize(self.row+1, 2)
-        self.setWidget(self.row, self.col, widget)
-        self.col += 1
-        if self.col == 2:
-            self.row +=1
-            self.col = 0
-
-
-
-class TabsContainer(LiberviaContainer, xmlui.TabsContainer, TabPanel):
-
-    def __init__(self, parent):
-        TabPanel.__init__(self)
-        self.setStyleName('liberviaTabPanel')
-
-    def _xmluiAddTab(self, label):
-        tab_panel = VerticalContainer(self)
-        self.add(tab_panel, label)
-        if len(self.getChildren()) == 1:
-            self.selectTab(0)
-        return tab_panel
-
-
-class VerticalContainer(LiberviaContainer, xmlui.VerticalContainer, VerticalPanel):
-    __bases__ = (LiberviaContainer, xmlui.VerticalContainer, VerticalPanel)
-
-    def __init__(self, parent):
-        VerticalPanel.__init__(self)
-
-
-class WidgetFactory(object):
-    # XXX: __getattr__ doesn't work here for an unknown reason
-
-    def createVerticalContainer(self, *args, **kwargs):
-        instance = VerticalContainer(*args, **kwargs)
-        instance._xmlui_main = self._xmlui_main
-        return instance
-
-    def createPairsContainer(self, *args, **kwargs):
-        instance = PairsContainer(*args, **kwargs)
-        instance._xmlui_main = self._xmlui_main
-        return instance
-
-    def createTabsContainer(self, *args, **kwargs):
-        instance = TabsContainer(*args, **kwargs)
-        instance._xmlui_main = self._xmlui_main
-        return instance
-
-    def createAdvancedListContainer(self, *args, **kwargs):
-        instance = AdvancedListContainer(*args, **kwargs)
-        instance._xmlui_main = self._xmlui_main
-        return instance
-
-    def createEmptyWidget(self, *args, **kwargs):
-        instance = EmptyWidget(*args, **kwargs)
-        instance._xmlui_main = self._xmlui_main
-        return instance
-
-    def createTextWidget(self, *args, **kwargs):
-        instance = TextWidget(*args, **kwargs)
-        instance._xmlui_main = self._xmlui_main
-        return instance
-
-    def createLabelWidget(self, *args, **kwargs):
-        instance = LabelWidget(*args, **kwargs)
-        instance._xmlui_main = self._xmlui_main
-        return instance
-
-    def createJidWidget(self, *args, **kwargs):
-        instance = JidWidget(*args, **kwargs)
-        instance._xmlui_main = self._xmlui_main
-        return instance
-
-    def createDividerWidget(self, *args, **kwargs):
-        instance = DividerWidget(*args, **kwargs)
-        instance._xmlui_main = self._xmlui_main
-        return instance
-
-    def createStringWidget(self, *args, **kwargs):
-        instance = StringWidget(*args, **kwargs)
-        instance._xmlui_main = self._xmlui_main
-        return instance
-
-    def createPasswordWidget(self, *args, **kwargs):
-        instance = PasswordWidget(*args, **kwargs)
-        instance._xmlui_main = self._xmlui_main
-        return instance
-
-    def createTextBoxWidget(self, *args, **kwargs):
-        instance = TextBoxWidget(*args, **kwargs)
-        instance._xmlui_main = self._xmlui_main
-        return instance
-
-    def createBoolWidget(self, *args, **kwargs):
-        instance = BoolWidget(*args, **kwargs)
-        instance._xmlui_main = self._xmlui_main
-        return instance
-
-    def createButtonWidget(self, *args, **kwargs):
-        instance = ButtonWidget(*args, **kwargs)
-        instance._xmlui_main = self._xmlui_main
-        return instance
-
-    def createListWidget(self, *args, **kwargs):
-        instance = ListWidget(*args, **kwargs)
-        instance._xmlui_main = self._xmlui_main
-        return instance
-
-
-    # def __getattr__(self, attr):
-    #     if attr.startswith("create"):
-    #         cls = globals()[attr[6:]]
-    #         cls._xmlui_main = self._xmlui_main
-    #         return cls
-
-
-class XMLUI(xmlui.XMLUI, VerticalPanel):
-    widget_factory = WidgetFactory()
-
-    def __init__(self, host, xml_data, title = None, flags = None):
-        self.widget_factory._xmlui_main = self
-        self.dom = NativeDOM()
-        dom_parse = lambda xml_data: self.dom.parseString(xml_data)
-        VerticalPanel.__init__(self)
-        self.setSize('100%', '100%')
-        xmlui.XMLUI.__init__(self, host, xml_data, title, flags, dom_parse)
-
-    def setCloseCb(self, close_cb):
-        self.close_cb = close_cb
-
-    def _xmluiClose(self):
-        if self.close_cb:
-            self.close_cb()
-        else:
-            log.warning("no close method defined")
-
-    def _xmluiLaunchAction(self, action_id, data):
-        self.host.launchAction(action_id, data)
-
-    def _xmluiSetParam(self, name, value, category):
-        self.host.bridge.call('setParam', None, name, value, category)
-
-    def constructUI(self, xml_data):
-        super(XMLUI, self).constructUI(xml_data)
-        self.add(self.main_cont)
-        self.setCellHeight(self.main_cont, '100%')
-        if self.type == 'form':
-            hpanel = HorizontalPanel()
-            hpanel.setStyleName('marginAuto')
-            hpanel.add(Button('Submit',self.onFormSubmitted))
-            if not 'NO_CANCEL' in self.flags:
-                hpanel.add(Button('Cancel',self.onFormCancelled))
-            self.add(hpanel)
-        elif self.type == 'param':
-            assert(isinstance(self.children[0][0],TabPanel))
-            hpanel = HorizontalPanel()
-            hpanel.add(Button('Save', self.onSaveParams))
-            hpanel.add(Button('Cancel', lambda ignore: self._xmluiClose()))
-            self.add(hpanel)
--- a/constants.py	Fri May 16 11:51:10 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Primitivus: a SAT frontend
-# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 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 sat.core.i18n import D_
-from sat_frontends import constants
-
-
-class Const(constants.Const):
-
-    APP_NAME = 'Libervia'
-    SERVICE_PROFILE = 'libervia'  # the SàT profile that is used for exporting the service
-
-    TIMEOUT = 300  # Session's time out, after that the user will be disconnected
-    LIBERVIA_DIR = "output/"
-    MEDIA_DIR = "media/"
-    AVATARS_DIR = "avatars/"
-    CARDS_DIR = "games/cards/tarot"
-
-    ERRNUM_BRIDGE_ERRBACK = 0  # FIXME
-    ERRNUM_LIBERVIA = 0  # FIXME
-
-    # Security limit for Libervia (get/set params)
-    SECURITY_LIMIT = 5
-
-    # Security limit for Libervia server_side
-    SERVER_SECURITY_LIMIT = constants.Const.NO_SECURITY_LIMIT
-
-    # Frontend parameters
-    ENABLE_UNIBOX_KEY = D_("Composition")
-    ENABLE_UNIBOX_PARAM = D_("Enable unibox")
-
-    # MISC
-    PASSWORD_MIN_LENGTH = 6  # for new account creation
-    LOG_OPT_SECTION = APP_NAME.lower()
--- a/libervia.py	Fri May 16 11:51:10 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,903 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Libervia: a Salut à Toi frontend
-# Copyright (C) 2011, 2012, 2013, 2014 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/>.
-
-import pyjd  # this is dummy in pyjs
-
-### logging configuration ###
-from browser_side import logging
-logging.configure()
-from sat.core.log import getLogger
-log = getLogger(__name__)
-###
-
-from pyjamas.ui.RootPanel import RootPanel
-from pyjamas.ui.HTML import HTML
-from pyjamas.ui.KeyboardListener import KEY_ESCAPE
-from pyjamas.Timer import Timer
-from pyjamas import Window, DOM
-from pyjamas.JSONService import JSONProxy
-
-from browser_side.register import RegisterBox
-from browser_side.contact import ContactPanel
-from browser_side.base_widget import WidgetsPanel
-from browser_side.panels import MicroblogItem
-from browser_side import panels, dialog
-from browser_side.jid import JID
-from browser_side.xmlui import XMLUI
-from browser_side.html_tools import html_sanitize
-from browser_side.notification import Notification
-
-from sat_frontends.tools.misc import InputHistory
-from sat_frontends.tools.strings import getURLParams
-from sat.core.i18n import _
-from constants import Const as C
-
-
-MAX_MBLOG_CACHE = 500  # Max microblog entries kept in memories
-
-# Set to true to not create a new LiberviaWidget when a similar one
-# already exist (i.e. a chat panel with the same target). Instead
-# the existing widget will be eventually removed from its parent
-# and added to new WidgetsPanel, or replaced to the expected
-# position if the previous and the new parent are the same.
-REUSE_EXISTING_LIBERVIA_WIDGETS = True
-
-
-class LiberviaJsonProxy(JSONProxy):
-    def __init__(self, *args, **kwargs):
-        JSONProxy.__init__(self, *args, **kwargs)
-        self.handler = self
-        self.cb = {}
-        self.eb = {}
-
-    def call(self, method, cb, *args):
-        _id = self.callMethod(method, args)
-        if cb:
-            if isinstance(cb, tuple):
-                if len(cb) != 2:
-                    log.error("tuple syntax for bridge.call is (callback, errback), aborting")
-                    return
-                if cb[0] is not None:
-                    self.cb[_id] = cb[0]
-                self.eb[_id] = cb[1]
-            else:
-                self.cb[_id] = cb
-
-    def onRemoteResponse(self, response, request_info):
-        if request_info.id in self.cb:
-            _cb = self.cb[request_info.id]
-            # if isinstance(_cb, tuple):
-            #     #we have arguments attached to the callback
-            #     #we send them after the answer
-            #     callback, args = _cb
-            #     callback(response, *args)
-            # else:
-            #     #No additional argument, we call directly the callback
-            _cb(response)
-            del self.cb[request_info.id]
-            if request_info.id in self.eb:
-                del self.eb[request_info.id]
-
-    def onRemoteError(self, code, errobj, request_info):
-        """def dump(obj):
-            print "\n\nDUMPING %s\n\n" % obj
-            for i in dir(obj):
-                print "%s: %s" % (i, getattr(obj,i))"""
-        if request_info.id in self.eb:
-            _eb = self.eb[request_info.id]
-            _eb((code, errobj))
-            del self.cb[request_info.id]
-            del self.eb[request_info.id]
-        else:
-            if code != 0:
-                log.error("Internal server error")
-                """for o in code, error, request_info:
-                    dump(o)"""
-            else:
-                if isinstance(errobj['message'], dict):
-                    log.error("Error %s: %s" % (errobj['message']['faultCode'], errobj['message']['faultString']))
-                else:
-                    log.error("%s" % errobj['message'])
-
-
-class RegisterCall(LiberviaJsonProxy):
-    def __init__(self):
-        LiberviaJsonProxy.__init__(self, "/register_api",
-                        ["isRegistered", "isConnected", "connect", "registerParams", "getMenus"])
-
-
-class BridgeCall(LiberviaJsonProxy):
-    def __init__(self):
-        LiberviaJsonProxy.__init__(self, "/json_api",
-                        ["getContacts", "addContact", "sendMessage", "sendMblog", "sendMblogComment",
-                         "getLastMblogs", "getMassiveLastMblogs", "getMblogComments", "getProfileJid",
-                         "getHistory", "getPresenceStatuses", "joinMUC", "mucLeave", "getRoomsJoined",
-                         "inviteMUC", "launchTarotGame", "getTarotCardsPaths", "tarotGameReady",
-                         "tarotGamePlayCards", "launchRadioCollective", "getMblogs", "getMblogsWithComments",
-                         "getWaitingSub", "subscription", "delContact", "updateContact", "getCard",
-                         "getEntityData", "getParamsUI", "asyncGetParamA", "setParam", "launchAction",
-                         "disconnect", "chatStateComposing", "getNewAccountDomain", "confirmationAnswer",
-                         "syntaxConvert", "getAccountDialogUI",
-                        ])
-
-
-class BridgeSignals(LiberviaJsonProxy):
-    RETRY_BASE_DELAY = 1000
-
-    def __init__(self, host):
-        self.host = host
-        self.retry_delay = self.RETRY_BASE_DELAY
-        LiberviaJsonProxy.__init__(self, "/json_signal_api",
-                        ["getSignals"])
-
-    def onRemoteResponse(self, response, request_info):
-        self.retry_delay = self.RETRY_BASE_DELAY
-        LiberviaJsonProxy.onRemoteResponse(self, response, request_info)
-
-    def onRemoteError(self, code, errobj, request_info):
-        if errobj['message'] == 'Empty Response':
-            Window.getLocation().reload()  # XXX: reset page in case of session ended.
-                                           # FIXME: Should be done more properly without hard reload
-        LiberviaJsonProxy.onRemoteError(self, code, errobj, request_info)
-        #we now try to reconnect
-        if isinstance(errobj['message'], dict) and errobj['message']['faultCode'] == 0:
-            Window.alert('You are not allowed to connect to server')
-        else:
-            def _timerCb(timer):
-                self.host.bridge_signals.call('getSignals', self.host._getSignalsCB)
-            Timer(notify=_timerCb).schedule(self.retry_delay)
-            self.retry_delay *= 2
-
-
-class SatWebFrontend(InputHistory):
-    def onModuleLoad(self):
-        log.info("============ onModuleLoad ==============")
-        panels.ChatPanel.registerClass()
-        panels.MicroblogPanel.registerClass()
-        self.whoami = None
-        self._selected_listeners = set()
-        self.bridge = BridgeCall()
-        self.bridge_signals = BridgeSignals(self)
-        self.uni_box = None
-        self.status_panel = HTML('<br />')
-        self.contact_panel = ContactPanel(self)
-        self.panel = panels.MainPanel(self)
-        self.discuss_panel = self.panel.discuss_panel
-        self.tab_panel = self.panel.tab_panel
-        self.tab_panel.addTabListener(self)
-        self.libervia_widgets = set()  # keep track of all actives LiberviaWidgets
-        self.room_list = []  # list of rooms
-        self.mblog_cache = []  # used to keep our own blog entries in memory, to show them in new mblog panel
-        self.avatars_cache = {}  # keep track of jid's avatar hash (key=jid, value=file)
-        self._register_box = None
-        RootPanel().add(self.panel)
-        self.notification = Notification()
-        DOM.addEventPreview(self)
-        self._register = RegisterCall()
-        self._register.call('getMenus', self.panel.menu.createMenus)
-        self._register.call('registerParams', None)
-        self._register.call('isRegistered', self._isRegisteredCB)
-        self.initialised = False
-        self.init_cache = []  # used to cache events until initialisation is done
-        # define here the parameters that have an incidende to UI refresh
-        self.params_ui = {"unibox": {"name": C.ENABLE_UNIBOX_PARAM,
-                                     "category": C.ENABLE_UNIBOX_KEY,
-                                     "cast": lambda value: value == 'true',
-                                     "value": None
-                                     }
-                          }
-
-    def addSelectedListener(self, callback):
-        self._selected_listeners.add(callback)
-
-    def getSelected(self):
-        wid = self.tab_panel.getCurrentPanel()
-        if not isinstance(wid, WidgetsPanel):
-            log.error("Tab widget is not a WidgetsPanel, can't get selected widget")
-            return None
-        return wid.selected
-
-    def setSelected(self, widget):
-        """Define the selected widget"""
-        widgets_panel = self.tab_panel.getCurrentPanel()
-        if not isinstance(widgets_panel, WidgetsPanel):
-            return
-
-        selected = widgets_panel.selected
-
-        if selected == widget:
-            return
-
-        if selected:
-            selected.removeStyleName('selected_widget')
-
-        widgets_panel.selected = widget
-
-        if widget:
-            widgets_panel.selected.addStyleName('selected_widget')
-
-        for callback in self._selected_listeners:
-            callback(widget)
-
-    def resize(self):
-        """Resize elements"""
-        Window.onResize()
-
-    def onBeforeTabSelected(self, sender, tab_index):
-        return True
-
-    def onTabSelected(self, sender, tab_index):
-        selected = self.getSelected()
-        for callback in self._selected_listeners:
-            callback(selected)
-
-    def onEventPreview(self, event):
-        if event.type in ["keydown", "keypress", "keyup"] and event.keyCode == KEY_ESCAPE:
-            #needed to prevent request cancellation in Firefox
-            event.preventDefault()
-        return True
-
-    def getAvatar(self, jid_str):
-        """Return avatar of a jid if in cache, else ask for it"""
-        def dataReceived(result):
-            if 'avatar' in result:
-                self._entityDataUpdatedCb(jid_str, 'avatar', result['avatar'])
-            else:
-                self.bridge.call("getCard", None, jid_str)
-
-        def avatarError(error_data):
-            # The jid is maybe not in our roster, we ask for the VCard
-            self.bridge.call("getCard", None, jid_str)
-
-        if jid_str not in self.avatars_cache:
-            self.bridge.call('getEntityData', (dataReceived, avatarError), jid_str, ['avatar'])
-            self.avatars_cache[jid_str] = "/media/icons/tango/emotes/64/face-plain.png"
-        return self.avatars_cache[jid_str]
-
-    def registerWidget(self, wid):
-        log.debug("Registering %s" % wid.getDebugName())
-        self.libervia_widgets.add(wid)
-
-    def unregisterWidget(self, wid):
-        try:
-            self.libervia_widgets.remove(wid)
-        except KeyError:
-            log.warning('trying to remove a non registered Widget: %s' % wid.getDebugName())
-
-    def refresh(self):
-        """Refresh the general display."""
-        self.panel.refresh()
-        if self.params_ui['unibox']['value']:
-            self.uni_box = self.panel.unibox_panel.unibox
-        else:
-            self.uni_box = None
-        for lib_wid in self.libervia_widgets:
-            lib_wid.refresh()
-        self.resize()
-
-    def addTab(self, label, wid, select=True):
-        """Create a new tab and eventually add a widget in
-        @param label: label of the tab
-        @param wid: LiberviaWidget to add
-        @param select: True to select the added tab
-        """
-        widgets_panel = WidgetsPanel(self)
-        self.tab_panel.add(widgets_panel, label)
-        widgets_panel.addWidget(wid)
-        if select:
-            self.tab_panel.selectTab(self.tab_panel.getWidgetCount() - 1)
-        return widgets_panel
-
-    def addWidget(self, wid, tab_index=None):
-        """ Add a widget at the bottom of the current or specified tab
-        @param wid: LiberviaWidget to add
-        @param tab_index: index of the tab to add the widget to"""
-        if tab_index is None or tab_index < 0 or tab_index >= self.tab_panel.getWidgetCount():
-            panel = self.tab_panel.getCurrentPanel()
-        else:
-            panel = self.tab_panel.tabBar.getTabWidget(tab_index)
-        panel.addWidget(wid)
-
-    def displayNotification(self, title, body):
-        self.notification.notify(title, body)
-
-    def _isRegisteredCB(self, result):
-        registered, warning = result
-        if not registered:
-            self._register_box = RegisterBox(self.logged)
-            self._register_box.centerBox()
-            self._register_box.show()
-            if warning:
-                dialog.InfoDialog(_('Security warning'), warning).show()
-            self._tryAutoConnect(skip_validation=not not warning)
-        else:
-            self._register.call('isConnected', self._isConnectedCB)
-
-    def _isConnectedCB(self, connected):
-        if not connected:
-            self._register.call('connect', lambda x: self.logged())
-        else:
-            self.logged()
-
-    def logged(self):
-        if self._register_box:
-            self._register_box.hide()
-            del self._register_box  # don't work if self._register_box is None
-
-        # display the real presence status panel
-        self.panel.header.remove(self.status_panel)
-        self.status_panel = panels.PresenceStatusPanel(self)
-        self.panel.header.add(self.status_panel)
-
-        #it's time to fill the page
-        self.bridge.call('getContacts', self._getContactsCB)
-        self.bridge.call('getParamsUI', self._getParamsUICB)
-        self.bridge_signals.call('getSignals', self._getSignalsCB)
-        #We want to know our own jid
-        self.bridge.call('getProfileJid', self._getProfileJidCB)
-
-        def domain_cb(value):
-            self._defaultDomain = value
-            log.info("new account domain: %s" % value)
-
-        def domain_eb(value):
-            self._defaultDomain = "libervia.org"
-
-        self.bridge.call("getNewAccountDomain", (domain_cb, domain_eb))
-        self.discuss_panel.addWidget(panels.MicroblogPanel(self, []))
-
-        # get ui params and refresh the display
-        count = 0  # used to do something similar to DeferredList
-
-        def params_ui_cb(param, value=None):
-            count += 1
-            refresh = count == len(self.params_ui)
-            self._paramUpdate(param['name'], value, param['category'], refresh)
-        for param in self.params_ui:
-            self.bridge.call('asyncGetParamA', lambda value: params_ui_cb(self.params_ui[param], value),
-                             self.params_ui[param]['name'], self.params_ui[param]['category'])
-
-    def _tryAutoConnect(self, skip_validation=False):
-        """This method retrieve the eventual URL parameters to auto-connect the user.
-        @param skip_validation: if True, set the form values but do not validate it
-        """
-        params = getURLParams(Window.getLocation().getSearch())
-        if "login" in params:
-            self._register_box._form.login_box.setText(params["login"])
-            self._register_box._form.login_pass_box.setFocus(True)
-            if "passwd" in params:
-                # try to connect
-                self._register_box._form.login_pass_box.setText(params["passwd"])
-                if not skip_validation:
-                    self._register_box._form.onLogin(None)
-                return True
-            else:
-                # this would eventually set the browser saved password
-                Timer(5, lambda: self._register_box._form.login_pass_box.setFocus(True))
-
-    def _actionCb(self, data):
-        if not data:
-            # action was a one shot, nothing to do
-            pass
-        elif "xmlui" in data:
-            ui = XMLUI(self, xml_data = data['xmlui'])
-            options = ['NO_CLOSE'] if ui.type == 'form' else []
-            _dialog = dialog.GenericDialog(ui.title, ui, options=options)
-            ui.setCloseCb(_dialog.close)
-            _dialog.show()
-        else:
-            dialog.InfoDialog("Error",
-                              "Unmanaged action result", Width="400px").center()
-
-    def _actionEb(self, err_data):
-        err_code, err_obj = err_data
-        dialog.InfoDialog("Error",
-                          str(err_obj), Width="400px").center()
-
-    def launchAction(self, callback_id, data):
-        """ Launch a dynamic action
-        @param callback_id: id of the action to launch
-        @param data: data needed only for certain actions
-
-        """
-        if data is None:
-            data = {}
-        self.bridge.call('launchAction', (self._actionCb, self._actionEb), callback_id, data)
-
-    def _getContactsCB(self, contacts_data):
-        for contact in contacts_data:
-            jid, attributes, groups = contact
-            self._newContactCb(jid, attributes, groups)
-
-    def _getSignalsCB(self, signal_data):
-        self.bridge_signals.call('getSignals', self._getSignalsCB)
-        log.debug("Got signal ==> name: %s, params: %s" % (signal_data[0], signal_data[1]))
-        name, args = signal_data
-        if name == 'personalEvent':
-            self._personalEventCb(*args)
-        elif name == 'newMessage':
-            self._newMessageCb(*args)
-        elif name == 'presenceUpdate':
-            self._presenceUpdateCb(*args)
-        elif name == 'paramUpdate':
-            self._paramUpdate(*args)
-        elif name == 'roomJoined':
-            self._roomJoinedCb(*args)
-        elif name == 'roomLeft':
-            self._roomLeftCb(*args)
-        elif name == 'roomUserJoined':
-            self._roomUserJoinedCb(*args)
-        elif name == 'roomUserLeft':
-            self._roomUserLeftCb(*args)
-        elif name == 'roomUserChangedNick':
-            self._roomUserChangedNickCb(*args)
-        elif name == 'askConfirmation':
-            self._askConfirmation(*args)
-        elif name == 'newAlert':
-            self._newAlert(*args)
-        elif name == 'tarotGamePlayers':
-            self._tarotGameStartedCb(True, *args)
-        elif name == 'tarotGameStarted':
-            self._tarotGameStartedCb(False, *args)
-        elif name == 'tarotGameNew' or \
-             name == 'tarotGameChooseContrat' or \
-             name == 'tarotGameShowCards' or \
-             name == 'tarotGameInvalidCards' or \
-             name == 'tarotGameCardsPlayed' or \
-             name == 'tarotGameYourTurn' or \
-             name == 'tarotGameScore':
-            self._tarotGameGenericCb(name, args[0], args[1:])
-        elif name == 'radiocolPlayers':
-            self._radioColStartedCb(True, *args)
-        elif name == 'radiocolStarted':
-            self._radioColStartedCb(False, *args)
-        elif name == 'radiocolPreload':
-            self._radioColGenericCb(name, args[0], args[1:])
-        elif name == 'radiocolPlay':
-            self._radioColGenericCb(name, args[0], args[1:])
-        elif name == 'radiocolNoUpload':
-            self._radioColGenericCb(name, args[0], args[1:])
-        elif name == 'radiocolUploadOk':
-            self._radioColGenericCb(name, args[0], args[1:])
-        elif name == 'radiocolSongRejected':
-            self._radioColGenericCb(name, args[0], args[1:])
-        elif name == 'subscribe':
-            self._subscribeCb(*args)
-        elif name == 'contactDeleted':
-            self._contactDeletedCb(*args)
-        elif name == 'newContact':
-            self._newContactCb(*args)
-        elif name == 'entityDataUpdated':
-            self._entityDataUpdatedCb(*args)
-        elif name == 'chatStateReceived':
-            self._chatStateReceivedCb(*args)
-
-    def _getParamsUICB(self, xmlui):
-        """Hide the parameters item if there's nothing to display"""
-        if not xmlui:
-            self.panel.menu.removeItemParams()
-
-    def _ownBlogsFills(self, mblogs):
-        #put our own microblogs in cache, then fill all panels with them
-        for publisher in mblogs:
-            for mblog in mblogs[publisher]:
-                if not mblog.has_key('content'):
-                    log.warning("No content found in microblog [%s]" % mblog)
-                    continue
-                if mblog.has_key('groups'):
-                    _groups = set(mblog['groups'].split() if mblog['groups'] else [])
-                else:
-                    _groups = None
-                mblog_entry = MicroblogItem(mblog)
-                self.mblog_cache.append((_groups, mblog_entry))
-
-        if len(self.mblog_cache) > MAX_MBLOG_CACHE:
-            del self.mblog_cache[0:len(self.mblog_cache - MAX_MBLOG_CACHE)]
-        for lib_wid in self.libervia_widgets:
-            if isinstance(lib_wid, panels.MicroblogPanel):
-                self.FillMicroblogPanel(lib_wid)
-        self.initialised = True # initialisation phase is finished here
-        for event_data in self.init_cache: # so we have to send all the cached events
-            self._personalEventCb(*event_data)
-        del self.init_cache
-
-    def _getProfileJidCB(self, jid):
-        self.whoami = JID(jid)
-        #we can now ask our status
-        self.bridge.call('getPresenceStatuses', self._getPresenceStatusesCb)
-        #the rooms where we are
-        self.bridge.call('getRoomsJoined', self._getRoomsJoinedCb)
-        #and if there is any subscription request waiting for us
-        self.bridge.call('getWaitingSub', self._getWaitingSubCb)
-        #we fill the panels already here
-        for lib_wid in self.libervia_widgets:
-            if isinstance(lib_wid, panels.MicroblogPanel):
-                if lib_wid.accept_all():
-                    self.bridge.call('getMassiveLastMblogs', lib_wid.massiveInsert, 'ALL', [], 10)
-                else:
-                    self.bridge.call('getMassiveLastMblogs', lib_wid.massiveInsert, 'GROUP', lib_wid.accepted_groups, 10)
-
-        #we ask for our own microblogs:
-        self.bridge.call('getMassiveLastMblogs', self._ownBlogsFills, 'JID', [self.whoami.bare], 10)
-
-    ## Signals callbacks ##
-
-    def _personalEventCb(self, sender, event_type, data):
-        if not self.initialised:
-            self.init_cache.append((sender, event_type, data))
-            return
-        sender = JID(sender).bare
-        if event_type == "MICROBLOG":
-            if not 'content' in data:
-                log.warning("No content found in microblog data")
-                return
-            if 'groups' in data:
-                _groups = set(data['groups'].split() if data['groups'] else [])
-            else:
-                _groups = None
-            mblog_entry = MicroblogItem(data)
-
-            for lib_wid in self.libervia_widgets:
-                if isinstance(lib_wid, panels.MicroblogPanel):
-                    self.addBlogEntry(lib_wid, sender, _groups, mblog_entry)
-
-            if sender == self.whoami.bare:
-                found = False
-                for index in xrange(0, len(self.mblog_cache)):
-                    entry = self.mblog_cache[index]
-                    if entry[1].id == mblog_entry.id:
-                        # replace existing entry
-                        self.mblog_cache.remove(entry)
-                        self.mblog_cache.insert(index, (_groups, mblog_entry))
-                        found = True
-                        break
-                if not found:
-                    self.mblog_cache.append((_groups, mblog_entry))
-                    if len(self.mblog_cache) > MAX_MBLOG_CACHE:
-                        del self.mblog_cache[0:len(self.mblog_cache - MAX_MBLOG_CACHE)]
-        elif event_type == 'MICROBLOG_DELETE':
-            for lib_wid in self.libervia_widgets:
-                if isinstance(lib_wid, panels.MicroblogPanel):
-                    lib_wid.removeEntry(data['type'], data['id'])
-            log.debug("%s %s %s" % (self.whoami.bare, sender, data['type']))
-
-            if sender == self.whoami.bare and data['type'] == 'main_item':
-                for index in xrange(0, len(self.mblog_cache)):
-                    entry = self.mblog_cache[index]
-                    if entry[1].id == data['id']:
-                        self.mblog_cache.remove(entry)
-                        break
-
-    def addBlogEntry(self, mblog_panel, sender, _groups, mblog_entry):
-        """Check if an entry can go in MicroblogPanel and add to it
-        @param mblog_panel: MicroblogPanel instance
-        @param sender: jid of the entry sender
-        @param _groups: groups which can receive this entry
-        @param mblog_entry: MicroblogItem instance"""
-        if mblog_entry.type == "comment" or mblog_panel.isJidAccepted(sender) or (_groups == None and self.whoami and sender == self.whoami.bare) \
-           or (_groups and _groups.intersection(mblog_panel.accepted_groups)):
-            mblog_panel.addEntry(mblog_entry)
-
-    def FillMicroblogPanel(self, mblog_panel):
-        """Fill a microblog panel with entries in cache
-        @param mblog_panel: MicroblogPanel instance
-        """
-        #XXX: only our own entries are cached
-        for cache_entry in self.mblog_cache:
-            _groups, mblog_entry = cache_entry
-            self.addBlogEntry(mblog_panel, self.whoami.bare, *cache_entry)
-
-    def getEntityMBlog(self, entity):
-        log.info("geting mblog for entity [%s]" % (entity,))
-        for lib_wid in self.libervia_widgets:
-            if isinstance(lib_wid, panels.MicroblogPanel):
-                if lib_wid.isJidAccepted(entity):
-                    self.bridge.call('getMassiveLastMblogs', lib_wid.massiveInsert, 'JID', [entity], 10)
-
-    def getLiberviaWidget(self, class_, entity, ignoreOtherTabs=True):
-        """Get the corresponding panel if it exists.
-        @param class_: class of the panel (ChatPanel, MicroblogPanel...)
-        @param entity: polymorphic parameter, see class_.matchEntity.
-        @param ignoreOtherTabs: if True, the widgets that are not
-        contained by the currently selected tab will be ignored
-        @return: the existing widget that has been found or None."""
-        selected_tab = self.tab_panel.getCurrentPanel()
-        for lib_wid in self.libervia_widgets:
-            parent = lib_wid.getWidgetsPanel(verbose=False)
-            if parent is None or (ignoreOtherTabs and parent != selected_tab):
-                # do not return a widget that is not in the currently selected tab
-                continue
-            if isinstance(lib_wid, class_):
-                try:
-                    if lib_wid.matchEntity(entity):
-                        log.debug("existing widget found: %s" % lib_wid.getDebugName())
-                        return lib_wid
-                except AttributeError as e:
-                    e.stack_list()
-                    return None
-        return None
-
-    def getOrCreateLiberviaWidget(self, class_, entity, select=True, new_tab=None):
-        """Get the matching LiberviaWidget if it exists, or create a new one.
-        @param class_: class of the panel (ChatPanel, MicroblogPanel...)
-        @param entity: polymorphic parameter, see class_.matchEntity.
-        @param select: if True, select the widget that has been found or created
-        @param new_tab: if not None, a widget which is created is created in
-        a new tab. In that case new_tab is a unicode to label that new tab.
-        If new_tab is not None and a widget is found, no tab is created.
-        @return: the newly created wigdet if REUSE_EXISTING_LIBERVIA_WIDGETS
-         is set to False or if the widget has not been found, the existing
-         widget that has been found otherwise."""
-        lib_wid = None
-        tab = None
-        if REUSE_EXISTING_LIBERVIA_WIDGETS:
-            lib_wid = self.getLiberviaWidget(class_, entity, new_tab is None)
-        if lib_wid is None:  # create a new widget
-            lib_wid = class_.createPanel(self, entity[0] if isinstance(entity, tuple) else entity)
-            if new_tab is None:
-                self.addWidget(lib_wid)
-            else:
-                tab = self.addTab(new_tab, lib_wid, False)
-        else:  # reuse existing widget
-            tab = lib_wid.getWidgetsPanel(verbose=False)
-            if new_tab is None:
-                if tab is not None:
-                    tab.removeWidget(lib_wid)
-                self.addWidget(lib_wid)
-        if select:
-            if new_tab is not None:
-                self.tab_panel.selectTab(tab)
-            # must be done after the widget is added,
-            # for example to scroll to the bottom
-            self.setSelected(lib_wid)
-            lib_wid.refresh()
-        return lib_wid
-
-    def _newMessageCb(self, from_jid, msg, msg_type, to_jid, extra):
-        _from = JID(from_jid)
-        _to = JID(to_jid)
-        other = _to if _from.bare == self.whoami.bare else _from
-        lib_wid = self.getLiberviaWidget(panels.ChatPanel, other, ignoreOtherTabs=False)
-        self.displayNotification(_from, msg)
-        if lib_wid is not None:
-            lib_wid.printMessage(_from, msg, extra)
-        else:
-            # The message has not been shown, we must indicate it
-            self.contact_panel.setContactMessageWaiting(other.bare, True)
-
-    def _presenceUpdateCb(self, entity, show, priority, statuses):
-        entity_jid = JID(entity)
-        if self.whoami and self.whoami == entity_jid:  # XXX: QnD way to get our presence/status
-            self.status_panel.setPresence(show)
-            if statuses:
-                self.status_panel.setStatus(statuses.values()[0])
-        else:
-            self.contact_panel.setConnected(entity_jid.bare, entity_jid.resource, show, priority, statuses)
-
-    def _roomJoinedCb(self, room_jid, room_nicks, user_nick):
-        _target = JID(room_jid)
-        if _target not in self.room_list:
-            self.room_list.append(_target)
-        chat_panel = panels.ChatPanel(self, _target, type_='group')
-        chat_panel.setUserNick(user_nick)
-        if _target.node.startswith('sat_tarot_'): #XXX: it's not really beautiful, but it works :)
-            self.addTab("Tarot", chat_panel)
-        elif _target.node.startswith('sat_radiocol_'):
-            self.addTab("Radio collective", chat_panel)
-        else:
-            self.addTab(_target.node, chat_panel)
-        chat_panel.setPresents(room_nicks)
-        chat_panel.historyPrint()
-        chat_panel.refresh()
-
-    def _roomLeftCb(self, room_jid, room_nicks, user_nick):
-        # FIXME: room_list contains JID instances so why MUST we do
-        # 'remove(room_jid)' and not 'remove(JID(room_jid))' ????!!
-        # This looks like a pyjamas bug --> check/report
-        try:
-            self.room_list.remove(room_jid)
-        except KeyError:
-            pass
-
-    def _roomUserJoinedCb(self, room_jid_s, user_nick, user_data):
-        for lib_wid in self.libervia_widgets:
-            if isinstance(lib_wid, panels.ChatPanel) and lib_wid.type == 'group' and lib_wid.target.bare == room_jid_s:
-                lib_wid.userJoined(user_nick, user_data)
-
-    def _roomUserLeftCb(self, room_jid_s, user_nick, user_data):
-        for lib_wid in self.libervia_widgets:
-            if isinstance(lib_wid, panels.ChatPanel) and lib_wid.type == 'group' and lib_wid.target.bare == room_jid_s:
-                lib_wid.userLeft(user_nick, user_data)
-
-    def _roomUserChangedNickCb(self, room_jid_s, old_nick, new_nick):
-        """Called when an user joined a MUC room"""
-        for lib_wid in self.libervia_widgets:
-            if isinstance(lib_wid, panels.ChatPanel) and lib_wid.type == 'group' and lib_wid.target.bare == room_jid_s:
-                lib_wid.changeUserNick(old_nick, new_nick)
-
-    def _tarotGameStartedCb(self, waiting, room_jid_s, referee, players):
-        for lib_wid in self.libervia_widgets:
-            if isinstance(lib_wid, panels.ChatPanel) and lib_wid.type == 'group' and lib_wid.target.bare == room_jid_s:
-                lib_wid.startGame("Tarot", waiting, referee, players)
-
-    def _tarotGameGenericCb(self, event_name, room_jid_s, args):
-        for lib_wid in self.libervia_widgets:
-            if isinstance(lib_wid, panels.ChatPanel) and lib_wid.type == 'group' and lib_wid.target.bare == room_jid_s:
-                getattr(lib_wid.getGame("Tarot"), event_name)(*args)
-
-    def _radioColStartedCb(self, waiting, room_jid_s, referee, players, queue_data):
-        for lib_wid in self.libervia_widgets:
-            if isinstance(lib_wid, panels.ChatPanel) and lib_wid.type == 'group' and lib_wid.target.bare == room_jid_s:
-                lib_wid.startGame("RadioCol", waiting, referee, players, queue_data)
-
-    def _radioColGenericCb(self, event_name, room_jid_s, args):
-        for lib_wid in self.libervia_widgets:
-            if isinstance(lib_wid, panels.ChatPanel) and lib_wid.type == 'group' and lib_wid.target.bare == room_jid_s:
-                getattr(lib_wid.getGame("RadioCol"), event_name)(*args)
-
-    def _getPresenceStatusesCb(self, presence_data):
-        for entity in presence_data:
-            for resource in presence_data[entity]:
-                args = presence_data[entity][resource]
-                self._presenceUpdateCb("%s/%s" % (entity, resource), *args)
-
-    def _getRoomsJoinedCb(self, room_data):
-        for room in room_data:
-            self._roomJoinedCb(*room)
-
-    def _getWaitingSubCb(self, waiting_sub):
-        for sub in waiting_sub:
-            self._subscribeCb(waiting_sub[sub], sub)
-
-    def _subscribeCb(self, sub_type, entity):
-        if sub_type == 'subscribed':
-            dialog.InfoDialog('Subscription confirmation', 'The contact <b>%s</b> has added you to his/her contact list' % html_sanitize(entity)).show()
-            self.getEntityMBlog(entity)
-
-        elif sub_type == 'unsubscribed':
-            dialog.InfoDialog('Subscription refusal', 'The contact <b>%s</b> has refused to add you in his/her contact list' % html_sanitize(entity)).show()
-            #TODO: remove microblogs from panels
-
-        elif sub_type == 'subscribe':
-            #The user want to subscribe to our presence
-            _dialog = None
-            msg = HTML('The contact <b>%s</b> want to add you in his/her contact list, do you accept ?' % html_sanitize(entity))
-
-            def ok_cb(ignore):
-                self.bridge.call('subscription', None, "subscribed", entity, '', _dialog.getSelectedGroups())
-            def cancel_cb(ignore):
-                self.bridge.call('subscription', None, "unsubscribed", entity, '', '')
-
-            _dialog = dialog.GroupSelector([msg], self.contact_panel.getGroups(), [], "Add", ok_cb, cancel_cb)
-            _dialog.setHTML('<b>Add contact request</b>')
-            _dialog.show()
-
-    def _contactDeletedCb(self, entity):
-        self.contact_panel.removeContact(entity)
-
-    def _newContactCb(self, contact, attributes, groups):
-        self.contact_panel.updateContact(contact, attributes, groups)
-
-    def _entityDataUpdatedCb(self, entity_jid_s, key, value):
-        if key == "avatar":
-            avatar = '/avatars/%s' % value
-
-            self.avatars_cache[entity_jid_s] = avatar
-
-            for lib_wid in self.libervia_widgets:
-                if isinstance(lib_wid, panels.MicroblogPanel):
-                    if lib_wid.isJidAccepted(entity_jid_s) or (self.whoami and entity_jid_s == self.whoami.bare):
-                        lib_wid.updateValue('avatar', entity_jid_s, avatar)
-
-    def _chatStateReceivedCb(self, from_jid_s, state):
-        """Callback when a new chat state is received.
-        @param from_jid_s: JID of the contact who sent his state, or '@ALL@'
-        @param state: new state (string)
-        """
-        if from_jid_s == '@ALL@':
-            target = '@ALL@'
-            nick = C.ALL_OCCUPANTS
-        else:
-            from_jid = JID(from_jid_s)
-            target = from_jid.bare
-            nick = from_jid.resource
-
-        for lib_wid in self.libervia_widgets:
-            if isinstance(lib_wid, panels.ChatPanel):
-                if target == '@ALL' or target == lib_wid.target.bare:
-                    if lib_wid.type == 'one2one':
-                        lib_wid.setState(state)
-                    elif lib_wid.type == 'group':
-                        lib_wid.setState(state, nick=nick)
-
-    def _askConfirmation(self, confirmation_id, confirmation_type, data):
-        answer_data = {}
-
-        def confirm_cb(result):
-            self.bridge.call('confirmationAnswer', None, confirmation_id, result, answer_data)
-
-        if confirmation_type == "YES/NO":
-            dialog.ConfirmDialog(confirm_cb, text=data["message"], title=data["title"]).show()
-
-    def _newAlert(self, message, title, alert_type):
-        dialog.InfoDialog(title, message).show()
-
-    def _paramUpdate(self, name, value, category, refresh=True):
-        """This is called when the paramUpdate signal is received, but also
-        during initialization when the UI parameters values are retrieved.
-        @param refresh: set to True to refresh the general UI
-        """
-        for param in self.params_ui:
-            if name == self.params_ui[param]['name']:
-                self.params_ui[param]['value'] = self.params_ui[param]['cast'](value)
-                if refresh:
-                    self.refresh()
-                break
-
-    def sendError(self, errorData):
-        dialog.InfoDialog("Error while sending message",
-                          "Your message can't be sent", Width="400px").center()
-        log.error("sendError: %s" % str(errorData))
-
-    def send(self, targets, text, extra={}):
-        """Send a message to any target type.
-        @param targets: list of tuples (type, entities, addr) with:
-        - type in ("PUBLIC", "GROUP", "COMMENT", "STATUS" , "groupchat" , "chat")
-        - entities could be a JID, a list groups, a node hash... depending the target
-        - addr in ("To", "Cc", "Bcc") - ignore case
-        @param text: the message content
-        @param extra: options
-        """
-        # FIXME: too many magic strings, we should use constants instead
-        addresses = []
-        for target in targets:
-            type_, entities, addr = target[0], target[1], 'to' if len(target) < 3 else target[2].lower()
-            if type_ in ("PUBLIC", "GROUP"):
-                self.bridge.call("sendMblog", None, type_, entities if type_ == "GROUP" else None, text, extra)
-            elif type_ == "COMMENT":
-                self.bridge.call("sendMblogComment", None, entities, text, extra)
-            elif type_ == "STATUS":
-                self.bridge.call('setStatus', None, self.status_panel.presence, text)
-            elif type_ in ("groupchat", "chat"):
-                addresses.append((addr, entities))
-            else:
-                log.error("Unknown target type")
-        if addresses:
-            if len(addresses) == 1 and addresses[0][0] == 'to':
-                self.bridge.call('sendMessage', (None, self.sendError), addresses[0][1], text, '', type_, extra)
-            else:
-                extra.update({'address': '\n'.join([('%s:%s' % entry) for entry in addresses])})
-                self.bridge.call('sendMessage', (None, self.sendError), self.whoami.domain, text, '', type_, extra)
-
-    def showWarning(self, type_=None, msg=None):
-        """Display a popup information message, e.g. to notify the recipient of a message being composed.
-        If type_ is None, a popup being currently displayed will be hidden.
-        @type_: a type determining the CSS style to be applied (see WarningPopup.showWarning)
-        @msg: message to be displayed
-        """
-        if not hasattr(self, "warning_popup"):
-            self.warning_popup = panels.WarningPopup()
-        self.warning_popup.showWarning(type_, msg)
-
-
-if __name__ == '__main__':
-    pyjd.setup("http://localhost:8080/libervia.html")
-    app = SatWebFrontend()
-    app.onModuleLoad()
-    pyjd.run()
--- a/libervia_server/__init__.py	Fri May 16 11:51:10 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1142 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Libervia: a Salut à Toi frontend
-# Copyright (C) 2011, 2012, 2013, 2014 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 twisted.application import service
-from twisted.internet import glib2reactor
-glib2reactor.install()
-from twisted.internet import reactor, defer
-from twisted.web import server
-from twisted.web.static import File
-from twisted.web.resource import Resource, NoResource
-from twisted.web.util import Redirect, redirectTo
-from twisted.python.components import registerAdapter
-from twisted.python.failure import Failure
-from twisted.words.protocols.jabber.jid import JID
-from txjsonrpc.web import jsonrpc
-from txjsonrpc import jsonrpclib
-
-from sat.core.log import getLogger
-log = getLogger(__name__)
-import re
-import glob
-import os.path
-import sys
-import tempfile
-import shutil
-import uuid
-from zope.interface import Interface, Attribute, implements
-from xml.dom import minidom
-from httplib import HTTPS_PORT
-
-from constants import Const as C
-from libervia_server.blog import MicroBlog
-from sat_frontends.bridge.DBus import DBusBridgeFrontend, BridgeExceptionNoService
-from sat.core.i18n import _, D_
-from sat.tools.xml_tools import paramsXML2XMLUI
-try:
-    import OpenSSL
-    from twisted.internet import ssl
-    ssl_available = True
-except:
-    ssl_available = False
-
-
-class ISATSession(Interface):
-    profile = Attribute("Sat profile")
-    jid = Attribute("JID associated with the profile")
-
-class SATSession(object):
-    implements(ISATSession)
-    def __init__(self, session):
-        self.profile = None
-        self.jid = None
-
-class LiberviaSession(server.Session):
-    sessionTimeout = C.TIMEOUT
-
-    def __init__(self, *args, **kwargs):
-        self.__lock = False
-        server.Session.__init__(self, *args, **kwargs)
-
-    def lock(self):
-        """Prevent session from expiring"""
-        self.__lock = True
-        self._expireCall.reset(sys.maxint)
-
-    def unlock(self):
-        """Allow session to expire again, and touch it"""
-        self.__lock = False
-        self.touch()
-
-    def touch(self):
-        if not self.__lock:
-            server.Session.touch(self)
-
-class ProtectedFile(File):
-    """A File class which doens't show directory listing"""
-
-    def directoryListing(self):
-        return NoResource()
-
-class SATActionIDHandler(object):
-    """Manage SàT action action_id lifecycle"""
-    ID_LIFETIME = 30 #after this time (in seconds), action_id will be suppressed and action result will be ignored
-
-    def __init__(self):
-        self.waiting_ids = {}
-
-    def waitForId(self, callback, action_id, profile, *args, **kwargs):
-        """Wait for an action result
-        @param callback: method to call when action gave a result back
-        @param action_id: action_id to wait for
-        @param profile: %(doc_profile)s
-        @param *args: additional argument to pass to callback
-        @param **kwargs: idem"""
-        action_tuple = (action_id, profile)
-        self.waiting_ids[action_tuple] = (callback, args, kwargs)
-        reactor.callLater(self.ID_LIFETIME, self.purgeID, action_tuple)
-
-    def purgeID(self, action_tuple):
-        """Called when an action_id has not be handled in time"""
-        if action_tuple in self.waiting_ids:
-            log.warning ("action of action_id %s [%s] has not been managed, action_id is now ignored" % action_tuple)
-            del self.waiting_ids[action_tuple]
-
-    def actionResultCb(self, answer_type, action_id, data, profile):
-        """Manage the actionResult signal"""
-        action_tuple = (action_id, profile)
-        if action_tuple in self.waiting_ids:
-            callback, args, kwargs = self.waiting_ids[action_tuple]
-            del self.waiting_ids[action_tuple]
-            callback(answer_type, action_id, data, *args, **kwargs)
-
-class JSONRPCMethodManager(jsonrpc.JSONRPC):
-
-    def __init__(self, sat_host):
-        jsonrpc.JSONRPC.__init__(self)
-        self.sat_host=sat_host
-
-    def asyncBridgeCall(self, method_name, *args, **kwargs):
-        """Call an asynchrone bridge method and return a deferred
-        @param method_name: name of the method as a unicode
-        @return: a deferred which trigger the result
-
-        """
-        d = defer.Deferred()
-
-        def _callback(*args):
-            if not args:
-                d.callback(None)
-            else:
-                if len(args) != 1:
-                    Exception("Multiple return arguments not supported")
-                d.callback(args[0])
-
-        def _errback(result):
-            d.errback(Failure(jsonrpclib.Fault(C.ERRNUM_BRIDGE_ERRBACK, unicode(result))))
-
-        kwargs["callback"] = _callback
-        kwargs["errback"] = _errback
-        getattr(self.sat_host.bridge, method_name)(*args, **kwargs)
-        return d
-
-
-class MethodHandler(JSONRPCMethodManager):
-
-    def __init__(self, sat_host):
-        JSONRPCMethodManager.__init__(self, sat_host)
-        self.authorized_params = None
-
-    def render(self, request):
-        self.session = request.getSession()
-        profile = ISATSession(self.session).profile
-        if not profile:
-            #user is not identified, we return a jsonrpc fault
-            parsed = jsonrpclib.loads(request.content.read())
-            fault = jsonrpclib.Fault(C.ERRNUM_LIBERVIA, "Not allowed") #FIXME: define some standard error codes for libervia
-            return jsonrpc.JSONRPC._cbRender(self, fault, request, parsed.get('id'), parsed.get('jsonrpc'))
-        return jsonrpc.JSONRPC.render(self, request)
-
-    def jsonrpc_getProfileJid(self):
-        """Return the jid of the profile"""
-        sat_session = ISATSession(self.session)
-        profile = sat_session.profile
-        sat_session.jid = JID(self.sat_host.bridge.getParamA("JabberID", "Connection", profile_key=profile))
-        return sat_session.jid.full()
-
-    def jsonrpc_disconnect(self):
-        """Disconnect the profile"""
-        sat_session = ISATSession(self.session)
-        profile = sat_session.profile
-        self.sat_host.bridge.disconnect(profile)
-
-    def jsonrpc_getContacts(self):
-        """Return all passed args."""
-        profile = ISATSession(self.session).profile
-        return self.sat_host.bridge.getContacts(profile)
-
-    def jsonrpc_addContact(self, entity, name, groups):
-        """Subscribe to contact presence, and add it to the given groups"""
-        profile = ISATSession(self.session).profile
-        self.sat_host.bridge.addContact(entity, profile)
-        self.sat_host.bridge.updateContact(entity, name, groups, profile)
-
-    def jsonrpc_delContact(self, entity):
-        """Remove contact from contacts list"""
-        profile = ISATSession(self.session).profile
-        self.sat_host.bridge.delContact(entity, profile)
-
-    def jsonrpc_updateContact(self, entity, name, groups):
-        """Update contact's roster item"""
-        profile = ISATSession(self.session).profile
-        self.sat_host.bridge.updateContact(entity, name, groups, profile)
-
-    def jsonrpc_subscription(self, sub_type, entity, name, groups):
-        """Confirm (or infirm) subscription,
-        and setup user roster in case of subscription"""
-        profile = ISATSession(self.session).profile
-        self.sat_host.bridge.subscription(sub_type, entity, profile)
-        if sub_type == 'subscribed':
-            self.sat_host.bridge.updateContact(entity, name, groups, profile)
-
-    def jsonrpc_getWaitingSub(self):
-        """Return list of room already joined by user"""
-        profile = ISATSession(self.session).profile
-        return self.sat_host.bridge.getWaitingSub(profile)
-
-    def jsonrpc_setStatus(self, presence, status):
-        """Change the presence and/or status
-        @param presence: value from ("", "chat", "away", "dnd", "xa")
-        @param status: any string to describe your status
-        """
-        profile = ISATSession(self.session).profile
-        self.sat_host.bridge.setPresence('', presence, {'': status}, profile)
-
-
-    def jsonrpc_sendMessage(self, to_jid, msg, subject, type_, options={}):
-        """send message"""
-        profile = ISATSession(self.session).profile
-        return self.asyncBridgeCall("sendMessage", to_jid, msg, subject, type_, options, profile)
-
-    def jsonrpc_sendMblog(self, type_, dest, text, extra={}):
-        """ Send microblog message
-        @param type_: one of "PUBLIC", "GROUP"
-        @param dest: destinees (list of groups, ignored for "PUBLIC")
-        @param text: microblog's text
-        """
-        profile = ISATSession(self.session).profile
-        extra['allow_comments'] = 'True'
-
-        if not type_:  # auto-detect
-            type_ = "PUBLIC" if dest == [] else "GROUP"
-
-        if type_ in ("PUBLIC", "GROUP") and text:
-            if type_ == "PUBLIC":
-                #This text if for the public microblog
-                print "sending public blog"
-                return self.sat_host.bridge.sendGroupBlog("PUBLIC", [], text, extra, profile)
-            else:
-                print "sending group blog"
-                dest = dest if isinstance(dest, list) else [dest]
-                return self.sat_host.bridge.sendGroupBlog("GROUP", dest, text, extra, profile)
-        else:
-            raise Exception("Invalid data")
-
-    def jsonrpc_deleteMblog(self, pub_data, comments):
-        """Delete a microblog node
-        @param pub_data: a tuple (service, comment node identifier, item identifier)
-        @param comments: comments node identifier (for main item) or False
-        """
-        profile = ISATSession(self.session).profile
-        return self.sat_host.bridge.deleteGroupBlog(pub_data, comments if comments else '', profile)
-
-    def jsonrpc_updateMblog(self, pub_data, comments, message, extra={}):
-        """Modify a microblog node
-        @param pub_data: a tuple (service, comment node identifier, item identifier)
-        @param comments: comments node identifier (for main item) or False
-        @param message: new message
-        @param extra: dict which option name as key, which can be:
-            - allow_comments: True to accept an other level of comments, False else (default: False)
-            - rich: if present, contain rich text in currently selected syntax
-        """
-        profile = ISATSession(self.session).profile
-        if comments:
-            extra['allow_comments'] = 'True'
-        return self.sat_host.bridge.updateGroupBlog(pub_data, comments if comments else '', message, extra, profile)
-
-    def jsonrpc_sendMblogComment(self, node, text, extra={}):
-        """ Send microblog message
-        @param node: url of the comments node
-        @param text: comment
-        """
-        profile = ISATSession(self.session).profile
-        if node and text:
-            return self.sat_host.bridge.sendGroupBlogComment(node, text, extra, profile)
-        else:
-            raise Exception("Invalid data")
-
-    def jsonrpc_getMblogs(self, publisher_jid, item_ids):
-        """Get specified microblogs posted by a contact
-        @param publisher_jid: jid of the publisher
-        @param item_ids: list of microblogs items IDs
-        @return list of microblog data (dict)"""
-        profile = ISATSession(self.session).profile
-        d = self.asyncBridgeCall("getGroupBlogs", publisher_jid, item_ids, profile)
-        return d
-
-    def jsonrpc_getMblogsWithComments(self, publisher_jid, item_ids):
-        """Get specified microblogs posted by a contact and their comments
-        @param publisher_jid: jid of the publisher
-        @param item_ids: list of microblogs items IDs
-        @return list of couple (microblog data, list of microblog data)"""
-        profile = ISATSession(self.session).profile
-        d = self.asyncBridgeCall("getGroupBlogsWithComments", publisher_jid, item_ids, profile)
-        return d
-
-    def jsonrpc_getLastMblogs(self, publisher_jid, max_item):
-        """Get last microblogs posted by a contact
-        @param publisher_jid: jid of the publisher
-        @param max_item: number of items to ask
-        @return list of microblog data (dict)"""
-        profile = ISATSession(self.session).profile
-        d = self.asyncBridgeCall("getLastGroupBlogs", publisher_jid, max_item, profile)
-        return d
-
-    def jsonrpc_getMassiveLastMblogs(self, publishers_type, publishers_list, max_item):
-        """Get lasts microblogs posted by several contacts at once
-        @param publishers_type: one of "ALL", "GROUP", "JID"
-        @param publishers_list: list of publishers type (empty list of all, list of groups or list of jids)
-        @param max_item: number of items to ask
-        @return: dictionary key=publisher's jid, value=list of microblog data (dict)"""
-        profile = ISATSession(self.session).profile
-        d = self.asyncBridgeCall("getMassiveLastGroupBlogs", publishers_type, publishers_list, max_item, profile)
-        self.sat_host.bridge.massiveSubscribeGroupBlogs(publishers_type, publishers_list, profile)
-        return d
-
-    def jsonrpc_getMblogComments(self, service, node):
-        """Get all comments of given node
-        @param service: jid of the service hosting the node
-        @param node: comments node
-        """
-        profile = ISATSession(self.session).profile
-        d = self.asyncBridgeCall("getGroupBlogComments", service, node, profile)
-        return d
-
-
-    def jsonrpc_getPresenceStatuses(self):
-        """Get Presence information for connected contacts"""
-        profile = ISATSession(self.session).profile
-        return self.sat_host.bridge.getPresenceStatuses(profile)
-
-    def jsonrpc_getHistory(self, from_jid, to_jid, size, between):
-        """Return history for the from_jid/to_jid couple"""
-        sat_session = ISATSession(self.session)
-        profile = sat_session.profile
-        sat_jid = sat_session.jid
-        if not sat_jid:
-            log.error("No jid saved for this profile")
-            return {}
-        if JID(from_jid).userhost() != sat_jid.userhost() and JID(to_jid).userhost() != sat_jid.userhost():
-            log.error("Trying to get history from a different jid, maybe a hack attempt ?")
-            return {}
-        d = self.asyncBridgeCall("getHistory", from_jid, to_jid, size, between, profile)
-        def show(result_dbus):
-            result = []
-            for line in result_dbus:
-                #XXX: we have to do this stupid thing because Python D-Bus use its own types instead of standard types
-                #     and txJsonRPC doesn't accept D-Bus types, resulting in a empty query
-                timestamp, from_jid, to_jid, message, mess_type, extra = line
-                result.append((float(timestamp), unicode(from_jid), unicode(to_jid), unicode(message), unicode(mess_type), dict(extra)))
-            return result
-        d.addCallback(show)
-        return d
-
-    def jsonrpc_joinMUC(self, room_jid, nick):
-        """Join a Multi-User Chat room
-        @room_jid: leave empty string to generate a unique name
-        """
-        profile = ISATSession(self.session).profile
-        try:
-            if room_jid != "":
-                room_jid = JID(room_jid).userhost()
-        except:
-            log.warning('Invalid room jid')
-            return
-        d = self.asyncBridgeCall("joinMUC", room_jid, nick, {}, profile)
-        return d
-
-    def jsonrpc_inviteMUC(self, contact_jid, room_jid):
-        """Invite a user to a Multi-User Chat room"""
-        profile = ISATSession(self.session).profile
-        try:
-            room_jid = JID(room_jid).userhost()
-        except:
-            log.warning('Invalid room jid')
-            return
-        room_id = room_jid.split("@")[0]
-        service = room_jid.split("@")[1]
-        self.sat_host.bridge.inviteMUC(contact_jid, service, room_id, {}, profile)
-
-    def jsonrpc_mucLeave(self, room_jid):
-        """Quit a Multi-User Chat room"""
-        profile = ISATSession(self.session).profile
-        try:
-            room_jid = JID(room_jid)
-        except:
-            log.warning('Invalid room jid')
-            return
-        self.sat_host.bridge.mucLeave(room_jid.userhost(), profile)
-
-    def jsonrpc_getRoomsJoined(self):
-        """Return list of room already joined by user"""
-        profile = ISATSession(self.session).profile
-        return self.sat_host.bridge.getRoomsJoined(profile)
-
-    def jsonrpc_launchTarotGame(self, other_players, room_jid=""):
-        """Create a room, invite the other players and start a Tarot game
-        @param room_jid: leave empty string to generate a unique room name
-        """
-        profile = ISATSession(self.session).profile
-        try:
-            if room_jid != "":
-                room_jid = JID(room_jid).userhost()
-        except:
-            log.warning('Invalid room jid')
-            return
-        self.sat_host.bridge.tarotGameLaunch(other_players, room_jid, profile)
-
-    def jsonrpc_getTarotCardsPaths(self):
-        """Give the path of all the tarot cards"""
-        _join = os.path.join
-        _media_dir = _join(self.sat_host.media_dir,'')
-        return map(lambda x: _join(C.MEDIA_DIR, x[len(_media_dir):]), glob.glob(_join(_media_dir, C.CARDS_DIR, '*_*.png')));
-
-    def jsonrpc_tarotGameReady(self, player, referee):
-        """Tell to the server that we are ready to start the game"""
-        profile = ISATSession(self.session).profile
-        self.sat_host.bridge.tarotGameReady(player, referee, profile)
-
-    def jsonrpc_tarotGamePlayCards(self, player_nick, referee, cards):
-        """Tell to the server the cards we want to put on the table"""
-        profile = ISATSession(self.session).profile
-        self.sat_host.bridge.tarotGamePlayCards(player_nick, referee, cards, profile)
-
-    def jsonrpc_launchRadioCollective(self, invited, room_jid=""):
-        """Create a room, invite people, and start a radio collective
-        @param room_jid: leave empty string to generate a unique room name
-        """
-        profile = ISATSession(self.session).profile
-        try:
-            if room_jid != "":
-                room_jid = JID(room_jid).userhost()
-        except:
-            log.warning('Invalid room jid')
-            return
-        self.sat_host.bridge.radiocolLaunch(invited, room_jid, profile)
-
-    def jsonrpc_getEntityData(self, jid, keys):
-        """Get cached data for an entit
-        @param jid: jid of contact from who we want data
-        @param keys: name of data we want (list)
-        @return: requested data"""
-        profile = ISATSession(self.session).profile
-        return self.sat_host.bridge.getEntityData(jid, keys, profile)
-
-    def jsonrpc_getCard(self, jid):
-        """Get VCard for entiry
-        @param jid: jid of contact from who we want data
-        @return: id to retrieve the profile"""
-        profile = ISATSession(self.session).profile
-        return self.sat_host.bridge.getCard(jid, profile)
-
-    def jsonrpc_getAccountDialogUI(self):
-        """Get the dialog for managing user account
-        @return: XML string of the XMLUI"""
-        profile = ISATSession(self.session).profile
-        return self.sat_host.bridge.getAccountDialogUI(profile)
-
-    def jsonrpc_getParamsUI(self):
-        """Return the parameters XML for profile"""
-        profile = ISATSession(self.session).profile
-        d = self.asyncBridgeCall("getParams", C.SECURITY_LIMIT, C.APP_NAME, profile)
-
-        def setAuthorizedParams(params_xml):
-            if self.authorized_params is None:
-                self.authorized_params = {}
-                for cat in minidom.parseString(params_xml.encode('utf-8')).getElementsByTagName("category"):
-                    params = cat.getElementsByTagName("param")
-                    params_list = [param.getAttribute("name") for param in params]
-                    self.authorized_params[cat.getAttribute("name")] = params_list
-            if self.authorized_params:
-                return params_xml
-            else:
-                return None
-
-        d.addCallback(setAuthorizedParams)
-
-        d.addCallback(lambda params_xml: paramsXML2XMLUI(params_xml) if params_xml else "")
-
-        return d
-
-    def jsonrpc_asyncGetParamA(self, param, category, attribute="value"):
-        """Return the parameter value for profile"""
-        profile = ISATSession(self.session).profile
-        d = self.asyncBridgeCall("asyncGetParamA", param, category, attribute, C.SECURITY_LIMIT, profile_key=profile)
-        return d
-
-    def jsonrpc_setParam(self, name, value, category):
-        profile = ISATSession(self.session).profile
-        if category in self.authorized_params and name in self.authorized_params[category]:
-            return self.sat_host.bridge.setParam(name, value, category, C.SECURITY_LIMIT, profile)
-        else:
-            log.warning("Trying to set parameter '%s' in category '%s' without authorization!!!"
-                    % (name, category))
-
-    def jsonrpc_launchAction(self, callback_id, data):
-        #FIXME: any action can be launched, this can be a huge security issue if callback_id can be guessed
-        #       a security system with authorised callback_id must be implemented, similar to the one for authorised params
-        profile = ISATSession(self.session).profile
-        d = self.asyncBridgeCall("launchAction", callback_id, data, profile)
-        return d
-
-    def jsonrpc_chatStateComposing(self, to_jid_s):
-        """Call the method to process a "composing" state.
-        @param to_jid_s: contact the user is composing to
-        """
-        profile = ISATSession(self.session).profile
-        self.sat_host.bridge.chatStateComposing(to_jid_s, profile)
-
-    def jsonrpc_getNewAccountDomain(self):
-        """@return: the domain for new account creation"""
-        d = self.asyncBridgeCall("getNewAccountDomain")
-        return d
-
-    def jsonrpc_confirmationAnswer(self, confirmation_id, result, answer_data):
-        """Send the user's answer to any previous 'askConfirmation' signal"""
-        profile = ISATSession(self.session).profile
-        self.sat_host.bridge.confirmationAnswer(confirmation_id, result, answer_data, profile)
-
-    def jsonrpc_syntaxConvert(self, text, syntax_from=C.SYNTAX_XHTML, syntax_to=C.SYNTAX_CURRENT):
-        """ Convert a text between two syntaxes
-        @param text: text to convert
-        @param syntax_from: source syntax (e.g. "markdown")
-        @param syntax_to: dest syntax (e.g.: "XHTML")
-        @param safe: clean resulting XHTML to avoid malicious code if True (forced here)
-        @return: converted text """
-        profile = ISATSession(self.session).profile
-        return self.sat_host.bridge.syntaxConvert(text, syntax_from, syntax_to, True, profile)
-
-
-class Register(JSONRPCMethodManager):
-    """This class manage the registration procedure with SàT
-    It provide an api for the browser, check password and setup the web server"""
-
-    def __init__(self, sat_host):
-        JSONRPCMethodManager.__init__(self, sat_host)
-        self.profiles_waiting={}
-        self.request=None
-
-    def getWaitingRequest(self, profile):
-        """Tell if a profile is trying to log in"""
-        if self.profiles_waiting.has_key(profile):
-            return self.profiles_waiting[profile]
-        else:
-            return None
-
-    def render(self, request):
-        """
-        Render method with some hacks:
-           - if login is requested, try to login with form data
-           - except login, every method is jsonrpc
-           - user doesn't need to be authentified for explicitely listed methods, but must be for all others
-        """
-        if request.postpath == ['login']:
-            return self.loginOrRegister(request)
-        _session = request.getSession()
-        parsed = jsonrpclib.loads(request.content.read())
-        method = parsed.get("method")
-        if  method not in ['isRegistered', 'registerParams', 'getMenus']:
-            #if we don't call these methods, we need to be identified
-            profile = ISATSession(_session).profile
-            if not profile:
-                #user is not identified, we return a jsonrpc fault
-                fault = jsonrpclib.Fault(C.ERRNUM_LIBERVIA, "Not allowed") #FIXME: define some standard error codes for libervia
-                return jsonrpc.JSONRPC._cbRender(self, fault, request, parsed.get('id'), parsed.get('jsonrpc'))
-        self.request = request
-        return jsonrpc.JSONRPC.render(self, request)
-
-    def loginOrRegister(self, request):
-        """This method is called with the POST information from the registering form.
-
-        @param request: request of the register form
-        @return: a constant indicating the state:
-            - BAD REQUEST: something is wrong in the request (bad arguments)
-            - a return value from self._loginAccount or self._registerNewAccount
-        """
-        try:
-            submit_type = request.args['submit_type'][0]
-        except KeyError:
-            return "BAD REQUEST"
-
-        if submit_type == 'register':
-            return self._registerNewAccount(request)
-        elif submit_type == 'login':
-            return self._loginAccount(request)
-        return Exception('Unknown submit type')
-
-    def _loginAccount(self, request):
-        """Try to authenticate the user with the request information.
-        @param request: request of the register form
-        @return: a constant indicating the state:
-            - BAD REQUEST: something is wrong in the request (bad arguments)
-            - AUTH ERROR: either the profile (login) or the password is wrong
-            - ALREADY WAITING: a request has already been submitted for this profile
-            - server.NOT_DONE_YET: the profile is being processed, the return
-                value will be given by self._logged or self._logginError
-        """
-        try:
-            login_ = request.args['login'][0]
-            password_ = request.args['login_password'][0]
-        except KeyError:
-            return "BAD REQUEST"
-
-        if login_.startswith('@'):
-            raise Exception('No profile_key allowed')
-
-        profile_check = self.sat_host.bridge.getProfileName(login_)
-        if not profile_check or profile_check != login_ or not password_:
-            # profiles with empty passwords are restricted to local frontends
-            return "AUTH ERROR"
-
-        if login_ in self.profiles_waiting:
-            return "ALREADY WAITING"
-
-        def auth_eb(ignore=None):
-            self.__cleanWaiting(login_)
-            log.info("Profile %s doesn't exist or the submitted password is wrong" % login_)
-            request.write("AUTH ERROR")
-            request.finish()
-
-        self.profiles_waiting[login_] = request
-        d = self.asyncBridgeCall("asyncConnect", login_, password_)
-        d.addCallbacks(lambda connected: self._logged(login_, request) if connected else None, auth_eb)
-
-        return server.NOT_DONE_YET
-
-    def _registerNewAccount(self, request):
-        """Create a new account, or return error
-        @param request: request of the register form
-        @return: a constant indicating the state:
-            - BAD REQUEST: something is wrong in the request (bad arguments)
-            - REGISTRATION: new account has been successfully registered
-            - ALREADY EXISTS: the given profile already exists
-            - INTERNAL or 'Unknown error (...)'
-            - server.NOT_DONE_YET: the profile is being processed, the return
-                value will be given later (one of those previously described)
-        """
-        try:
-            profile = login = request.args['register_login'][0]
-            password = request.args['register_password'][0]
-            email = request.args['email'][0]
-        except KeyError:
-            return "BAD REQUEST"
-        if not re.match(r'^[a-z0-9_-]+$', login, re.IGNORECASE) or \
-           not re.match(r'^.+@.+\..+', email, re.IGNORECASE) or \
-           len(password) < C.PASSWORD_MIN_LENGTH:
-            return "BAD REQUEST"
-
-        def registered(result):
-            request.write('REGISTRATION')
-            request.finish()
-
-        def registeringError(failure):
-            reason = failure.value.faultString
-            if reason == "ConflictError":
-                request.write('ALREADY EXISTS')
-            elif reason == "InternalError":
-                request.write('INTERNAL')
-            else:
-                log.error('Unknown registering error: %s' % (reason,))
-                request.write('Unknown error (%s)' % reason)
-            request.finish()
-
-        d = self.asyncBridgeCall("registerSatAccount", email, password, profile)
-        d.addCallback(registered)
-        d.addErrback(registeringError)
-        return server.NOT_DONE_YET
-
-    def __cleanWaiting(self, login):
-        """Remove login from waiting queue"""
-        try:
-            del self.profiles_waiting[login]
-        except KeyError:
-            pass
-
-    def _logged(self, profile, request):
-        """Set everything when a user just logged in
-
-        @param profile
-        @param request
-        @return: a constant indicating the state:
-            - LOGGED
-            - SESSION_ACTIVE
-        """
-        self.__cleanWaiting(profile)
-        _session = request.getSession()
-        sat_session = ISATSession(_session)
-        if sat_session.profile:
-            log.error(('/!\\ Session has already a profile, this should NEVER happen!'))
-            request.write('SESSION_ACTIVE')
-            request.finish()
-            return
-        sat_session.profile = profile
-        self.sat_host.prof_connected.add(profile)
-
-        def onExpire():
-            log.info("Session expired (profile=%s)" % (profile,))
-            try:
-                #We purge the queue
-                del self.sat_host.signal_handler.queue[profile]
-            except KeyError:
-                pass
-            #and now we disconnect the profile
-            self.sat_host.bridge.disconnect(profile)
-
-        _session.notifyOnExpire(onExpire)
-
-        request.write('LOGGED')
-        request.finish()
-
-    def _logginError(self, login, request, error_type):
-        """Something went wrong during logging in
-        @return: error
-        """
-        self.__cleanWaiting(login)
-        return error_type
-
-    def jsonrpc_isConnected(self):
-        _session = self.request.getSession()
-        profile = ISATSession(_session).profile
-        return self.sat_host.bridge.isConnected(profile)
-
-    def jsonrpc_connect(self):
-        _session = self.request.getSession()
-        profile = ISATSession(_session).profile
-        if self.profiles_waiting.has_key(profile):
-            raise jsonrpclib.Fault(1,'Already waiting') #FIXME: define some standard error codes for libervia
-        self.profiles_waiting[profile] = self.request
-        self.sat_host.bridge.connect(profile)
-        return server.NOT_DONE_YET
-
-    def jsonrpc_isRegistered(self):
-        """
-        @return: a couple (registered, message) with:
-        - registered: True if the user is already registered, False otherwise
-        - message: a security warning message if registered is False *and* the connection is unsecure, None otherwise
-        """
-        _session = self.request.getSession()
-        profile = ISATSession(_session).profile
-        if bool(profile):
-            return (True, None)
-        return (False, self.__getSecurityWarning())
-
-    def jsonrpc_registerParams(self):
-        """Register the frontend specific parameters"""
-        params = """
-        <params>
-        <individual>
-        <category name="%(category_name)s" label="%(category_label)s">
-            <param name="%(param_name)s" label="%(param_label)s" value="false" type="bool" security="0"/>
-         </category>
-        </individual>
-        </params>
-        """ % {
-            'category_name': C.ENABLE_UNIBOX_KEY,
-            'category_label': _(C.ENABLE_UNIBOX_KEY),
-            'param_name': C.ENABLE_UNIBOX_PARAM,
-            'param_label': _(C.ENABLE_UNIBOX_PARAM)
-        }
-
-        self.sat_host.bridge.paramsRegisterApp(params, C.SECURITY_LIMIT, C.APP_NAME)
-
-    def jsonrpc_getMenus(self):
-        """Return the parameters XML for profile"""
-        # XXX: we put this method in Register because we get menus before being logged
-        return self.sat_host.bridge.getMenus('', C.SECURITY_LIMIT)
-
-    def __getSecurityWarning(self):
-        """@return: a security warning message, or None if the connection is secure"""
-        if self.request.URLPath().scheme == 'https' or not self.sat_host.security_warning:
-            return None
-        text = D_("You are about to connect to an unsecured service.")
-        if self.sat_host.connection_type == 'both':
-            new_port = (':%s' % self.sat_host.port_https_ext) if self.sat_host.port_https_ext != HTTPS_PORT else ''
-            url = "https://%s" % self.request.URLPath().netloc.replace(':%s' % self.sat_host.port, new_port)
-            text += D_('<br />Secure version of this website: <a href="%(url)s">%(url)s</a>') % {'url': url}
-        return text
-
-
-class SignalHandler(jsonrpc.JSONRPC):
-
-    def __init__(self, sat_host):
-        Resource.__init__(self)
-        self.register=None
-        self.sat_host=sat_host
-        self.signalDeferred = {}
-        self.queue = {}
-
-    def plugRegister(self, register):
-        self.register = register
-
-    def jsonrpc_getSignals(self):
-        """Keep the connection alive until a signal is received, then send it
-        @return: (signal, *signal_args)"""
-        _session = self.request.getSession()
-        profile = ISATSession(_session).profile
-        if profile in self.queue: #if we have signals to send in queue
-            if self.queue[profile]:
-                return self.queue[profile].pop(0)
-            else:
-                #the queue is empty, we delete the profile from queue
-                del self.queue[profile]
-        _session.lock() #we don't want the session to expire as long as this connection is active
-        def unlock(signal, profile):
-            _session.unlock()
-            try:
-                source_defer = self.signalDeferred[profile]
-                if source_defer.called and source_defer.result[0] == "disconnected":
-                    log.info(u"[%s] disconnected" % (profile,))
-                    _session.expire()
-            except IndexError:
-                log.error("Deferred result should be a tuple with fonction name first")
-
-        self.signalDeferred[profile] = defer.Deferred()
-        self.request.notifyFinish().addBoth(unlock, profile)
-        return self.signalDeferred[profile]
-
-    def getGenericCb(self, function_name):
-        """Return a generic function which send all params to signalDeferred.callback
-        function must have profile as last argument"""
-        def genericCb(*args):
-            profile = args[-1]
-            if not profile in self.sat_host.prof_connected:
-                return
-            if profile in self.signalDeferred:
-                self.signalDeferred[profile].callback((function_name,args[:-1]))
-                del self.signalDeferred[profile]
-            else:
-                if not self.queue.has_key(profile):
-                    self.queue[profile] = []
-                self.queue[profile].append((function_name, args[:-1]))
-        return genericCb
-
-    def connected(self, profile):
-        assert(self.register)  # register must be plugged
-        request = self.register.getWaitingRequest(profile)
-        if request:
-            self.register._logged(profile, request)
-
-    def disconnected(self, profile):
-        if not profile in self.sat_host.prof_connected:
-            log.error("'disconnected' signal received for a not connected profile")
-            return
-        self.sat_host.prof_connected.remove(profile)
-        if profile in self.signalDeferred:
-            self.signalDeferred[profile].callback(("disconnected",))
-            del self.signalDeferred[profile]
-        else:
-            if not self.queue.has_key(profile):
-                self.queue[profile] = []
-            self.queue[profile].append(("disconnected",))
-
-
-    def connectionError(self, error_type, profile):
-        assert(self.register) #register must be plugged
-        request = self.register.getWaitingRequest(profile)
-        if request: #The user is trying to log in
-            if error_type == "AUTH_ERROR":
-                _error_t = "AUTH ERROR"
-            else:
-                _error_t = "UNKNOWN"
-            self.register._logginError(profile, request, _error_t)
-
-    def render(self, request):
-        """
-        Render method wich reject access if user is not identified
-        """
-        _session = request.getSession()
-        parsed = jsonrpclib.loads(request.content.read())
-        profile = ISATSession(_session).profile
-        if not profile:
-            #user is not identified, we return a jsonrpc fault
-            fault = jsonrpclib.Fault(C.ERRNUM_LIBERVIA, "Not allowed") #FIXME: define some standard error codes for libervia
-            return jsonrpc.JSONRPC._cbRender(self, fault, request, parsed.get('id'), parsed.get('jsonrpc'))
-        self.request = request
-        return jsonrpc.JSONRPC.render(self, request)
-
<