diff src/browser/libervia_main.py @ 679:a90cc8fc9605

merged branch frontends_multi_profiles
author Goffi <goffi@goffi.org>
date Wed, 18 Mar 2015 16:15:18 +0100
parents 3eb3a2c0c011 849ffb24d5bf
children 801eb94aa869
line wrap: on
line diff
--- a/src/browser/libervia_main.py	Thu Feb 05 12:05:32 2015 +0100
+++ b/src/browser/libervia_main.py	Wed Mar 18 16:15:18 2015 +0100
@@ -17,7 +17,6 @@
 # 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 sat_browser import logging
@@ -26,8 +25,16 @@
 log = getLogger(__name__)
 ###
 
+from sat.core.i18n import D_
+
+from sat_frontends.quick_frontend.quick_app import QuickApp
+from sat_frontends.quick_frontend import quick_widgets
+from sat_frontends.quick_frontend import quick_menus
+
 from sat_frontends.tools.misc import InputHistory
 from sat_frontends.tools import strings
+from sat_frontends.tools import jid
+from sat_frontends.tools import host_listener
 from sat.core.i18n import _
 
 from pyjamas.ui.RootPanel import RootPanel
@@ -35,19 +42,22 @@
 from pyjamas.ui.KeyboardListener import KEY_ESCAPE
 from pyjamas.Timer import Timer
 from pyjamas import Window, DOM
-from pyjamas.JSONService import JSONProxy
 
+from sat_browser import json
 from sat_browser import register
-from sat_browser import contact
-from sat_browser import base_widget
-from sat_browser import panels
+from sat_browser.contact_list import ContactList
+from sat_browser import widget
+from sat_browser import main_panel
+from sat_browser import blog
 from sat_browser import dialog
-from sat_browser import jid
 from sat_browser import xmlui
 from sat_browser import html_tools
 from sat_browser import notification
+from sat_browser import libervia_widget
 
 from sat_browser.constants import Const as C
+import os.path
+
 
 try:
     # FIXME: import plugin dynamically
@@ -55,148 +65,36 @@
 except ImportError:
     pass
 
+
+unicode = str  # FIXME: pyjamas workaround
+
+
 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 base_widget.WidgetsPanel, or replaced to the expected
+# and added to new libervia_widget.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'])
+# REUSE_EXISTING_LIBERVIA_WIDGETS = True # FIXME
 
 
-class RegisterCall(LiberviaJsonProxy):
-    def __init__(self):
-        LiberviaJsonProxy.__init__(self, "/register_api",
-                        ["isRegistered", "isConnected", "asyncConnect", "registerParams", "getMenus"])
-
-
-class BridgeCall(LiberviaJsonProxy):
-    def __init__(self):
-        LiberviaJsonProxy.__init__(self, "/json_api",
-                        ["getContacts", "addContact", "sendMessage", "sendMblog", "sendMblogComment",
-                         "getMblogs", "getMassiveMblogs", "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", "getLastResource"
-                        ])
-
-
-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):
+class SatWebFrontend(InputHistory, QuickApp):
     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 = contact.ContactPanel(self)
-        self.panel = panels.MainPanel(self)
-        self.discuss_panel = self.panel.discuss_panel
+        self.bridge_signals = json.BridgeSignals(self)
+        QuickApp.__init__(self, json.BridgeCall)
+        self.uni_box = None # FIXME: to be removed
+        self.panel = main_panel.MainPanel(self)
         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.Notification()
         DOM.addEventPreview(self)
         self.importPlugins()
-        self._register = RegisterCall()
+        self._register = json.RegisterCall()
         self._register.call('getMenus', self.gotMenus)
         self._register.call('registerParams', None)
         self._register.call('isRegistered', self._isRegisteredCB)
@@ -205,35 +103,65 @@
         self.cached_params = {}
         self.next_rsm_index = 0
 
+        #FIXME: microblog cache should be managed directly in blog module
+        self.mblog_cache = []  # used to keep our own blog entries in memory, to show them in new mblog panel
+
+        # self._selected_listeners = set() # FIXME: to be done with new listeners mechanism
+
+    @property
+    def whoami(self):
+        # XXX: works because Libervia is mono-profile
+        #      if one day Libervia manage several profiles at once, this must be deleted
+        return self.profiles[C.PROF_KEY_NONE].whoami
+
+    @property
+    def contact_list(self):
+        return self.contact_lists[C.PROF_KEY_NONE]
+
+    @property
+    def visible_widgets(self):
+        widgets_panel = self.tab_panel.getCurrentPanel()
+        return [wid for wid in widgets_panel.widgets if isinstance(wid, quick_widgets.QuickWidget)]
+
+    @property
+    def base_location(self):
+        """Return absolute base url of this Libervia instance"""
+        url = Window.getLocation().getHref()
+        if url.endswith(C.LIBERVIA_MAIN_PAGE):
+            url = url[:-len(C.LIBERVIA_MAIN_PAGE)]
+        if url.endswith("/"):
+            url = url[:-1]
+        return url
+
+    def registerSignal(self, functionName, handler=None, iface="core", with_profile=True):
+        if handler is None:
+            callback = getattr(self, "{}{}".format(functionName, "Handler"))
+        else:
+            callback = handler
+
+        self.bridge_signals.register(functionName, callback, with_profile=with_profile)
+
     def importPlugins(self):
         self.plugins = {}
-        inhibited_menus = []
-        # FIXME: plugins import should be dynamic and generic like in sat
         try:
             self.plugins['otr'] = plugin_sec_otr.OTR(self)
         except TypeError:  # plugin_sec_otr has not been imported
-            inhibited_menus.append('OTR')
+            pass
 
-        class DummyPlugin(object):
-            def inhibitMenus(self):
-                return inhibited_menus
-
-        self.plugins['dummy_plugin'] = DummyPlugin()
-
-    def addSelectedListener(self, callback):
-        self._selected_listeners.add(callback)
+    # def addSelectedListener(self, callback):
+    #     self._selected_listeners.add(callback)
 
     def getSelected(self):
         wid = self.tab_panel.getCurrentPanel()
-        if not isinstance(wid, base_widget.WidgetsPanel):
-            log.error("Tab widget is not a base_widget.WidgetsPanel, can't get selected widget")
+        if not isinstance(wid, libervia_widget.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, base_widget.WidgetsPanel):
+        if not isinstance(widgets_panel, libervia_widget.WidgetsPanel):
             return
 
         selected = widgets_panel.selected
@@ -244,13 +172,16 @@
         if selected:
             selected.removeStyleName('selected_widget')
 
+        # FIXME: check that widget is in the current WidgetsPanel
         widgets_panel.selected = widget
+        self.selected_widget = widget
 
         if widget:
             widgets_panel.selected.addStyleName('selected_widget')
 
-        for callback in self._selected_listeners:
-            callback(widget)
+        # FIXME:
+        # for callback in self._selected_listeners:
+        #     callback(widget)
 
     def resize(self):
         """Resize elements"""
@@ -260,9 +191,11 @@
         return True
 
     def onTabSelected(self, sender, tab_index):
-        selected = self.getSelected()
-        for callback in self._selected_listeners:
-            callback(selected)
+        pass
+        # selected = self.getSelected()
+        # FIXME:
+        # for callback in self._selected_listeners:
+        #     callback(selected)
 
     def onEventPreview(self, event):
         if event.type in ["keydown", "keypress", "keyup"] and event.keyCode == KEY_ESCAPE:
@@ -270,26 +203,24 @@
             event.preventDefault()
         return True
 
-    def getAvatar(self, jid_str):
+    # FIXME: must not call _entityDataUpdatedCb by itself
+    #        should not get VCard, backend plugin must be fixed too
+    def getAvatarURL(self, jid_):
         """Return avatar of a jid if in cache, else ask for it.
 
-        @param jid_str (str): JID of the contact
-        @return: the URL to the avatar (str)
+        @param jid_ (jid.JID): JID of the contact
+        @return: the URL to the avatar (unicode)
         """
-        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] = C.DEFAULT_AVATAR
-        return self.avatars_cache[jid_str]
+        assert isinstance(jid_, jid.JID)
+        contact_list = self.contact_list  # pyjamas issue: need a temporary variable to call a property's method
+        avatar_hash = contact_list.getCache(jid_, 'avatar')
+        if avatar_hash is None:
+            # we have no value for avatar_hash, so we request the vcard
+            self.bridge.getCard(unicode(jid_), profile=C.PROF_KEY_NONE)
+        if not avatar_hash:
+            return C.DEFAULT_AVATAR_URL
+        ret = os.path.join(C.AVATARS_DIR, avatar_hash)
+        return ret
 
     def registerWidget(self, wid):
         log.debug("Registering %s" % wid.getDebugName())
@@ -304,64 +235,65 @@
     def refresh(self):
         """Refresh the general display."""
         self.panel.refresh()
-        if self.getCachedParam(C.COMPOSITION_KEY, C.ENABLE_UNIBOX_PARAM) == 'true':
-            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 = base_widget.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"""
+        @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 = self.tab_panel.deck.getWidget(tab_index)
         panel.addWidget(wid)
 
     def displayNotification(self, title, body):
         self.notification.notify(title, body)
 
-    def gotMenus(self, menus):
+    def gotMenus(self, backend_menus):
         """Put the menus data in cache and build the main menu bar
 
-        @param menus (list[tuple]): menu data
+        @param backend_menus (list[tuple]): menu data from backend
         """
-        def process(menus, inhibited=None):
-            for raw_menu in menus:
-                id_, type_, path, path_i18n = raw_menu
-                if inhibited and path[0] in inhibited:
-                    continue
-                menus_data = self.menus.setdefault(type_, [])
-                menus_data.append((id_, path, path_i18n))
+        main_menu = self.panel.menu # most of global menu callbacks are in main_menu
+
+        # Categories (with icons)
+        self.menus.addCategory(C.MENU_GLOBAL, [D_(u"General")], extra={'icon': 'home'})
+        self.menus.addCategory(C.MENU_GLOBAL, [D_(u"Contacts")], extra={'icon': 'social'})
+        self.menus.addCategory(C.MENU_GLOBAL, [D_(u"Groups")], extra={'icon': 'social'})
+        self.menus.addCategory(C.MENU_GLOBAL, [D_(u"Games")], extra={'icon': 'games'})
+
+        # menus to have before backend menus
+        self.menus.addMenu(C.MENU_GLOBAL, (D_(u"Groups"), D_(u"Discussion")), callback=main_menu.onJoinRoom)
+
+        # menus added by the backend/plugins (include other types than C.MENU_GLOBAL)
+        self.menus.addMenus(backend_menus, top_extra={'icon': 'plugins'})
+
+        # menus to have under backend menus
+        self.menus.addMenu(C.MENU_GLOBAL, (D_(u"Contacts"), D_(u"Manage groups")), callback=main_menu.onManageContactGroups)
 
-        self.menus = {}
-        inhibited = set()
-        extras = []
-        for plugin in self.plugins.values():
-            if hasattr(plugin, "inhibitMenus"):
-                inhibited.update(plugin.inhibitMenus())
-            if hasattr(plugin, "extraMenus"):
-                extras.extend(plugin.extraMenus())
-        process(menus, inhibited)
-        process(extras)
-        self.panel.menu.createMenus()
+        # separator and right hand menus
+        self.menus.addMenuItem(C.MENU_GLOBAL, [], quick_menus.MenuSeparator())
+
+        self.menus.addMenu(C.MENU_GLOBAL, (D_(u"Help"), D_("Social contract")), top_extra={'icon': 'help'}, callback=main_menu.onSocialContract)
+        self.menus.addMenu(C.MENU_GLOBAL, (D_(u"Help"), D_("About")), callback=main_menu.onAbout)
+        self.menus.addMenu(C.MENU_GLOBAL, (D_(u"Settings"), D_("Account")), top_extra={'icon': 'settings'}, callback=main_menu.onAccount)
+        self.menus.addMenu(C.MENU_GLOBAL, (D_(u"Settings"), D_("Parameters")), callback=main_menu.onParameters)
+        # XXX: temporary, will change when a full profile will be managed in SàT
+        self.menus.addMenu(C.MENU_GLOBAL, (D_(u"Settings"), D_("Upload avatar")), callback=main_menu.onAvatarUpload)
+
+        # we call listener to have menu added by local classes/plugins
+        self.callListeners('gotMenus')  # FIXME: to be done another way or moved to quick_app
+
+        # and finally the menus which must appear at the bottom
+        self.menus.addMenu(C.MENU_GLOBAL, (D_(u"General"), D_(u"Disconnect")), callback=main_menu.onDisconnect)
+
+        # we can now display all the menus
+        main_menu.update(C.MENU_GLOBAL)
 
     def _isRegisteredCB(self, result):
         registered, warning = result
@@ -386,17 +318,19 @@
             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)
+        # display the presence status panel and tab bar
+        self.presence_status_panel = main_panel.PresenceStatusPanel(self)
+        self.panel.addPresenceStatusPanel(self.presence_status_panel)
+        self.panel.tab_panel.getTabBar().setVisible(True)
+
+        self.bridge_signals.call('getSignals', self.bridge_signals.signalHandler)
 
         #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)
+        # 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
@@ -405,18 +339,72 @@
         def domain_eb(value):
             self._defaultDomain = "libervia.org"
 
-        self.bridge.call("getNewAccountDomain", (domain_cb, domain_eb))
-        self.discuss_panel.addWidget(panels.MicroblogPanel(self, []))
+        self.bridge.getNewAccountDomain(callback=domain_cb, errback=domain_eb)
+        self.plug_profiles([C.PROF_KEY_NONE]) # XXX: None was used intitially, but pyjamas bug when using variable arguments and None is the only arg.
+        # self.discuss_panel.addWidget(panel.MicroblogPanel(self, []))
+
+        # # get cached params and refresh the display
+        # def param_cb(cat, name, count):
+        #     count[0] += 1
+        #     refresh = count[0] == len(C.CACHED_PARAMS)
+        #     return lambda value: self._paramUpdate(name, value, cat, refresh)
+
+        # count = [0]  # used to do something similar to DeferredList
+        # for cat, name in C.CACHED_PARAMS:
+        #     self.bridge.call('asyncGetParamA', param_cb(cat, name, count), name, cat)
+
+    def profilePlugged(self, dummy):  # FIXME: to be called as a "profilePlugged" listener?
+        QuickApp.profilePlugged(self, dummy)
+
+        microblog_widget = self.displayWidget(blog.MicroblogPanel, ())
+        self.setSelected(microblog_widget)
+
+        # we fill the panels already here
+        for wid in self.widgets.getWidgets(blog.MicroblogPanel):
+            if wid.accept_all():
+                self.bridge.getMassiveMblogs('ALL', (), None, profile=C.PROF_KEY_NONE, callback=wid.massiveInsert)
+            else:
+                self.bridge.getMassiveMblogs('GROUP', list(wid.accepted_groups), None, profile=C.PROF_KEY_NONE, callback=wid.massiveInsert)
+
+        #we ask for our own microblogs:
+        self.loadOurMainEntries()
 
-        # get cached params and refresh the display
-        def param_cb(cat, name, count):
-            count[0] += 1
-            refresh = count[0] == len(C.CACHED_PARAMS)
-            return lambda value: self._paramUpdate(name, value, cat, refresh)
+    def addContactList(self, dummy):
+        contact_list = ContactList(self)
+        self.panel.addContactList(contact_list)
+
+        # FIXME: the contact list height has to be set manually the first time
+        self.resize()
+
+        return contact_list
+
+    def newWidget(self, wid):
+        log.debug("newWidget: {}".format(wid))
+        self.addWidget(wid)
 
-        count = [0]  # used to do something similar to DeferredList
-        for cat, name in C.CACHED_PARAMS:
-            self.bridge.call('asyncGetParamA', param_cb(cat, name, count), name, cat)
+    def newMessageHandler(self, from_jid_s, msg, type_, to_jid_s, extra, profile=C.PROF_KEY_NONE):
+        if type_ == C.MESS_TYPE_HEADLINE:
+            from_jid = jid.JID(from_jid_s)
+            if from_jid.domain == self._defaultDomain:
+                # we display announcement from the server in a dialog for better visibility
+                try:
+                    title = extra['subject']
+                except KeyError:
+                    title = _('Announcement from %s') % from_jid
+                msg = strings.addURLToText(html_tools.XHTML2Text(msg))
+                dialog.InfoDialog(title, msg).show()
+                return
+        QuickApp.newMessageHandler(self, from_jid_s, msg, type_, to_jid_s, extra, profile)
+
+    def disconnectedHandler(self, profile):
+        QuickApp.disconnectedHandler(self, profile)
+        Window.getLocation().reload()
+
+    def setStatusOnline(self, online=True, show='', statuses={}, profile=C.PROF_KEY_NONE):
+        self.presence_status_panel.setPresence(show)
+        if statuses:
+            # FIXME: retrieve user language status or fallback to 'default'
+            self.presence_status_panel.setStatus(statuses.values()[0])
 
     def _tryAutoConnect(self, skip_validation=False):
         """This method retrieve the eventual URL parameters to auto-connect the user.
@@ -447,7 +435,8 @@
         elif "public_blog" in data:
             # TODO: use the bare instead of node when all blogs can be retrieved
             node = jid.JID(data['public_blog']).node
-            self.addTab("%s's blog" % node, panels.WebPanel(self, "/blog/%s" % node))
+            # FIXME: "/blog/{}" won't work with unicode nodes
+            self.displayWidget(widget.WebWidget, "/blog/{}".format(node), show_url=False, new_tab=_(u"{}'s blog").format(unicode(node)))
         else:
             dialog.InfoDialog("Error",
                               "Unmanaged action result", Width="400px").center()
@@ -455,9 +444,9 @@
     def _actionEb(self, err_data):
         err_code, err_obj = err_data
         dialog.InfoDialog("Error",
-                          str(err_obj), Width="400px").center()
+                          unicode(err_obj), Width="400px").center()
 
-    def launchAction(self, callback_id, data):
+    def launchAction(self, callback_id, data=None, callback=None, profile=C.PROF_KEY_NONE):
         """ Launch a dynamic action
         @param callback_id: id of the action to launch
         @param data: data needed only for certain actions
@@ -465,78 +454,13 @@
         """
         if data is None:
             data = {}
-        self.bridge.call('launchAction', (self._actionCb, self._actionEb), callback_id, data)
+        self.bridge.launchAction(callback_id, data, profile=profile, callback=self._actionCb, errback=self._actionEb)
 
     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)
-        if len(signal_data) == 1:
-            signal_data.append([])
-        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, xml_ui):
         """Hide the parameters item if there's nothing to display"""
         if not xml_ui:
@@ -558,48 +482,52 @@
                     _groups = set(mblog['groups'].split() if mblog['groups'] else [])
                 else:
                     _groups = None
-                mblog_entry = panels.MicroblogItem(mblog)
+                mblog_entry = blog.MicroblogItem(mblog)
                 cache.append((_groups, mblog_entry))
 
         self.mblog_cache.extend(cache)
         if len(self.mblog_cache) > MAX_MBLOG_CACHE:
             del self.mblog_cache[0:len(self.mblog_cache - MAX_MBLOG_CACHE)]
 
-        widget_list = [mblog_panel] if mblog_panel else self.libervia_widgets
-        for lib_wid in widget_list:
-            if isinstance(lib_wid, panels.MicroblogPanel):
-                self.fillMicroblogPanel(lib_wid, cache)
+        widget_list = [mblog_panel] if mblog_panel else self.widgets.getWidgets(blog.MicroblogPanel)
+
+        for wid in widget_list:
+            self.fillMicroblogPanel(wid, cache)
+
+        # FIXME
 
         if self.initialised:
             return
         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)
+            self.personalEventHandler(*event_data)
         del self.init_cache
 
     def _getProfileJidCB(self, jid_s):
-        self.whoami = jid.JID(jid_s)
-        #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('getMassiveMblogs', lib_wid.massiveInsert, 'ALL', [])
-                else:
-                    self.bridge.call('getMassiveMblogs', lib_wid.massiveInsert, 'GROUP', lib_wid.accepted_groups)
+        # FIXME
+        raise Exception("should not be here !")
+        # self.whoami = jid.JID(jid_s)
+        # #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, panel.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.loadOurMainEntries()
+        # #we ask for our own microblogs:
+        # self.loadOurMainEntries()
 
-        # initialize plugins which waited for the connection to be done
-        for plugin in self.plugins.values():
-            if hasattr(plugin, 'profileConnected'):
-                plugin.profileConnected()
+        # # initialize plugins which waited for the connection to be done
+        # for plugin in self.plugins.values():
+        #     if hasattr(plugin, 'profileConnected'):
+        #         plugin.profileConnected()
 
     def loadOurMainEntries(self, index=0, mblog_panel=None):
         """Load a page of our own blogs from the cache or ask them to the
@@ -610,7 +538,7 @@
         """
         delta = index - self.next_rsm_index
         if delta < 0:
-            assert(mblog_panel is not None)
+            assert mblog_panel is not None
             self.fillMicroblogPanel(mblog_panel, self.mblog_cache[index:index + C.RSM_MAX_ITEMS])
             return
 
@@ -618,12 +546,13 @@
             self._ownBlogsFills(result, mblog_panel)
 
         rsm = {'max': str(delta + C.RSM_MAX_ITEMS), 'index': str(self.next_rsm_index)}
-        self.bridge.call('getMassiveMblogs', cb, 'JID', [self.whoami.bare], rsm)
+        self.bridge.getMassiveMblogs('JID', [unicode(self.whoami.bare)], rsm, callback=cb, profile=C.PROF_KEY_NONE)
         self.next_rsm_index = index + C.RSM_MAX_ITEMS
 
     ## Signals callbacks ##
 
-    def _personalEventCb(self, sender, event_type, data):
+    def personalEventHandler(self, sender, event_type, data):
+        # FIXME: move some code from here to QuickApp
         if not self.initialised:
             self.init_cache.append((sender, event_type, data))
             return
@@ -636,11 +565,10 @@
                 _groups = set(data['groups'].split() if data['groups'] else [])
             else:
                 _groups = None
-            mblog_entry = panels.MicroblogItem(data)
+            mblog_entry = blog.MicroblogItem(data)
 
-            for lib_wid in self.libervia_widgets:
-                if isinstance(lib_wid, panels.MicroblogPanel):
-                    self.addBlogEntry(lib_wid, sender, _groups, mblog_entry)
+            for wid in self.widgets.getWidgets(blog.MicroblogPanel):
+                wid.addEntryIfAccepted(sender, _groups, mblog_entry)
 
             if sender == self.whoami.bare:
                 found = False
@@ -657,9 +585,8 @@
                     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'])
+            for wid in self.widgets.getWidgets(blog.MicroblogPanel):
+                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':
@@ -669,235 +596,276 @@
                         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: panels.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, mblogs):
         """Fill a microblog panel with entries in cache
+
         @param mblog_panel: MicroblogPanel instance
         """
         #XXX: only our own entries are cached
         for cache_entry in mblogs:
             _groups, mblog_entry = cache_entry
-            self.addBlogEntry(mblog_panel, self.whoami.bare, *cache_entry)
+            mblog_panel.addEntryIfAccepted(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 isinstance(lib_wid, blog.MicroblogPanel):
                 if lib_wid.isJidAccepted(entity):
-                    self.bridge.call('getMassiveMblogs', lib_wid.massiveInsert, 'JID', [entity])
+                    self.bridge.call('getMassiveMblogs', lib_wid.massiveInsert, 'JID', [unicode(entity)])
+
+    # def getLiberviaWidget(self, class_, entity, ignoreOtherTabs=True):
+    #     """Get the corresponding panel if it exists.
+    #     @param class_ (class): class of the panel (ChatPanel, MicroblogPanel...)
+    #     @param entity (dict): dictionnary to define the entity.
+    #     @param ignoreOtherTabs (bool): 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(expect=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.values())):  # XXX: passing **entity bugs!
+    #                     log.debug("existing widget found: %s" % lib_wid.getDebugName())
+    #                     return lib_wid
+    #             except AttributeError as e:
+    #                 e.stack_list()
+    #                 return None
+    #     return None
 
-    def getLiberviaWidget(self, class_, entity, ignoreOtherTabs=True):
-        """Get the corresponding panel if it exists.
-        @param class_ (class): class of the panel (ChatPanel, MicroblogPanel...)
-        @param entity (dict): dictionnary to define the entity.
-        @param ignoreOtherTabs (bool): 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(expect=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.values())):  # XXX: passing **entity bugs!
-                        log.debug("existing widget found: %s" % lib_wid.getDebugName())
-                        return lib_wid
-                except AttributeError as e:
-                    e.stack_list()
-                    return None
-        return None
+    def displayWidget(self, class_, target, dropped=False, new_tab=None, *args, **kwargs):
+        """Get or create a LiberviaWidget and select it. When the user dropped
+        something, a new widget is always created, otherwise we look for an
+        existing widget and re-use it if it's in the current tab.
+
+        @arg class_(class): see quick_widgets.getOrCreateWidget
+        @arg target: see quick_widgets.getOrCreateWidget
+        @arg dropped(bool): if True, assume the widget has been dropped
+        @arg new_tab(unicode): if not None, it holds the name of a new tab to
+            open for the widget. If None, use the default behavior.
+        @param args(list): optional args to create a new instance of class_
+        @param kwargs(list): optional kwargs to create a new instance of class_
+        @return: the widget
+        """
+        kwargs['profile'] = C.PROF_KEY_NONE
+
+        if dropped:
+            kwargs['on_new_widget'] = None
+            kwargs['on_existing_widget'] = C.WIDGET_RECREATE
+            wid = self.widgets.getOrCreateWidget(class_, target, *args, **kwargs)
+            self.setSelected(wid)
+            return wid
+
+        if new_tab:
+            kwargs['on_new_widget'] = None
+            kwargs['on_existing_widget'] = C.WIDGET_RECREATE
+            wid = self.widgets.getOrCreateWidget(class_, target, *args, **kwargs)
+            self.tab_panel.addWidgetsTab(new_tab)
+            self.addWidget(wid, tab_index=self.tab_panel.getWidgetCount() - 1)
+            return wid
 
-    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): class of the panel (ChatPanel, MicroblogPanel...)
-        @param entity (dict): dictionnary to define the entity.
-        @param select (bool): if True, select the widget that has been found or created
-        @param new_tab (str): 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.values()))  # XXX: passing **entity bugs!
-            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(expect=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
+        kwargs['on_existing_widget'] = C.WIDGET_RAISE
+        try:
+            wid = self.widgets.getOrCreateWidget(class_, target, *args, **kwargs)
+        except quick_widgets.WidgetAlreadyExistsError:
+            kwargs['on_existing_widget'] = C.WIDGET_KEEP
+            wid = self.widgets.getOrCreateWidget(class_, target, *args, **kwargs)
+            widgets_panel = wid.getParent(libervia_widget.WidgetsPanel, expect=False)
+            if widgets_panel is None:
+                # The widget exists but is hidden
+                self.addWidget(wid)
+            elif widgets_panel != self.tab_panel.getCurrentPanel():
+                # the widget is on an other tab, so we add a new one here
+                kwargs['on_existing_widget'] = C.WIDGET_RECREATE
+                wid = self.widgets.getOrCreateWidget(class_, target, *args, **kwargs)
+                self.addWidget(wid)
+        self.setSelected(wid)
+        return wid
+
 
-    def getRoomWidget(self, target):
-        """Get the MUC widget for the given target.
-
-        @param target (jid.JID): BARE jid of the MUC
-        @return: panels.ChatPanel instance or None
-        """
-        entity = {'item': target, 'type_': 'group'}
-        if target.full() in self.room_list or target in self.room_list:  # as JID is a string-based class, we don't know what will please Pyjamas...
-            return self.getLiberviaWidget(panels.ChatPanel, entity, ignoreOtherTabs=False)
-        return None
-
-    def getOrCreateRoomWidget(self, target):
-        """Get the MUC widget for the given target, create it if necessary.
+    # 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): class of the panel (ChatPanel, MicroblogPanel...)
+    #     @param entity (dict): dictionnary to define the entity.
+    #     @param select (bool): if True, select the widget that has been found or created
+    #     @param new_tab (unicode): 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.values()))  # XXX: passing **entity bugs!
+    #         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(expect=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
 
-        @param target (jid.JID): BARE jid of the MUC
-        @return: panels.ChatPanel instance
-        """
-        lib_wid = self.getRoomWidget(target)
-        if lib_wid:
-            return lib_wid
-
-        # XXX: target.node.startwith(...) raises an error "startswith is not a function"
-        # This happens when node a is property defined in the JID class
-        # FIXME: pyjamas doesn't handle the properties well
-        node = target.node
+    # def getRoomWidget(self, target):
+    #     """Get the MUC widget for the given target.
 
-        # XXX: it's not really beautiful, but it works :)
-        if node.startswith('sat_tarot_'):
-            tab_name = "Tarot"
-        elif node.startswith('sat_radiocol_'):
-            tab_name = "Radio collective"
-        else:
-            tab_name = target.node
+    #     @param target (jid.JID): BARE jid of the MUC
+    #     @return: panel.ChatPanel instance or None
+    #     """
+    #     entity = {'item': target, 'type_': 'group'}
+    #     if target.full() in self.room_list or target in self.room_list:  # as JID is a string-based class, we don't know what will please Pyjamas...
+    #         return self.getLiberviaWidget(panel.ChatPanel, entity, ignoreOtherTabs=False)
+    #     return None
 
-        self.room_list.append(target)
-        entity = {'item': target, 'type_': 'group'}
-        return self.getOrCreateLiberviaWidget(panels.ChatPanel, entity, new_tab=tab_name)
+    # def getOrCreateRoomWidget(self, target):
+    #     """Get the MUC widget for the given target, create it if necessary.
 
-    def _newMessageCb(self, from_jid_s, msg, msg_type, to_jid_s, extra):
-        from_jid = jid.JID(from_jid_s)
-        to_jid = jid.JID(to_jid_s)
-        for plugin in self.plugins.values():
-            if hasattr(plugin, 'messageReceivedTrigger'):
-                if not plugin.messageReceivedTrigger(from_jid, msg, msg_type, to_jid, extra):
-                    return  # plugin returned False to interrupt the process
-        self.newMessageCb(from_jid, msg, msg_type, to_jid, extra)
+    #     @param target (jid.JID): BARE jid of the MUC
+    #     @return: panel.ChatPanel instance
+    #     """
+    #     lib_wid = self.getRoomWidget(target)
+    #     if lib_wid:
+    #         return lib_wid
+
+    #     # XXX: target.node.startwith(...) raises an error "startswith is not a function"
+    #     # This happens when node a is property defined in the JID class
+    #     # FIXME: pyjamas doesn't handle the properties well
+    #     node = target.node
+
+    #     # XXX: it's not really beautiful, but it works :)
+    #     if node.startswith('sat_tarot_'):
+    #         tab_name = "Tarot"
+    #     elif node.startswith('sat_radiocol_'):
+    #         tab_name = "Radio collective"
+    #     else:
+    #         tab_name = target.node
+
+    #     self.room_list.append(target)
+    #     entity = {'item': target, 'type_': 'group'}
+    #     return self.getOrCreateLiberviaWidget(panel.ChatPanel, entity, new_tab=tab_name)
 
-    def newMessageCb(self, from_jid, msg, msg_type, to_jid, extra):
-        other = to_jid if from_jid.bare == self.whoami.bare else from_jid
-        lib_wid = self.getLiberviaWidget(panels.ChatPanel, {'item': other}, ignoreOtherTabs=False)
-        self.displayNotification(from_jid, msg)
-        if msg_type == 'headline' and from_jid.full() == self._defaultDomain:
-            try:
-                assert extra['subject']  # subject is defined and not empty
-                title = extra['subject']
-            except (KeyError, AssertionError):
-                title = _('Announcement from %s') % from_jid.full()
-            msg = strings.addURLToText(html_tools.XHTML2Text(msg))
-            dialog.InfoDialog(title, msg).show()
-            return
-        if lib_wid is not None:
-            if msg_type == C.MESS_TYPE_INFO:
-                lib_wid.printInfo(msg)
-            else:
-                lib_wid.printMessage(from_jid, msg, extra)
-            if 'header_info' in extra:
-                lib_wid.setHeaderInfo(extra['header_info'])
-        else:
-            # FIXME: "info" message and header info will be lost here
-            if not self.contact_panel.isContactInRoster(other.bare):
-                self.contact_panel.updateContact(other.bare, {}, [C.GROUP_NOT_IN_ROSTER])
-            # The message has not been shown, we must indicate it
-            self.contact_panel.setContactMessageWaiting(other.bare, True)
+    # def _newMessageCb(self, from_jid_s, msg, msg_type, to_jid_s, extra):
+    #     from_jid = jid.JID(from_jid_s)
+    #     to_jid = jid.JID(to_jid_s)
+    #     for plugin in self.plugins.values():
+    #         if hasattr(plugin, 'messageReceivedTrigger'):
+    #             if not plugin.messageReceivedTrigger(from_jid, msg, msg_type, to_jid, extra):
+    #                 return  # plugin returned False to interrupt the process
+    #     self.newMessageCb(from_jid, msg, msg_type, to_jid, extra)
+
+    # def newMessageCb(self, from_jid, msg, msg_type, to_jid, extra):
+    #     other = to_jid if from_jid.bare == self.whoami.bare else from_jid
+    #     lib_wid = self.getLiberviaWidget(panel.ChatPanel, {'item': other}, ignoreOtherTabs=False)
+    #     self.displayNotification(from_jid, msg)
+    #     if msg_type == 'headline' and from_jid.full() == self._defaultDomain:
+    #         try:
+    #             assert extra['subject']  # subject is defined and not empty
+    #             title = extra['subject']
+    #         except (KeyError, AssertionError):
+    #             title = _('Announcement from %s') % from_jid.full()
+    #         msg = strings.addURLToText(html_tools.XHTML2Text(msg))
+    #         dialog.InfoDialog(title, msg).show()
+    #         return
+    #     if lib_wid is not None:
+    #         if msg_type == C.MESS_TYPE_INFO:
+    #             lib_wid.printInfo(msg)
+    #         else:
+    #             lib_wid.printMessage(from_jid, msg, extra)
+    #         if 'header_info' in extra:
+    #             lib_wid.setHeaderInfo(extra['header_info'])
+    #     else:
+    #         # FIXME: "info" message and header info will be lost here
+    #         if not self.contact_panel.isContactInRoster(other.bare):
+    #             self.contact_panel.updateContact(other.bare, {}, [C.GROUP_NOT_IN_ROSTER])
+    #         # 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.JID(entity)
-        if self.whoami and self.whoami == entity_jid:  # XXX: QnD way to get our presence/status
-            assert(isinstance(self.status_panel, panels.PresenceStatusPanel))
-            self.status_panel.setPresence(show)  # pylint: disable=E1103
-            if statuses:
-                self.status_panel.setStatus(statuses.values()[0])  # pylint: disable=E1103
-        else:
-            bare_jid = entity_jid.bareJID()
-            if bare_jid.full() in self.room_list or bare_jid in self.room_list:  # as JID is a string-based class, we don't know what will please Pyjamas...
-                wid = self.getRoomWidget(bare_jid)
-            else:
-                wid = self.contact_panel
-                if show == 'unavailable':  # XXX: save some resources as for now we only need 'unavailable'
-                    for plugin in self.plugins.values():
-                        if hasattr(plugin, 'presenceReceivedTrigger'):
-                            plugin.presenceReceivedTrigger(entity_jid, show, priority, statuses)
-            if wid:
-                wid.setConnected(entity_jid.bare, entity_jid.resource, show, priority, statuses)
+    # def _presenceUpdateCb(self, entity, show, priority, statuses):
+    #     entity_jid = jid.JID(entity)
+    #     if self.whoami and self.whoami == entity_jid:  # XXX: QnD way to get our presence/status
+    #         assert(isinstance(self.status_panel, main_panel.PresenceStatusPanel))
+    #         self.status_panel.setPresence(show)  # pylint: disable=E1103
+    #         if statuses:
+    #             self.status_panel.setStatus(statuses.values()[0])  # pylint: disable=E1103
+    #     else:
+    #         bare_jid = entity_jid.bareJID()
+    #         if bare_jid.full() in self.room_list or bare_jid in self.room_list:  # as JID is a string-based class, we don't know what will please Pyjamas...
+    #             wid = self.getRoomWidget(bare_jid)
+    #         else:
+    #             wid = self.contact_panel
+    #             if show == 'unavailable':  # XXX: save some resources as for now we only need 'unavailable'
+    #                 for plugin in self.plugins.values():
+    #                     if hasattr(plugin, 'presenceReceivedTrigger'):
+    #                         plugin.presenceReceivedTrigger(entity_jid, show, priority, statuses)
+    #         if wid:
+    #             wid.setConnected(entity_jid.bare, entity_jid.resource, show, priority, statuses)
 
-    def _roomJoinedCb(self, room_jid_s, room_nicks, user_nick):
-        chat_panel = self.getOrCreateRoomWidget(jid.JID(room_jid_s))
-        chat_panel.setUserNick(user_nick)
-        chat_panel.setPresents(room_nicks)
-        chat_panel.refresh()
+    # def _roomJoinedCb(self, room_jid_s, room_nicks, user_nick):
+    #     chat_panel = self.getOrCreateRoomWidget(jid.JID(room_jid_s))
+    #     chat_panel.setUserNick(user_nick)
+    #     chat_panel.setPresents(room_nicks)
+    #     chat_panel.refresh()
 
-    def _roomLeftCb(self, room_jid_s, room_nicks, user_nick):
-        try:
-            del self.room_list[room_jid_s]
-        except KeyError:
-            try:  # as JID is a string-based class,  we don't know what will please Pyjamas...
-                del self.room_list[jid.JID(room_jid_s)]
-            except KeyError:
-                pass
+    # def _roomLeftCb(self, room_jid_s, room_nicks, user_nick):
+    #     try:
+    #         del self.room_list[room_jid_s]
+    #     except KeyError:
+    #         try:  # as JID is a string-based class,  we don't know what will please Pyjamas...
+    #             del self.room_list[jid.JID(room_jid_s)]
+    #         except KeyError:
+    #             pass
 
-    def _roomUserJoinedCb(self, room_jid_s, user_nick, user_data):
-        lib_wid = self.getOrCreateRoomWidget(jid.JID(room_jid_s))
-        if lib_wid:
-            lib_wid.userJoined(user_nick, user_data)
+    # def _roomUserJoinedCb(self, room_jid_s, user_nick, user_data):
+    #     lib_wid = self.getOrCreateRoomWidget(jid.JID(room_jid_s))
+    #     if lib_wid:
+    #         lib_wid.userJoined(user_nick, user_data)
 
-    def _roomUserLeftCb(self, room_jid_s, user_nick, user_data):
-        lib_wid = self.getRoomWidget(jid.JID(room_jid_s))
-        if lib_wid:
-            lib_wid.userLeft(user_nick, user_data)
+    # def _roomUserLeftCb(self, room_jid_s, user_nick, user_data):
+    #     lib_wid = self.getRoomWidget(jid.JID(room_jid_s))
+    #     if lib_wid:
+    #         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"""
-        lib_wid = self.getRoomWidget(jid.JID(room_jid_s))
-        if lib_wid:
-            lib_wid.changeUserNick(old_nick, new_nick)
+    # def _roomUserChangedNickCb(self, room_jid_s, old_nick, new_nick):
+    #     """Called when an user joined a MUC room"""
+    #     lib_wid = self.getRoomWidget(jid.JID(room_jid_s))
+    #     if lib_wid:
+    #         lib_wid.changeUserNick(old_nick, new_nick)
 
-    def _tarotGameStartedCb(self, waiting, room_jid_s, referee, players):
-        lib_wid = self.getRoomWidget(jid.JID(room_jid_s))
-        if lib_wid:
-            lib_wid.startGame("Tarot", waiting, referee, players)
+    # def _tarotGameStartedCb(self, waiting, room_jid_s, referee, players):
+    #     lib_wid = self.getRoomWidget(jid.JID(room_jid_s))
+    #     if lib_wid:
+    #         lib_wid.startGame("Tarot", waiting, referee, players)
 
-    def _tarotGameGenericCb(self, event_name, room_jid_s, args):
-        lib_wid = self.getRoomWidget(jid.JID(room_jid_s))
-        if lib_wid:
-            getattr(lib_wid.getGame("Tarot"), event_name)(*args)
+    # def _tarotGameGenericCb(self, event_name, room_jid_s, args):
+    #     lib_wid = self.getRoomWidget(jid.JID(room_jid_s))
+    #     if lib_wid:
+    #         getattr(lib_wid.getGame("Tarot"), event_name)(*args)
 
-    def _radioColStartedCb(self, waiting, room_jid_s, referee, players, queue_data):
-        lib_wid = self.getRoomWidget(jid.JID(room_jid_s))
-        if lib_wid:
-            lib_wid.startGame("RadioCol", waiting, referee, players, queue_data)
+    # def _radioColStartedCb(self, waiting, room_jid_s, referee, players, queue_data):
+    #     lib_wid = self.getRoomWidget(jid.JID(room_jid_s))
+    #     if lib_wid:
+    #         lib_wid.startGame("RadioCol", waiting, referee, players, queue_data)
 
-    def _radioColGenericCb(self, event_name, room_jid_s, args):
-        lib_wid = self.getRoomWidget(jid.JID(room_jid_s))
-        if lib_wid:
-            getattr(lib_wid.getGame("RadioCol"), event_name)(*args)
+    # def _radioColGenericCb(self, event_name, room_jid_s, args):
+    #     lib_wid = self.getRoomWidget(jid.JID(room_jid_s))
+    #     if lib_wid:
+    #         getattr(lib_wid.getGame("RadioCol"), event_name)(*args)
 
     def _getPresenceStatusesCb(self, presence_data):
         for entity in presence_data:
@@ -929,7 +897,8 @@
             msg = HTML('The contact <b>%s</b> want to add you in his/her contact list, do you accept ?' % html_tools.html_sanitize(entity))
 
             def ok_cb(ignore):
-                self.bridge.call('subscription', None, "subscribed", entity, '', _dialog.getSelectedGroups())
+                self.bridge.call('subscription', None, "subscribed", entity)
+                self.bridge.updateContact(entity, '', _dialog.getSelectedGroups())
 
             def cancel_cb(ignore):
                 self.bridge.call('subscription', None, "unsubscribed", entity, '', '')
@@ -945,31 +914,32 @@
         self.contact_panel.updateContact(contact_jid, attributes, groups)
 
     def _entityDataUpdatedCb(self, entity_jid_s, key, value):
+        raise Exception # FIXME should not be here
         if key == "avatar":
             avatar = '/' + C.AVATARS_DIR + value
             self.avatars_cache[entity_jid_s] = avatar
             self.contact_panel.updateAvatar(entity_jid_s, avatar)
 
             for lib_wid in self.libervia_widgets:
-                if isinstance(lib_wid, panels.MicroblogPanel):
+                if isinstance(lib_wid, blog.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@':
-            for lib_wid in self.libervia_widgets:
-                if isinstance(lib_wid, panels.ChatPanel):
-                    lib_wid.setState(state, nick=C.ALL_OCCUPANTS)
-            return
-        from_jid = jid.JID(from_jid_s)
-        lib_wid = self.getLiberviaWidget(panels.ChatPanel, {'item': from_jid}, ignoreOtherTabs=False)
-        lib_wid.setState(state, nick=from_jid.resource)
+    # 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 (unicode): new state
+    #     """
+    #     if from_jid_s == '@ALL@':
+    #         for lib_wid in self.libervia_widgets:
+    #             if isinstance(lib_wid, panel.ChatPanel):
+    #                 lib_wid.setState(state, nick=C.ALL_OCCUPANTS)
+    #         return
+    #     from_jid = jid.JID(from_jid_s)
+    #     lib_wid = self.getLiberviaWidget(panel.ChatPanel, {'item': from_jid}, ignoreOtherTabs=False)
+    #     lib_wid.setState(state, nick=from_jid.resource)
 
-    def _askConfirmation(self, confirmation_id, confirmation_type, data):
+    def askConfirmationHandler(self, confirmation_id, confirmation_type, data):
         answer_data = {}
 
         def confirm_cb(result):
@@ -996,51 +966,53 @@
     def getCachedParam(self, category, name):
         """Return a parameter cached value (e.g for refreshing the UI)
 
-        @param category (str): the parameter category
-        @pram name (str): the parameter name
+        @param category (unicode): the parameter category
+        @pram name (unicode): the parameter name
         """
         return self.cached_params[(category, name)] if (category, name) in self.cached_params else None
 
     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))
+        log.error("sendError: %s" % unicode(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":
-                assert(isinstance(self.status_panel, panels.PresenceStatusPanel))
-                self.bridge.call('setStatus', None, self.status_panel.presence, text)  # pylint: disable=E1103
-            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':
-                to_jid_s = addresses[0][1]
-                for plugin in self.plugins.values():
-                    if hasattr(plugin, 'sendMessageTrigger'):
-                        if not plugin.sendMessageTrigger(jid.JID(to_jid_s), text, type_, extra):
-                            return  # plugin returned False to interrupt the process
-                self.bridge.call('sendMessage', (None, self.sendError), to_jid_s, 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)
+    # FIXME: this method is fat too complicated and depend of widget type
+    #        must be refactored and moved to each widget instead
+    # 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":
+    #             assert(isinstance(self.status_panel, main_panel.PresenceStatusPanel))
+    #             self.bridge.call('setStatus', None, self.status_panel.presence, text)  # pylint: disable=E1103
+    #         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':
+    #             to_jid_s = addresses[0][1]
+    #             for plugin in self.plugins.values():
+    #                 if hasattr(plugin, 'sendMessageTrigger'):
+    #                     if not plugin.sendMessageTrigger(jid.JID(to_jid_s), text, type_, extra):
+    #                         return  # plugin returned False to interrupt the process
+    #             self.bridge.call('sendMessage', (None, self.sendError), to_jid_s, 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.
@@ -1049,11 +1021,25 @@
         @msg: message to be displayed
         """
         if not hasattr(self, "warning_popup"):
-            self.warning_popup = panels.WarningPopup()
+            self.warning_popup = main_panel.WarningPopup()
         self.warning_popup.showWarning(type_, msg)
 
+    def showDialog(self, message, title="", type_="info", answer_cb=None, answer_data=None):
+        if type_ == 'info':
+            popup = dialog.InfoDialog(unicode(title), unicode(message), callback=answer_cb)
+        elif type_ == 'error':
+            popup = dialog.InfoDialog(unicode(title), unicode(message), callback=answer_cb)
+        elif type_ == 'yes/no':
+            popup = dialog.ConfirmDialog(lambda answer: answer_cb(answer, answer_data),
+                                         text=unicode(message), title=unicode(title))
+            popup.cancel_button.setText(_("No"))
+        else:
+            popup = dialog.InfoDialog(unicode(title), unicode(message), callback=answer_cb)
+            log.error(_('unmanaged dialog type: %s'), type_)
+        popup.show()
+
 
 if __name__ == '__main__':
     app = SatWebFrontend()
     app.onModuleLoad()
-    pyjd.run()
+    host_listener.callListeners(app)