Mercurial > libervia-web
view src/browser/sat_browser/chat.py @ 660:267761bf7f08 frontends_multi_profiles
browser side (contact list): ContactPanels is used instead of OccupantsList in MUC:
- ContactPanels become the generic class for all lists of contacts
- OccupantsList will be removed
- need to implements specials icons (e.g. for games) in ContactBox
- ContactBox now manage a display arguments to set which data we want to display
- ContactsPanel.display renamed to setList
- ContactBox style can be changed when instaciating parent ContactsPanel
- muc_contact CSS class is used for list of MUC occupants
Not fully functionnal yet
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 27 Feb 2015 22:53:27 +0100 |
parents | 0262fee86375 |
children | ebb602d8b3f2 |
line wrap: on
line source
#!/usr/bin/python # -*- coding: utf-8 -*- # Libervia: a Salut à Toi frontend # Copyright (C) 2011, 2012, 2013, 2014 Jérôme Poisson <goffi@goffi.org> # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. from sat.core.log import getLogger log = getLogger(__name__) from sat_frontends.tools.games import SYMBOLS from sat_frontends.tools import strings from sat_frontends.tools import jid from sat_frontends.quick_frontend import quick_widgets from sat_frontends.quick_frontend.quick_chat import QuickChat from sat.core.i18n import _ from pyjamas.ui.AbsolutePanel import AbsolutePanel from pyjamas.ui.VerticalPanel import VerticalPanel from pyjamas.ui.HorizontalPanel import HorizontalPanel from pyjamas.ui.Label import Label from pyjamas.ui.HTML import HTML from pyjamas.ui.KeyboardListener import KEY_ENTER, KeyboardHandler from pyjamas.ui.HTMLPanel import HTMLPanel from datetime import datetime from time import time import html_tools import libervia_widget import base_panel import contact_panel import editor_widget import card_game import radiocol import contact_list from constants import Const as C import plugin_xep_0085 unicode = str # FIXME: pyjamas workaround class ChatText(HTMLPanel): def __init__(self, nick, mymess, msg, extra): try: timestamp = float(extra['timestamp']) except KeyError: timestamp=None xhtml = extra.get('xhtml') _date = datetime.fromtimestamp(float(timestamp or time())) _msg_class = ["chat_text_msg"] if mymess: _msg_class.append("chat_text_mymess") HTMLPanel.__init__(self, "<span class='chat_text_timestamp'>%(timestamp)s</span> <span class='chat_text_nick'>%(nick)s</span> <span class='%(msg_class)s'>%(msg)s</span>" % {"timestamp": _date.strftime("%H:%M"), "nick": "[%s]" % html_tools.html_sanitize(nick), "msg_class": ' '.join(_msg_class), "msg": strings.addURLToText(html_tools.html_sanitize(msg)) if not xhtml else html_tools.inlineRoot(xhtml)} # FIXME: images and external links must be removed according to preferences ) self.setStyleName('chatText') class Chat(QuickChat, libervia_widget.LiberviaWidget, KeyboardHandler): def __init__(self, host, target, type_=C.CHAT_ONE2ONE, profiles=None): """Panel used for conversation (one 2 one or group chat) @param host: SatWebFrontend instance @param target: entity (jid.JID) with who we have a conversation (contact's jid for one 2 one chat, or MUC room) @param type: one2one for simple conversation, group for MUC """ QuickChat.__init__(self, host, target, type_, profiles=profiles) self.vpanel = VerticalPanel() self.vpanel.setSize('100%', '100%') # FIXME: temporary dirty initialization to display the OTR state def header_info_cb(cb): host.plugins['otr'].infoTextCallback(target, cb) header_info = header_info_cb if (type_ == C.CHAT_ONE2ONE and 'otr' in host.plugins) else None libervia_widget.LiberviaWidget.__init__(self, host, title=unicode(target.bare), info=header_info, selectable=True) self._body = AbsolutePanel() self._body.setStyleName('chatPanel_body') chat_area = HorizontalPanel() chat_area.setStyleName('chatArea') if type_ == C.CHAT_GROUP: self.occupants_panel = contact_panel.ContactsPanel(self, contacts_style="muc_contact", contacts_display=('resource',)) self.occupants_panel.setStyleName("occupantsPanel") chat_area.add(self.occupants_panel) self._body.add(chat_area) self.content = AbsolutePanel() self.content.setStyleName('chatContent') self.content_scroll = base_panel.ScrollPanelWrapper(self.content) chat_area.add(self.content_scroll) chat_area.setCellWidth(self.content_scroll, '100%') self.vpanel.add(self._body) self.vpanel.setCellHeight(self._body, '100%') self.addStyleName('chatPanel') self.setWidget(self.vpanel) self.state_machine = plugin_xep_0085.ChatStateMachine(self.host, str(self.target)) self._state = None self.refresh() if type_ == C.CHAT_ONE2ONE: self.historyPrint(profile=self.profile) @property def target(self): # FIXME: for unknow reason, pyjamas doesn't use the method inherited from QuickChat # FIXME: must remove this when either pyjamas is fixed, or we use an alternative if self.type == C.CHAT_GROUP: return self.current_target.bare return self.current_target @property def profile(self): # FIXME: for unknow reason, pyjamas doesn't use the method inherited from QuickWidget # FIXME: must remove this when either pyjamas is fixed, or we use an alternative assert len(self.profiles) == 1 and not self.PROFILES_MULTIPLE and not self.PROFILES_ALLOW_NONE return list(self.profiles)[0] # @classmethod # def createPanel(cls, host, item, type_=C.CHAT_ONE2ONE): # assert(item) # _contact = item if isinstance(item, jid.JID) else jid.JID(item) # host.contact_panel.setContactMessageWaiting(_contact.bare, False) # _new_panel = Chat(host, _contact, type_) # XXX: pyjamas doesn't seems to support creating with cls directly # _new_panel.historyPrint() # host.setSelected(_new_panel) # _new_panel.refresh() # return _new_panel def refresh(self): """Refresh the display of this widget. If the unibox is disabled, add a message box at the bottom of the panel""" # FIXME: must be checked # self.host.contact_panel.setContactMessageWaiting(self.target.bare, False) # self.content_scroll.scrollToBottom() enable_box = self.host.uni_box is None if hasattr(self, 'message_box'): self.message_box.setVisible(enable_box) elif enable_box: self.message_box = editor_widget.MessageBox(self.host) self.message_box.onSelectedChange(self) self.message_box.addKeyboardListener(self) self.vpanel.add(self.message_box) def onKeyDown(self, sender, keycode, modifiers): if keycode == KEY_ENTER: self.host.showWarning(None, None) else: self.host.showWarning(*self.getWarningData()) def addMenus(self, menu_bar): """Add cached menus to the header. @param menu_bar (GenericMenuBar): menu bar of the widget's header """ if self.type == C.CHAT_GROUP: menu_bar.addCachedMenus(C.MENU_ROOM, {'room_jid': self.target.bare}) elif self.type == C.CHAT_ONE2ONE: menu_bar.addCachedMenus(C.MENU_SINGLE, {'jid': self.target}) def getWarningData(self): if self.type not in [C.CHAT_ONE2ONE, C.CHAT_GROUP]: raise Exception("Unmanaged type !") if self.type == C.CHAT_ONE2ONE: msg = "This message will be sent to your contact <span class='warningTarget'>%s</span>" % self.target elif self.type == C.CHAT_GROUP: msg = "This message will be sent to all the participants of the multi-user room <span class='warningTarget'>%s</span>" % self.target return ("ONE2ONE" if self.type == C.CHAT_ONE2ONE else "GROUP", msg) def onTextEntered(self, text): self.host.sendMessage(str(self.target), text, mess_type = C.MESS_TYPE_GROUPCHAT if self.type == C.CHAT_GROUP else C.MESS_TYPE_CHAT, errback=self.host.sendError, profile_key=C.PROF_KEY_NONE ) self.state_machine._onEvent("active") def onQuit(self): libervia_widget.LiberviaWidget.onQuit(self) if self.type == C.CHAT_GROUP: self.host.bridge.call('mucLeave', None, unicode(self.target.bare)) def setUserNick(self, nick): """Set the nick of the user, usefull for e.g. change the color of the user""" self.nick = nick def setPresents(self, nicks): """Set the users presents in this room @param occupants: list of nicks (string)""" self.occupants_panel.setList([jid.JID(u"%s/%s" % (self.target,nick)) for nick in nicks]) # def userJoined(self, nick, data): # if self.occupants_panel.getOccupantBox(nick): # return # user is already displayed # self.occupants_panel.addOccupant(nick) # if self.occupants_initialised: # self.printInfo("=> %s has joined the room" % nick) # def userLeft(self, nick, data): # self.occupants_panel.removeOccupant(nick) # self.printInfo("<= %s has left the room" % nick) def changeUserNick(self, old_nick, new_nick): assert self.type == C.CHAT_GROUP # FIXME # self.occupants_panel.removeOccupant(old_nick) # self.occupants_panel.addOccupant(new_nick) self.printInfo(_("%(old_nick)s is now known as %(new_nick)s") % {'old_nick': old_nick, 'new_nick': new_nick}) # def historyPrint(self, size=C.HISTORY_LIMIT_DEFAULT): # """Print the initial history""" # def getHistoryCB(history): # # display day change # day_format = "%A, %d %b %Y" # previous_day = datetime.now().strftime(day_format) # for line in history: # timestamp, from_jid_s, to_jid_s, message, mess_type, extra = line # message_day = datetime.fromtimestamp(float(timestamp or time())).strftime(day_format) # if previous_day != message_day: # self.printInfo("* " + message_day) # previous_day = message_day # self.printMessage(jid.JID(from_jid_s), message, extra, timestamp) # self.host.bridge.call('getHistory', getHistoryCB, self.host.whoami.bare, self.target.bare, size, True) def printInfo(self, msg, type_='normal', extra=None, link_cb=None): """Print general info @param msg: message to print @param type_: one of: "normal": general info like "toto has joined the room" (will be sanitized) "link": general info that is clickable like "click here to join the main room" (no sanitize done) "me": "/me" information like "/me clenches his fist" ==> "toto clenches his fist" (will stay on one line) @param extra (dict): message data @param link_cb: method to call when the info is clicked, ignored if type_ is not 'link' """ if extra is None: extra = {} if type_ == 'normal': _wid = HTML(strings.addURLToText(html_tools.XHTML2Text(msg))) _wid.setStyleName('chatTextInfo') elif type_ == 'link': _wid = HTML(msg) _wid.setStyleName('chatTextInfo-link') if link_cb: _wid.addClickListener(link_cb) elif type_ == 'me': _wid = Label(msg) _wid.setStyleName('chatTextMe') else: raise ValueError("Unknown printInfo type %s" % type_) self.content.add(_wid) self.content_scroll.scrollToBottom() def printMessage(self, from_jid, msg, extra=None, profile=C.PROF_KEY_NONE): if extra is None: extra = {} try: nick, mymess = QuickChat.printMessage(self, from_jid, msg, extra, profile) except TypeError: # None is returned, the message is managed return self.content.add(ChatText(nick, mymess, msg, extra)) self.content_scroll.scrollToBottom() def startGame(self, game_type, waiting, referee, players, *args): """Configure the chat window to start a game""" classes = {"Tarot": card_game.CardPanel, "RadioCol": radiocol.RadioColPanel} if game_type not in classes.keys(): return # unknown game attr = game_type.lower() # self.occupants_panel.updateSpecials(players, SYMBOLS[attr]) # FIXME if waiting or not self.nick in players: return # waiting for player or not playing attr = "%s_panel" % attr if hasattr(self, attr): return log.info("%s Game Started \o/" % game_type) panel = classes[game_type](self, referee, self.nick, players, *args) setattr(self, attr, panel) self.vpanel.insert(panel, 0) self.vpanel.setCellHeight(panel, panel.getHeight()) def getGame(self, game_type): """Return class managing the game type""" # TODO: check that the game is launched, and manage errors if game_type == "Tarot": return self.tarot_panel elif game_type == "RadioCol": return self.radiocol_panel def setState(self, state, nick=None): """Set the chat state (XEP-0085) of the contact. Leave nick to None to set the state for a one2one conversation, or give a nickname or C.ALL_OCCUPANTS to set the state of a participant within a MUC. @param state: the new chat state @param nick: ignored for one2one, otherwise the MUC user nick or C.ALL_OCCUPANTS """ return # FIXME if self.type == C.CHAT_GROUP: assert(nick) if nick == C.ALL_OCCUPANTS: occupants = self.occupants_panel.occupants_panel.keys() else: occupants = [nick] if nick in self.occupants_panel.occupants_panel else [] for occupant in occupants: self.occupants_panel.occupants_panel[occupant].setState(state) else: self._state = state self.refreshTitle() self.state_machine.started = not not state # start to send "composing" state from now def refreshTitle(self): """Refresh the title of this Chat dialog""" title = unicode(self.target.bare) if self._state: title += " (%s)".format(self._state) self.setTitle(title) def setConnected(self, jid_s, resource, availability, priority, statuses): """Set connection status @param jid_s (str): JID userhost as unicode """ raise Exception("should not be there") # FIXME assert(jid_s == self.target.bare) if self.type != C.CHAT_GROUP: return box = self.occupants_panel.getOccupantBox(resource) if box: contact_list.setPresenceStyle(box, availability) def updateChatState(self, from_jid, state): #TODO pass quick_widgets.register(QuickChat, Chat) libervia_widget.LiberviaWidget.addDropKey("CONTACT", lambda host, item: host.displayWidget(Chat, jid.JID(item), dropped=True))