view frontends/src/primitivus/primitivus @ 1265:e3a9ea76de35 frontends_multi_profiles

quick_frontend, primitivus: multi-profiles refactoring part 1 (big commit, sorry :p): This refactoring allow primitivus to manage correctly several profiles at once, with various other improvments: - profile_manager can now plug several profiles at once, requesting password when needed. No more profile plug specific method is used anymore in backend, instead a "validated" key is used in actions - Primitivus widget are now based on a common "PrimitivusWidget" classe which mainly manage the decoration so far - all widgets are treated in the same way (contactList, Chat, Progress, etc), no more chat_wins specific behaviour - widgets are created in a dedicated manager, with facilities to react on new widget creation or other events - quick_frontend introduce a new QuickWidget class, which aims to be as generic and flexible as possible. It can manage several targets (jids or something else), and several profiles - each widget class return a Hash according to its target. For example if given a target jid and a profile, a widget class return a hash like (target.bare, profile), the same widget will be used for all resources of the same jid - better management of CHAT_GROUP mode for Chat widgets - some code moved from Primitivus to QuickFrontend, the final goal is to have most non backend code in QuickFrontend, and just graphic code in subclasses - no more (un)escapePrivate/PRIVATE_PREFIX - contactList improved a lot: entities not in roster and special entities (private MUC conversations) are better managed - resources can be displayed in Primitivus, and their status messages - profiles are managed in QuickFrontend with dedicated managers This is work in progress, other frontends are broken. Urwid SàText need to be updated. Most of features of Primitivus should work as before (or in a better way ;))
author Goffi <goffi@goffi.org>
date Wed, 10 Dec 2014 19:00:09 +0100
parents 82dabb442e2e
children faa1129559b8
line wrap: on
line source

#!/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.primitivus.constants import Const as C
from sat.core import log_config
log_config.satConfigure(C.LOG_BACKEND_STANDARD, C)
from sat.core import log as logging
log = logging.getLogger(__name__)
import urwid
from urwid_satext import sat_widgets
from urwid_satext.files_management import FileDialog
from sat_frontends.quick_frontend.quick_app import QuickApp
from sat_frontends.quick_frontend.quick_utils import getNewPath
from sat_frontends.quick_frontend import quick_chat
from sat_frontends.primitivus.profile_manager import ProfileManager
from sat_frontends.primitivus.contact_list import ContactList
from sat_frontends.primitivus.chat import Chat
from sat_frontends.primitivus import xmlui
from sat_frontends.primitivus.progress import Progress
from sat_frontends.primitivus.notify import Notify
from sat_frontends.primitivus.keys import action_key_map as a_key
from sat_frontends.primitivus import config
from sat_frontends.tools.misc import InputHistory
from sat_frontends.tools import jid
from os.path import join
import signal



class EditBar(sat_widgets.ModalEdit):
    """
    The modal edit bar where you would enter messages and commands.
    """

    def __init__(self, host):
        modes = {None: (C.MODE_NORMAL, u''),
                 a_key['MODE_INSERTION']: (C.MODE_INSERTION, u'> '),
                 a_key['MODE_COMMAND']: (C.MODE_COMMAND, u':')} #XXX: captions *MUST* be unicode
        super(EditBar, self).__init__(modes)
        self.host = host
        self.setCompletionMethod(self._text_completion)
        urwid.connect_signal(self, 'click', self.onTextEntered)

    def _text_completion(self, text, completion_data, mode):
        if mode == C.MODE_INSERTION:
            return self._nick_completion(text, completion_data)
        else:
            return text

    def _nick_completion(self, text, completion_data):
        """Completion method which complete pseudo in group chat
        for params, see AdvancedEdit"""
        contact = self.host.contact_list.getContact() ###Based on the fact that there is currently only one contact selectable at once
        if contact:
            chat = self.host.chat_wins[contact]
            if chat.type != "group":
                return text
            space = text.rfind(" ")
            start = text[space+1:]
            nicks = list(chat.occupants)
            nicks.sort()
            try:
                start_idx=nicks.index(completion_data['last_nick'])+1
                if start_idx == len(nicks):
                    start_idx = 0
            except (KeyError,ValueError):
                start_idx = 0
            for idx in range(start_idx,len(nicks)) + range(0,start_idx):
                if nicks[idx].lower().startswith(start.lower()):
                    completion_data['last_nick'] = nicks[idx]
                    return text[:space+1] + nicks[idx] + (': ' if space < 0 else '')
        return text

    def onTextEntered(self, editBar):
        """Called when text is entered in the main edit bar"""
        if self.mode == C.MODE_INSERTION:
            if isinstance(self.host.selected_widget, quick_chat.QuickChat):
                chat_widget = self.host.selected_widget
                self.host.sendMessage(chat_widget.target,
                                     editBar.get_edit_text(),
                                     mess_type = "groupchat" if chat_widget.type == 'group' else "chat", # TODO: put this in QuickChat
                                     errback=lambda failure: self.host.notify(_("Error while sending message ({})").format(failure)),
                                     profile_key=chat_widget.profile
                                    )
                editBar.set_edit_text('')
        elif self.mode == C.MODE_COMMAND:
            self.commandHandler()

    def commandHandler(self):
        #TODO: separate class with auto documentation (with introspection)
        #      and completion method
        tokens = self.get_edit_text().split(' ')
        command, args = tokens[0], tokens[1:]
        if command == 'quit':
            self.host.onExit()
            raise urwid.ExitMainLoop()
        elif command == 'messages':
            wid = sat_widgets.GenericList(logging.memoryGet())
            self.host.selectWidget(wid)
        # elif command == 'presence':
        #     values = [value for value in commonConst.PRESENCE.keys()]
        #     values = [value if value else 'online' for value in values]  # the empty value actually means 'online'
        #     if args and args[0] in values:
        #         presence = '' if args[0] == 'online' else args[0]
        #         self.host.status_bar.onChange(user_data=sat_widgets.ClickableText(commonConst.PRESENCE[presence]))
        #     else:
        #         self.host.status_bar.onPresenceClick()
        # elif command == 'status':
        #     if args:
        #         self.host.status_bar.onChange(user_data=sat_widgets.AdvancedEdit(args[0]))
        #     else:
        #         self.host.status_bar.onStatusClick()
        elif command == 'history':
            widget = self.host.selected_widget
            if isinstance(widget, quick_chat.QuickChat):
                try:
                    limit = int(args[0])
                except (IndexError, ValueError):
                    limit = 50
                widget.clearHistory()
                if limit > 0:
                    widget.historyPrint(size=limit, profile=widget.profile)
        elif command == 'search':
            widget = self.host.selected_widget
            if isinstance(widget, quick_chat.QuickChat):
                pattern = " ".join(args)
                if not pattern:
                    self.host.notif_bar.addMessage(D_("Please specify the globbing pattern to search for"))
                widget.clearHistory()
                widget.printInfo(D_("Results for searching the globbing pattern: %s") % pattern, timestamp=0)
                widget.historyPrint(size=C.HISTORY_LIMIT_NONE, search=pattern, profile=widget.profile)
                widget.printInfo(D_("Type ':history <lines>' to reset the chat history"))
        else:
            return
        self.set_edit_text('')

    def _historyCb(self, text):
        self.set_edit_text(text)
        self.set_edit_pos(len(text))

    def keypress(self, size, key):
        """Callback when a key is pressed. Send "composing" states
        and move the index of the temporary history stack."""
        if key == a_key['MODAL_ESCAPE']:
            # first save the text to the current mode, then change to NORMAL
            self.host._updateInputHistory(self.get_edit_text(), mode=self.mode)
            self.host._updateInputHistory(mode=C.MODE_NORMAL)
        if self._mode == C.MODE_NORMAL and key in self._modes:
            self.host._updateInputHistory(mode=self._modes[key][0])
        if key == a_key['HISTORY_PREV']:
            self.host._updateInputHistory(self.get_edit_text(), -1, self._historyCb, self.mode)
            return
        elif key == a_key['HISTORY_NEXT']:
            self.host._updateInputHistory(self.get_edit_text(), +1, self._historyCb, self.mode)
            return
        elif key == a_key['EDIT_ENTER']:
            self.host._updateInputHistory(self.get_edit_text(), mode=self.mode)
        else:
            if (self._mode == C.MODE_INSERTION
                and isinstance(self.host.selected_widget, quick_chat.QuickChat)
                and key not in sat_widgets.FOCUS_KEYS):
                self.host.bridge.chatStateComposing(self.host.selected_widget.target, self.host.selected_widget.profile)

        return super(EditBar, self).keypress(size, key)


class PrimitivusTopWidget(sat_widgets.FocusPile):
    """Top most widget used in Primitivus"""
    _focus_inversed = True
    positions = ('menu', 'body', 'notif_bar', 'edit_bar')
    can_hide = ('menu', 'notif_bar')

    def __init__(self, body, menu, notif_bar, edit_bar):
        self._body = body
        self._menu = menu
        self._notif_bar = notif_bar
        self._edit_bar = edit_bar
        self._hidden = {'notif_bar'}
        self._focus_extra = False
        super(PrimitivusTopWidget, self).__init__([('pack', self._menu), self._body, ('pack', self._edit_bar)])
        for position in self.positions:
            setattr(self,
                    position,
                    property(lambda: self, self.widgetGet(position=position),
                             lambda pos, new_wid: self.widgetSet(new_wid, position=pos))
                   )
        self.focus_position = len(self.contents)-1

    def getVisiblePositions(self, keep=None):
        """Return positions that are not hidden in the right order

        @param keep: if not None, this position will be keep in the right order, even if it's hidden
                    (can be useful to find its index)
        @return (list): list of visible positions
        """
        return [pos for pos in self.positions if (keep and pos == keep) or pos not in self._hidden]

    def keypress(self, size, key):
        """Manage FOCUS keys that focus directly a main part (one of self.positions)

        To avoid key conflicts, a combinaison must be made with FOCUS_EXTRA then an other key
        """
        if key == a_key['FOCUS_EXTRA']:
            self._focus_extra = True
            return
        if self._focus_extra:
            self._focus_extra = False
            if key in ('m', '1'):
                focus = 'menu'
            elif key in ('b', '2'):
                focus = 'body'
            elif key in ('n', '3'):
                focus = 'notif_bar'
            elif key in ('e', '4'):
                focus = 'edit_bar'
            else:
                return super(PrimitivusTopWidget, self).keypress(size, key)

            if focus in self._hidden:
                return

            self.focus_position = self.getVisiblePositions().index(focus)
            return

        return super(PrimitivusTopWidget, self).keypress(size, key)

    def widgetGet(self,  position):
        if not position in self.positions:
            raise ValueError("Unknown position {}".format(position))
        return getattr(self, "_{}".format(position))

    def widgetSet(self,  widget, position):
        if not position in self.positions:
            raise ValueError("Unknown position {}".format(position))
        return setattr(self, "_{}".format(position), widget)

    def hideSwitch(self, position):
        if not position in self.can_hide:
            raise ValueError("Can't switch position {}".format(position))
        hide = not position in self._hidden
        widget = self.widgetGet(position)
        idx = self.getVisiblePositions(position).index(position)
        if hide:
            del self.contents[idx]
            self._hidden.add(position)
        else:
            self.contents.insert(idx, (widget, ('pack', None)))
            self._hidden.remove(position)

    def show(self, position):
        if position in self._hidden:
            self.hideSwitch(position)

    def hide(self, position):
        if not position in self._hidden:
            self.hideSwitch(position)


class PrimitivusApp(QuickApp, InputHistory):

    def __init__(self):
        QuickApp.__init__(self)

        ## main loop setup ##
        self.main_widget = ProfileManager(self)
        self.loop = urwid.MainLoop(self.main_widget, C.PALETTE, event_loop=urwid.GLibEventLoop(), input_filter=self.inputFilter, unhandled_input=self.keyHandler)


        ##misc setup##
        self.notif_bar = sat_widgets.NotificationBar()
        urwid.connect_signal(self.notif_bar, 'change', self.onNotification)

        self.progress_wid = self.widgets.getOrCreateWidget(Progress, None, on_new_widget=None)
        urwid.connect_signal(self.notif_bar.progress, 'click', lambda x: self.selectWidget(self.progress_wid))
        self.__saved_overlay = None

        self.x_notify = Notify()

        # we already manage exit with a_key['APP_QUIT'], so we don't want C-c
        signal.signal(signal.SIGINT, signal.SIG_IGN)

    @property
    def mode(self):
        return self.editBar.mode

    @mode.setter
    def mode(self, value):
        self.editBar.mode = value

    def modeHint(self, value):
        """Change mode if make sens (i.e.: if there is nothing in the editBar)"""
        if not self.editBar.get_edit_text():
            self.mode = value

    def debug(self):
        """convenient method to reset screen and launch (i)p(u)db"""
        log.info('Entered debug mode')
        try:
            import pudb
            pudb.set_trace()
        except ImportError:
            import os
            os.system('reset')
            try:
                import ipdb
                ipdb.set_trace()
            except ImportError:
                import pdb
                pdb.set_trace()

    def redraw(self):
        """redraw the screen"""
        try:
            self.loop.draw_screen()
        except AttributeError:
            pass

    def start(self):
        self.i = 0
        self.loop.set_alarm_in(0,lambda a,b: self.postInit())
        self.loop.run()

    def postInit(self):
        try:
            config.applyConfig(self)
        except Exception as e:
            log.error("configuration error: {}".format(e))
            popup = sat_widgets.Alert(_("Configuration Error"), _("Something went wrong while reading the configuration, please check :messages"), ok_cb=self.removePopUp)
            if self.options.profile:
                self._early_popup = popup
            else:
                self.showPopUp(popup)
        super(PrimitivusApp, self).postInit(self.main_widget)

    def inputFilter(self, input_, raw):
        if self.__saved_overlay and input_ != a_key['OVERLAY_HIDE']:
            return
        for i in input_:
            if isinstance(i,tuple):
                if i[0] == 'mouse press':
                    if i[1] == 4: #Mouse wheel up
                        input_[input_.index(i)] = a_key['HISTORY_PREV']
                    if i[1] == 5: #Mouse wheel down
                        input_[input_.index(i)] = a_key['HISTORY_NEXT']
        return input_

    def keyHandler(self, input_):
        if input_ == a_key['MENU_HIDE']:
            """User want to (un)hide the menu roller"""
            try:
                self.main_widget.hideSwitch('menu')
            except AttributeError:
                pass
        elif input_ == a_key['NOTIFICATION_NEXT']:
            """User wants to see next notification"""
            self.notif_bar.showNext()
        elif input_ == a_key['OVERLAY_HIDE']:
            """User wants to (un)hide overlay window"""
            if isinstance(self.loop.widget,urwid.Overlay):
                self.__saved_overlay = self.loop.widget
                self.loop.widget = self.main_widget
            else:
                if self.__saved_overlay:
                    self.loop.widget = self.__saved_overlay
                    self.__saved_overlay = None

        elif input_ == a_key['DEBUG'] and 'D' in self.bridge.getVersion(): #Debug only for dev versions
            self.debug()
        elif input_ == a_key['CONTACTS_HIDE']: #user wants to (un)hide the contact lists
            try:
                for wid, options in self.center_part.contents:
                    if self.contact_lists_pile is wid:
                        self.center_part.contents.remove((wid, options))
                        break
                else:
                    self.center_part.contents.insert(0, (self.contact_lists_pile, ('weight', 2, False)))
            except AttributeError:
                #The main widget is not built (probably in Profile Manager)
                pass
        elif input_ == 'window resize':
            width,height = self.loop.screen_size
            if height<=5 and width<=35:
                if not 'save_main_widget' in dir(self):
                    self.save_main_widget = self.loop.widget
                    self.loop.widget = urwid.Filler(urwid.Text(_("Pleeeeasse, I can't even breathe !")))
            else:
                if 'save_main_widget' in dir(self):
                    self.loop.widget = self.save_main_widget
                    del self.save_main_widget
        try:
            return self.menu_roller.checkShortcuts(input_)
        except AttributeError:
            return input_

    def addMenus(self, menu, type_filter, menu_data=None):
        """Add cached menus to instance
        @param menu: sat_widgets.Menu instance
        @param type_filter: menu type like is sat.core.sat_main.importMenu
        @param menu_data: data to send with these menus

        """
        def add_menu_cb(callback_id):
            self.launchAction(callback_id, menu_data, profile=self.current_profile)
        for id_, type_, path, path_i18n  in self.bridge.getMenus("", C.NO_SECURITY_LIMIT ):
            if type_ != type_filter:
                continue
            if len(path) != 2:
                raise NotImplementedError("Menu with a path != 2 are not implemented yet")
            menu.addMenu(path_i18n[0], path_i18n[1], lambda dummy,id_=id_: add_menu_cb(id_))


    def _buildMenuRoller(self):
        menu = sat_widgets.Menu(self.loop)
        general = _("General")
        menu.addMenu(general, _("Connect"), self.onConnectRequest)
        menu.addMenu(general, _("Disconnect"), self.onDisconnectRequest)
        menu.addMenu(general, _("Parameters"), self.onParam)
        menu.addMenu(general, _("About"), self.onAboutRequest)
        menu.addMenu(general, _("Exit"), self.onExitRequest, a_key['APP_QUIT'])
        contact = _("Contacts")
        menu.addMenu(contact)
        communication = _("Communication")
        menu.addMenu(communication, _("Join room"), self.onJoinRoomRequest, a_key['ROOM_JOIN'])
        #additionals menus
        #FIXME: do this in a more generic way (in quickapp)
        self.addMenus(menu, C.MENU_GLOBAL)

        menu_roller = sat_widgets.MenuRoller([(_('Main menu'), menu, C.MENU_ID_MAIN)])
        return menu_roller

    def _buildMainWidget(self):
        self.contact_lists_pile = urwid.Pile([])
        #self.center_part = urwid.Columns([('weight',2,self.contact_lists[profile]),('weight',8,Chat('',self))])
        self.center_part = urwid.Columns([('weight', 2, self.contact_lists_pile), ('weight', 8, urwid.Filler(urwid.Text('')))])

        self.editBar = EditBar(self)
        self.menu_roller = self._buildMenuRoller()
        self.main_widget = PrimitivusTopWidget(self.center_part, self.menu_roller, self.notif_bar, self.editBar)
        return self.main_widget

    def addContactList(self, profile):
        contact_list = ContactList(self, on_click=self.contactSelected, on_change=lambda w: self.redraw(), profile=profile)
        self.contact_lists_pile.contents.append((contact_list, ('weight', 1)))
        self.contact_lists[profile] = contact_list
        return contact_list

    def plugging_profiles(self):
        self.loop.widget = self._buildMainWidget()
        self.redraw()
        try:
            # if a popup arrived before main widget is build, we need to show it now
            self.showPopUp(self._early_popup)
        except AttributeError:
            pass
        else:
            del self._early_popup

    def removePopUp(self, widget=None):
        "Remove current pop-up, and if there is other in queue, show it"
        self.loop.widget = self.main_widget
        next_popup = self.notif_bar.getNextPopup()
        if next_popup:
            #we still have popup to show, we display it
            self.showPopUp(next_popup)

    def showPopUp(self, pop_up_widget, perc_width=40, perc_height=40, align='center', valign='middle'):
        "Show a pop-up window if possible, else put it in queue"
        if not isinstance(self.loop.widget, urwid.Overlay):
            display_widget = urwid.Overlay(pop_up_widget, self.main_widget, align, ('relative', perc_width), valign, ('relative', perc_height))
            self.loop.widget = display_widget
            self.redraw()
        else:
            self.notif_bar.addPopUp(pop_up_widget)

    def notify(self, message):
        """"Notify message to user via notification bar"""
        self.notif_bar.addMessage(message)
        self.redraw()

    def newWidget(self, widget):
        """Display a widget if possible,

        else add it in the notification bar queue
        @param widget: BoxWidget
        """
        self.selectWidget(widget)

    def selectWidget(self, widget):
        assert len(self.center_part.widget_list)<=2
        wid_idx = len(self.center_part.widget_list)-1
        self.center_part.widget_list[wid_idx] = widget
        try:
            self.menu_roller.removeMenu(C.MENU_ID_WIDGET)
        except KeyError:
            log.debug("No menu to delete")
        self.selected_widget = widget
        self.visible_widgets = set([widget]) # XXX: we can only have one widget visible at the time for now
        for contact_list in self.contact_lists.itervalues():
            contact_list.unselectAll()

        for wid in self.visible_widgets:
            if isinstance(wid, Chat):
                contact_list = self.contact_lists[wid.profile]
                contact_list.select(wid.target)

        self.redraw()

    def removeWindow(self):
        """Remove window showed on the right column"""
        #TODO: to a better Window management than this crappy hack
        assert(len(self.center_part.widget_list)<=2)
        wid_idx = len(self.center_part.widget_list)-1
        self.center_part.widget_list[wid_idx] = urwid.Filler(urwid.Text(''))
        self.center_part.focus_position = 0
        self.redraw()

    def addProgress (self, id, message):
        """Follow a SàT progress bar
        @param id: SàT id of the progression
        @param message: message to show to identify the progression"""
        self.progress_wid.addProgress(id, message)

    def setProgress(self, percentage):
        """Set the progression shown in notification bar"""
        self.notif_bar.setProgress(percentage)

    def contactSelected(self, contact_list, entity):
        if entity.resource:
            # we have clicked on a private MUC conversation
            chat_widget = self.widgets.getOrCreateWidget(Chat, entity, on_new_widget=None, force_hash = Chat.getPrivateHash(contact_list.profile, entity), profile=contact_list.profile)
        else:
            chat_widget = self.widgets.getOrCreateWidget(Chat, entity, on_new_widget=None, profile=contact_list.profile)
        self.selectWidget(chat_widget)
        self.menu_roller.addMenu(_('Chat menu'), chat_widget.getMenu(), C.MENU_ID_WIDGET)

    def newMessageHandler(self, from_jid, to_jid, msg, type_, extra, profile):
        QuickApp.newMessageHandler(self, from_jid, to_jid, msg, type_, extra, profile)

        if not from_jid in self.contact_lists[profile] and from_jid.bare != self.profiles[profile].whoami.bare:
            #XXX: needed to show entities which haven't sent any
            #     presence information and which are not in roster
            self.contact_lists[profile].setContact(from_jid)
        visible = False
        for widget in self.visible_widgets:
            if isinstance(widget, Chat) and widget.manageMessage(from_jid, type_):
                visible = True
                break
        if not visible:
            self.contact_lists[profile].setAlert(from_jid.bare if type_ == C.MESS_TYPE_GROUPCHAT else from_jid)

    def _dialogOkCb(self, widget, data):
        self.removePopUp()
        answer_cb = data[0]
        answer_data = [data[1]] if data[1] else []
        answer_cb(True, *answer_data)

    def _dialogCancelCb(self, widget, data):
        self.removePopUp()
        answer_cb = data[0]
        answer_data = [data[1]] if data[1] else []
        answer_cb(False, *answer_data)

    def showDialog(self, message, title="", type_="info", answer_cb = None, answer_data = None):
        if type_ == 'info':
            popup = sat_widgets.Alert(unicode(title), unicode(message), ok_cb=answer_cb or self.removePopUp) #FIXME: remove unicode here when DBus Bridge will no return dbus.String anymore
        elif type_ == 'error':
            popup = sat_widgets.Alert(unicode(title), unicode(message), ok_cb=answer_cb or self.removePopUp) #FIXME: remove unicode here when DBus Bridge will no return dbus.String anymore
        elif type_ == 'yes/no':
            popup = sat_widgets.ConfirmDialog(unicode(message),
                    yes_cb=self._dialogOkCb, yes_value = (answer_cb, answer_data),
                    no_cb=self._dialogCancelCb, no_value = (answer_cb, answer_data))
        else:
            popup = sat_widgets.Alert(unicode(title), unicode(message), ok_cb=answer_cb or self.removePopUp) #FIXME: remove unicode here when DBus Bridge will no return dbus.String anymore
            log.error(_('unmanaged dialog type: %s'), type_)
        self.showPopUp(popup)

    def onNotification(self, notif_bar):
        """Called when a new notification has been received"""
        if not isinstance(self.main_widget, PrimitivusTopWidget):
            #if we are not in the main configuration, we ignore the notifications bar
            return
        if self.notif_bar.canHide():
                #No notification left, we can hide the bar
                self.main_widget.hide('notif_bar')
        else:
            self.main_widget.show('notif_bar')

    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
        @param callback: if not None and 'validated' key is present, it will be called with the following parameters:
            - callback_id
            - data
            - profile
        @param profile: %(doc_profile)s

        """
        if data is None:
            data = dict()

        def action_cb(data):
            if not data:
                # action was a one shot, nothing to do
                pass
            elif "xmlui" in data:
                ui = xmlui.create(self, xml_data=data['xmlui'], callback=callback, profile=profile)
                ui.show()
            elif 'validated' in data:
                pass # this key is managed below
            else:
                self.showPopUp(sat_widgets.Alert(_("Error"), _(u"Unmanaged action result"), ok_cb=self.removePopUp))

            if callback and 'validated' in data:
                callback(callback_id, data, profile)

        def action_eb(failure):
            self.showPopUp(sat_widgets.Alert(failure.fullname, failure.message, ok_cb=self.removePopUp))

        self.bridge.launchAction(callback_id, data, profile, callback=action_cb, errback=action_eb)

    def askConfirmationHandler(self, confirmation_id, confirmation_type, data, profile):
        answer_data={}

        def dir_selected_cb(path):
            dest_path = join(path, data['filename'])
            answer_data["dest_path"] = getNewPath(dest_path)
            self.addProgress(confirmation_id, dest_path)
            accept_cb(None)

        def accept_file_transfer_cb(widget):
            self.removePopUp()
            pop_up_widget = FileDialog(dir_selected_cb, refuse_cb, title=_(u"Where do you want to save the file ?"), style=['dir'])
            self.showPopUp(pop_up_widget)

        def accept_cb(widget):
            self.removePopUp()
            self.bridge.confirmationAnswer(confirmation_id, True, answer_data, profile)

        def refuse_cb(widget):
            self.removePopUp()
            self.bridge.confirmationAnswer(confirmation_id, False, answer_data, profile)

        if confirmation_type == "FILE_TRANSFER":
            pop_up_widget = sat_widgets.ConfirmDialog(_("The contact %(jid)s wants to send you the file %(filename)s\nDo you accept ?") % {'jid':data["from"], 'filename':data["filename"]}, no_cb=refuse_cb, yes_cb=accept_file_transfer_cb)
            self.showPopUp(pop_up_widget)

        elif confirmation_type == "YES/NO":
            pop_up_widget = sat_widgets.ConfirmDialog(data["message"], no_cb=refuse_cb, yes_cb=accept_cb)
            self.showPopUp(pop_up_widget)

    def actionResultHandler(self, type_, id, data, profile):
        # FIXME: to be removed

        if not id in self.current_action_ids:
            log.debug (_('unknown id, ignoring'))
            return
        if type_ == "SUPPRESS":
            self.current_action_ids.remove(id)
        elif type_ == "XMLUI":
            self.current_action_ids.remove(id)
            log.debug (_("XML user interface received"))
            misc = {}
            #FIXME FIXME FIXME: must clean all this crap !
            title = _('Form')
            if data['type'] == 'registration':
                title = _('Registration')
                misc['target'] = data['target']
                misc['action_back'] = self.bridge.gatewayRegister
            ui = xmlui.create(self, title=title, xml_data = data['xml'], misc = misc)
            if data['type'] == 'registration':
                ui.show('popup')
            else:
                ui.show('window')
        elif type_ == "ERROR":
            self.current_action_ids.remove(id)
            self.showPopUp(sat_widgets.Alert(_("Error"), unicode(data["message"]), ok_cb=self.removePopUp)) #FIXME: remove unicode here when DBus Bridge will no return dbus.String anymore
        elif type_ == "RESULT":
            self.current_action_ids.remove(id)
            if self.current_action_ids_cb.has_key(id):
                callback = self.current_action_ids_cb[id]
                del self.current_action_ids_cb[id]
                callback(data)
        elif type_ == "DICT_DICT":
            self.current_action_ids.remove(id)
            if self.current_action_ids_cb.has_key(id):
                callback = self.current_action_ids_cb[id]
                del self.current_action_ids_cb[id]
                callback(data)
        else:
            log.error (_("FIXME FIXME FIXME: type [%s] not implemented") % type_)
            raise NotImplementedError


    def roomJoinedHandler(self, room_jid_s, room_nicks, user_nick, profile):
        super(PrimitivusApp, self).roomJoinedHandler(room_jid_s, room_nicks, user_nick, profile)
        self.contact_lists[profile].setFocus(jid.JID(room_jid_s), True)



    ##DIALOGS CALLBACKS##
    def onJoinRoom(self, button, edit):
        self.removePopUp()
        room_jid = jid.JID(edit.get_edit_text())
        if room_jid.is_valid():
            self.bridge.joinMUC(room_jid, self.profiles[self.current_profile].whoami.node, {}, self.current_profile)
        else:
            message = _("'%s' is an invalid jid.JID !") % room_jid
            log.error (message)
            self.showPopUp(sat_widgets.Alert(_("Error"), message, ok_cb=self.removePopUp))

    #MENU EVENTS#
    def onConnectRequest(self, menu):
        QuickApp.asyncConnect(self, self.current_profile)

    def onDisconnectRequest(self, menu):
        self.bridge.disconnect(self.current_profile)

    def onParam(self, menu):
        def success(params):
            ui = xmlui.create(self, xml_data=params)
            ui.show()

        def failure(error):
            self.showPopUp(sat_widgets.Alert(_("Error"), _("Can't get parameters (%s)") % error, ok_cb=self.removePopUp))
        self.bridge.getParamsUI(app=C.APP_NAME, profile_key=self.current_profile, callback=success, errback=failure)

    def onExitRequest(self, menu):
        QuickApp.onExit(self)
        raise urwid.ExitMainLoop()

    def onJoinRoomRequest(self, menu):
        """User wants to join a MUC room"""
        pop_up_widget = sat_widgets.InputDialog(_("Entering a MUC room"), _("Please enter MUC's JID"), default_txt = 'room@muc_service.server.tld', cancel_cb=self.removePopUp, ok_cb=self.onJoinRoom)
        self.showPopUp(pop_up_widget)

    def onAboutRequest(self, menu):
        self.showPopUp(sat_widgets.Alert(_("About"), C.APP_NAME + " v" + self.bridge.getVersion(), ok_cb=self.removePopUp))

    #MISC CALLBACKS#

    def setStatusOnline(self, online=True, show="", statuses={}, profile=C.PROF_KEY_NONE):
        if not online or not statuses:
            self.contact_lists[profile].status_bar.setPresenceStatus(show if online else 'unavailable', '')
            return
        try:
            self.contact_lists[profile].status_bar.setPresenceStatus(show, statuses['default'])
        except (KeyError, TypeError):
            pass

sat = PrimitivusApp()
sat.start()