view frontends/src/quick_frontend/quick_chat.py @ 1304:1a61b18703c4 frontends_multi_profiles

quick frontend (quick widgets): class' __name__ method is used for classes_map hash because the use of class directly was causing bugs with pyjamas (difficult to find, several MicroblogPanel instances were added only once in Libervia's TabPanel, hash method seemed buggy)
author Goffi <goffi@goffi.org>
date Fri, 06 Feb 2015 19:05:51 +0100
parents faa1129559b8
children ebf72fe68d1c
line wrap: on
line source

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

# helper class for making 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 _
from sat.core.log import getLogger
log = getLogger(__name__)
from sat_frontends.tools import jid
from sat_frontends.quick_frontend import quick_widgets
from sat_frontends.quick_frontend.constants import Const as C


try:
    # FIXME: to be removed when an acceptable solution is here
    unicode('') # XXX: unicode doesn't exist in pyjamas
except (TypeError, AttributeError): # Error raised is not the same depending on pyjsbuild options
    unicode = lambda x: str(x)


class QuickChat(quick_widgets.QuickWidget):

    def __init__(self, host, target, type_=C.CHAT_ONE2ONE, profiles=None):
        """
        @param type_: can be C.CHAT_ONE2ONE for single conversation or C.CHAT_GROUP for chat à la IRC
        """

        quick_widgets.QuickWidget.__init__(self, host, target, profiles=profiles)
        assert type_ in (C.CHAT_ONE2ONE, C.CHAT_GROUP)
        if type_ == C.CHAT_GROUP and target.resource:
            raise ValueError("A group chat entity can't have a resource")
        self.current_target = target
        self.type = type_
        self.id = "" # FIXME: to be removed
        self.nick = None
        self.occupants = set()

    def __str__(self):
        return u"Chat Widget [target: {}, type: {}, profile: {}]".format(self.target, self.type, self.profile)

    @staticmethod
    def getWidgetHash(target, profile):
        return (unicode(profile), target.bare)

    @staticmethod
    def getPrivateHash(target, profile):
        """Get unique hash for private conversations

        This method should be used with force_hash to get unique widget for private MUC conversations
        """
        return (unicode(profile), target)


    def addTarget(self, target):
        super(QuickChat, self).addTarget(target)
        if target.resource:
            self.current_target = target # FIXME: tmp, must use resource priority throught contactList instead

    @property
    def target(self):
        if self.type == C.CHAT_GROUP:
            return self.current_target.bare
        return self.current_target

    def manageMessage(self, entity, mess_type):
        """Tell if this chat widget manage an entity and message type couple

        @param entity (jid.JID): (full) jid of the sending entity
        @param mess_type (str): message type as given by newMessage
        @return (bool): True if this Chat Widget manage this couple
        """
        if self.type == C.CHAT_GROUP:
            if mess_type == C.MESS_TYPE_GROUPCHAT and self.target == entity.bare:
                return True
        else:
            if mess_type != C.MESS_TYPE_GROUPCHAT and entity in self.targets:
                return True
        return False

    def setPresents(self, nicks):
        """Set the users presents in the contact list for a group chat
        @param nicks: list of nicknames
        """
        log.debug (_("Adding users %s to room") % nicks)
        if self.type != C.CHAT_GROUP:
            log.error (_("[INTERNAL] trying to set presents nicks for a non group chat window"))
            raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
        self.occupants.update(nicks)

    def replaceUser(self, nick, show_info=True):
        """Add user if it is not in the group list"""
        log.debug (_("Replacing user %s") % nick)
        if self.type != C.CHAT_GROUP:
            log.error (_("[INTERNAL] trying to replace user for a non group chat window"))
            raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
        len_before = len(self.occupants)
        self.occupants.add(nick)
        if len_before != len(self.occupants) and show_info:
            self.printInfo("=> %s has joined the room" % nick)

    def removeUser(self, nick, show_info=True):
        """Remove a user from the group list"""
        log.debug(_("Removing user %s") % nick)
        if self.type != C.CHAT_GROUP:
            log.error (_("[INTERNAL] trying to remove user for a non group chat window"))
            raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
        self.occupants.remove(nick)
        if show_info:
            self.printInfo("<= %s has left the room" % nick)

    def setUserNick(self, nick):
        """Set the nick of the user, usefull for e.g. change the color of the user"""
        self.nick = nick

    def getUserNick(self):
        return unicode(self.nick)

    def changeUserNick(self, old_nick, new_nick):
        """Change nick of a user in group list"""
        log.debug(_("Changing nick of user %(old_nick)s to %(new_nick)s") % {"old_nick": old_nick, "new_nick": new_nick})
        if self.type != C.CHAT_GROUP:
            log.error (_("[INTERNAL] trying to change user nick for a non group chat window"))
            raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
        self.removeUser(old_nick, show_info=False)
        self.replaceUser(new_nick, show_info=False)
        self.printInfo("%s is now known as %s" % (old_nick, new_nick))

    def setSubject(self, subject):
        """Set title for a group chat"""
        log.debug(_("Setting subject to %s") % subject)
        if self.type != C.CHAT_GROUP:
            log.error (_("[INTERNAL] trying to set subject for a non group chat window"))
            raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here

    def afterHistoryPrint(self):
        """Refresh or scroll down the focus after the history is printed"""
        pass

    def historyPrint(self, size=C.HISTORY_LIMIT_DEFAULT, search='', profile='@NONE@'):
        """Print the current history
        @param size (int): number of messages
        @param search (str): pattern to filter the history results
        @param profile (str): %(doc_profile)s
        """
        log.debug(_("now we print the history (%d messages)") % size)

        def onHistory(history):
            for line in history:
                timestamp, from_jid, to_jid, message, type_, extra = line # FIXME: extra is unused !
                if ((self.type == C.CHAT_GROUP and type_ != C.MESS_TYPE_GROUPCHAT) or
                   (self.type == C.CHAT_ONE2ONE and type_ == C.MESS_TYPE_GROUPCHAT)):
                    continue
                self.printMessage(jid.JID(from_jid), message, {'timestamp':timestamp}, profile)
            self.afterHistoryPrint()

        def onHistoryError(err):
            log.error(_("Can't get history"))

        target = self.target.bare

        self.host.bridge.getHistory(unicode(self.host.profiles[profile].whoami.bare), unicode(target), size, True, search, profile, callback=onHistory, errback=onHistoryError)

    def _get_nick(self, entity):
        """Return nick of this entity when possible"""
        if self.type == C.CHAT_GROUP:
            return entity.resource
        contact_list = self.host.contact_lists[self.profile]
        if entity.bare in contact_list:
            return contact_list.getCache(entity,'nick') or contact_list.getCache(entity,'name') or entity.node or entity
        return entity.node or entity

    def onPrivateCreated(self, widget):
        """Method called when a new widget for private conversation (MUC) is created"""
        raise NotImplementedError

    def getOrCreatePrivateWidget(self, entity):
        """Create a widget for private conversation, or get it if it already exists

        @param entity: full jid of the target
        """
        return self.host.widgets.getOrCreateWidget(QuickChat, entity, type_=C.CHAT_ONE2ONE, force_hash=self.getPrivateHash(self.profile, entity), on_new_widget=self.onPrivateCreated, profile=self.profile) # we force hash to have a new widget, not this one again

    def newMessage(self, from_jid, target, msg, type_, extra, profile):
        if self.type == C.CHAT_GROUP and target.resource and type_ != C.MESS_TYPE_GROUPCHAT:
            # we have a private message, we forward it to a private conversation widget
            chat_widget = self.getOrCreatePrivateWidget(target)
            chat_widget.newMessage(from_jid, target, msg, type_, extra, profile)
        else:
            if type_ == C.MESS_TYPE_INFO:
                self.printInfo(msg, extra=extra)
            else:
                self.printMessage(from_jid, msg, extra, profile)

    def printMessage(self, from_jid, msg, extra=None, profile=C.PROF_KEY_NONE):
        """Print message in chat window. Must be implemented by child class"""
        nick = self._get_nick(from_jid)
        mymess = (from_jid.resource == self.nick) if self.type == C.CHAT_GROUP else (from_jid.bare == self.host.profiles[profile].whoami.bare) #mymess = True if message comes from local user
        if msg.startswith('/me '):
            self.printInfo('* %s %s' % (nick, msg[4:]), type_='me', extra=extra)
            return
        return nick, mymess

    def printInfo(self, msg, type_='normal', extra=None):
        """Print general info
        @param msg: message to print
        @type_: one of:
            normal: general info like "toto has joined the room"
            me: "/me" information like "/me clenches his fist" ==> "toto clenches his fist"
        @param extra (dict): message data
        """
        raise NotImplementedError

    def startGame(self, game_type, referee, players):
        """Configure the chat window to start a game"""
        #No need to raise an error as game are not mandatory
        log.warning(_('startGame is not implemented in this frontend'))

    def getGame(self, game_type):
        """Return class managing the game type"""
        #No need to raise an error as game are not mandatory
        log.warning(_('getGame is not implemented in this frontend'))

    def updateChatState(self, from_jid, state):
        """Set the chat state (XEP-0085) of the contact.

        @param state: the new chat state
        """
        raise NotImplementedError

quick_widgets.register(QuickChat)