comparison frontends/src/quick_frontend/quick_chat.py @ 1367:f71a0fc26886

merged branch frontends_multi_profiles
author Goffi <goffi@goffi.org>
date Wed, 18 Mar 2015 10:52:28 +0100
parents ba87b940f07a
children 017270e6eea4
comparison
equal deleted inserted replaced
1295:1e3b1f9ad6e2 1367:f71a0fc26886
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20 from sat.core.i18n import _ 20 from sat.core.i18n import _
21 from sat.core.log import getLogger 21 from sat.core.log import getLogger
22 log = getLogger(__name__) 22 log = getLogger(__name__)
23 from sat_frontends.tools.jid import JID 23 from sat_frontends.tools import jid
24 from sat_frontends.quick_frontend.quick_utils import unescapePrivate 24 from sat_frontends.quick_frontend import quick_widgets
25 from sat_frontends.quick_frontend.constants import Const as C 25 from sat_frontends.quick_frontend.constants import Const as C
26 26
27 27
28 class QuickChat(object): 28 try:
29 29 # FIXME: to be removed when an acceptable solution is here
30 def __init__(self, target, host, type_='one2one'): 30 unicode('') # XXX: unicode doesn't exist in pyjamas
31 self.target = target 31 except (TypeError, AttributeError): # Error raised is not the same depending on pyjsbuild options
32 self.host = host 32 unicode = lambda x: str(x)
33
34
35 class QuickChat(quick_widgets.QuickWidget):
36
37 def __init__(self, host, target, type_=C.CHAT_ONE2ONE, profiles=None):
38 """
39 @param type_: can be C.CHAT_ONE2ONE for single conversation or C.CHAT_GROUP for chat à la IRC
40 """
41 quick_widgets.QuickWidget.__init__(self, host, target, profiles=profiles)
42 assert type_ in (C.CHAT_ONE2ONE, C.CHAT_GROUP)
43 if type_ == C.CHAT_GROUP and target.resource:
44 raise ValueError("A group chat entity can't have a resource")
45 self.current_target = target
33 self.type = type_ 46 self.type = type_
34 self.id = "" 47 self.id = "" # FIXME: to be removed
35 self.nick = None 48 self.nick = None
36 self.occupants = set() 49 self.occupants = set()
37 50 self.games = {}
38 def setType(self, type_): 51
39 """Set the type of the chat 52 def __str__(self):
40 @param type: can be 'one2one' for single conversation or 'group' for chat à la IRC 53 return u"Chat Widget [target: {}, type: {}, profile: {}]".format(self.target, self.type, self.profile)
41 """ 54
42 self.type = type_ 55 @staticmethod
56 def getWidgetHash(target, profile):
57 return (unicode(profile), target.bare)
58
59 @staticmethod
60 def getPrivateHash(target, profile):
61 """Get unique hash for private conversations
62
63 This method should be used with force_hash to get unique widget for private MUC conversations
64 """
65 return (unicode(profile), target)
66
67
68 def addTarget(self, target):
69 super(QuickChat, self).addTarget(target)
70 if target.resource:
71 self.current_target = target # FIXME: tmp, must use resource priority throught contactList instead
72
73 @property
74 def target(self):
75 if self.type == C.CHAT_GROUP:
76 return self.current_target.bare
77 return self.current_target
78
79 def manageMessage(self, entity, mess_type):
80 """Tell if this chat widget manage an entity and message type couple
81
82 @param entity (jid.JID): (full) jid of the sending entity
83 @param mess_type (str): message type as given by newMessage
84 @return (bool): True if this Chat Widget manage this couple
85 """
86 if self.type == C.CHAT_GROUP:
87 if mess_type == C.MESS_TYPE_GROUPCHAT and self.target == entity.bare:
88 return True
89 else:
90 if mess_type != C.MESS_TYPE_GROUPCHAT and entity in self.targets:
91 return True
92 return False
43 93
44 def setPresents(self, nicks): 94 def setPresents(self, nicks):
45 """Set the users presents in the contact list for a group chat 95 """Set the occupants of a group chat.
46 @param nicks: list of nicknames 96
47 """ 97 @param nicks (list[unicode]): sorted list of nicknames
48 log.debug (_("Adding users %s to room") % nicks) 98 """
49 if self.type != "group": 99 log.debug(_("Adding users %s to room") % nicks)
50 log.error (_("[INTERNAL] trying to set presents nicks for a non group chat window")) 100 if self.type != C.CHAT_GROUP:
51 raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here 101 log.error(_("[INTERNAL] trying to set presents nicks for a non group chat window"))
102 raise Exception("INTERNAL ERROR") # TODO: raise proper Exception here
52 self.occupants.update(nicks) 103 self.occupants.update(nicks)
53 104
54 def replaceUser(self, nick, show_info=True): 105 def replaceUser(self, nick, show_info=True):
55 """Add user if it is not in the group list""" 106 """Add user if it is not in the group list"""
56 log.debug (_("Replacing user %s") % nick) 107 log.debug (_("Replacing user %s") % nick)
57 if self.type != "group": 108 if self.type != C.CHAT_GROUP:
58 log.error (_("[INTERNAL] trying to replace user for a non group chat window")) 109 log.error (_("[INTERNAL] trying to replace user for a non group chat window"))
59 raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here 110 raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
60 len_before = len(self.occupants) 111 len_before = len(self.occupants)
61 self.occupants.add(nick) 112 self.occupants.add(nick)
62 if len_before != len(self.occupants) and show_info: 113 if len_before != len(self.occupants) and show_info:
63 self.printInfo("=> %s has joined the room" % nick) 114 self.printInfo("=> %s has joined the room" % nick)
64 115
65 def removeUser(self, nick, show_info=True): 116 def removeUser(self, nick, show_info=True):
66 """Remove a user from the group list""" 117 """Remove a user from the group list"""
67 log.debug(_("Removing user %s") % nick) 118 log.debug(_("Removing user %s") % nick)
68 if self.type != "group": 119 if self.type != C.CHAT_GROUP:
69 log.error (_("[INTERNAL] trying to remove user for a non group chat window")) 120 log.error (_("[INTERNAL] trying to remove user for a non group chat window"))
70 raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here 121 raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
71 self.occupants.remove(nick) 122 self.occupants.remove(nick)
72 if show_info: 123 if show_info:
73 self.printInfo("<= %s has left the room" % nick) 124 self.printInfo("<= %s has left the room" % nick)
80 return unicode(self.nick) 131 return unicode(self.nick)
81 132
82 def changeUserNick(self, old_nick, new_nick): 133 def changeUserNick(self, old_nick, new_nick):
83 """Change nick of a user in group list""" 134 """Change nick of a user in group list"""
84 log.debug(_("Changing nick of user %(old_nick)s to %(new_nick)s") % {"old_nick": old_nick, "new_nick": new_nick}) 135 log.debug(_("Changing nick of user %(old_nick)s to %(new_nick)s") % {"old_nick": old_nick, "new_nick": new_nick})
85 if self.type != "group": 136 if self.type != C.CHAT_GROUP:
86 log.error (_("[INTERNAL] trying to change user nick for a non group chat window")) 137 log.error (_("[INTERNAL] trying to change user nick for a non group chat window"))
87 raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here 138 raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
88 self.removeUser(old_nick, show_info=False) 139 self.removeUser(old_nick, show_info=False)
89 self.replaceUser(new_nick, show_info=False) 140 self.replaceUser(new_nick, show_info=False)
90 self.printInfo("%s is now known as %s" % (old_nick, new_nick)) 141 self.printInfo("%s is now known as %s" % (old_nick, new_nick))
91 142
92 def setSubject(self, subject): 143 def setSubject(self, subject):
93 """Set title for a group chat""" 144 """Set title for a group chat"""
94 log.debug(_("Setting subject to %s") % subject) 145 log.debug(_("Setting subject to %s") % subject)
95 if self.type != "group": 146 if self.type != C.CHAT_GROUP:
96 log.error (_("[INTERNAL] trying to set subject for a non group chat window")) 147 log.error (_("[INTERNAL] trying to set subject for a non group chat window"))
97 raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here 148 raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
98 149
99 def afterHistoryPrint(self): 150 def afterHistoryPrint(self):
100 """Refresh or scroll down the focus after the history is printed""" 151 """Refresh or scroll down the focus after the history is printed"""
104 """Print the current history 155 """Print the current history
105 @param size (int): number of messages 156 @param size (int): number of messages
106 @param search (str): pattern to filter the history results 157 @param search (str): pattern to filter the history results
107 @param profile (str): %(doc_profile)s 158 @param profile (str): %(doc_profile)s
108 """ 159 """
109 log.debug(_("now we print the history (%d messages)") % size) 160 log_msg = _(u"now we print the history")
161 if size != C.HISTORY_LIMIT_DEFAULT:
162 log_msg += _(u" (%d messages)" % size)
163 log.debug(log_msg)
110 164
111 def onHistory(history): 165 def onHistory(history):
112 for line in history: 166 for line in history:
113 timestamp, from_jid, to_jid, message, _type, extra = line 167 timestamp, from_jid, to_jid, message, type_, extra = line # FIXME: extra is unused !
114 if ((self.type == 'group' and _type != 'groupchat') or 168 if ((self.type == C.CHAT_GROUP and type_ != C.MESS_TYPE_GROUPCHAT) or
115 (self.type == 'one2one' and _type == 'groupchat')): 169 (self.type == C.CHAT_ONE2ONE and type_ == C.MESS_TYPE_GROUPCHAT)):
116 continue 170 continue
117 self.printMessage(JID(from_jid), message, profile, timestamp) 171 self.printMessage(jid.JID(from_jid), message, {'timestamp':timestamp}, profile)
118 self.afterHistoryPrint() 172 self.afterHistoryPrint()
119 173
120 def onHistoryError(err): 174 def onHistoryError(err):
121 log.error(_("Can't get history")) 175 log.error(_("Can't get history"))
122 176
123 if self.target.startswith(C.PRIVATE_PREFIX): 177 target = self.target.bare
124 target = unescapePrivate(self.target) 178
179 self.host.bridge.getHistory(unicode(self.host.profiles[profile].whoami.bare), unicode(target), size, True, search, profile, callback=onHistory, errback=onHistoryError)
180
181 def _get_nick(self, entity):
182 """Return nick of this entity when possible"""
183 if self.type == C.CHAT_GROUP:
184 return entity.resource
185 contact_list = self.host.contact_lists[self.profile]
186 if entity.bare in contact_list:
187 return contact_list.getCache(entity,'nick') or contact_list.getCache(entity,'name') or entity.node or entity
188 return entity.node or entity
189
190 def onPrivateCreated(self, widget):
191 """Method called when a new widget for private conversation (MUC) is created"""
192 raise NotImplementedError
193
194 def getOrCreatePrivateWidget(self, entity):
195 """Create a widget for private conversation, or get it if it already exists
196
197 @param entity: full jid of the target
198 """
199 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
200
201 def newMessage(self, from_jid, target, msg, type_, extra, profile):
202 if self.type == C.CHAT_GROUP and target.resource and type_ != C.MESS_TYPE_GROUPCHAT:
203 # we have a private message, we forward it to a private conversation widget
204 chat_widget = self.getOrCreatePrivateWidget(target)
205 chat_widget.newMessage(from_jid, target, msg, type_, extra, profile)
125 else: 206 else:
126 target = self.target.bare 207 if type_ == C.MESS_TYPE_INFO:
127 208 self.printInfo(msg, extra=extra)
128 return self.host.bridge.getHistory(self.host.profiles[profile]['whoami'].bare, target, size, search=search, profile=profile, callback=onHistory, errback=onHistoryError) 209 else:
129 210 self.printMessage(from_jid, msg, extra, profile)
130 def _get_nick(self, jid): 211
131 """Return nick of this jid when possible""" 212 def printMessage(self, from_jid, msg, extra=None, profile=C.PROF_KEY_NONE):
132 if self.target.startswith(C.PRIVATE_PREFIX):
133 unescaped = unescapePrivate(self.target)
134 if jid.startswith(C.PRIVATE_PREFIX) or unescaped.bare == jid.bare:
135 return unescaped.resource
136 return jid.resource if self.type == "group" else (self.host.contact_list.getCache(jid,'nick') or self.host.contact_list.getCache(jid,'name') or jid.node)
137
138 def printMessage(self, from_jid, msg, profile, timestamp=None):
139 """Print message in chat window. Must be implemented by child class""" 213 """Print message in chat window. Must be implemented by child class"""
140 jid = JID(from_jid) 214 nick = self._get_nick(from_jid)
141 nick = self._get_nick(jid) 215 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
142 mymess = (jid.resource == self.nick) if self.type == "group" else (jid.bare == self.host.profiles[profile]['whoami'].bare) #mymess = True if message comes from local user
143 if msg.startswith('/me '): 216 if msg.startswith('/me '):
144 self.printInfo('* %s %s' % (nick, msg[4:]), type_='me', timestamp=timestamp) 217 self.printInfo('* %s %s' % (nick, msg[4:]), type_='me', extra=extra)
145 return 218 return
146 return jid, nick, mymess 219 return nick, mymess
147 220
148 def printInfo(self, msg, type_='normal', timestamp=None): 221 def printInfo(self, msg, type_='normal', extra=None):
149 """Print general info 222 """Print general info
150 @param msg: message to print 223 @param msg: message to print
151 @type_: one of: 224 @type_: one of:
152 normal: general info like "toto has joined the room" 225 normal: general info like "toto has joined the room"
153 me: "/me" information like "/me clenches his fist" ==> "toto clenches his fist" 226 me: "/me" information like "/me clenches his fist" ==> "toto clenches his fist"
154 @param timestamp (float): number of seconds since epoch 227 @param extra (dict): message data
155 """ 228 """
156 raise NotImplementedError 229 raise NotImplementedError
157 230
158 def startGame(self, game_type, referee, players): 231 def updateChatState(self, from_jid, state):
159 """Configure the chat window to start a game""" 232 """Set the chat state (XEP-0085) of the contact.
160 #No need to raise an error as game are not mandatory 233
161 log.warning(_('startGame is not implemented in this frontend'))
162
163 def getGame(self, game_type):
164 """Return class managing the game type"""
165 #No need to raise an error as game are not mandatory
166 log.warning(_('getGame is not implemented in this frontend'))
167
168 def updateChatState(self, state, nick=None):
169 """Set the chat state (XEP-0085) of the contact. Leave nick to None
170 to set the state for a one2one conversation, or give a nickname or
171 C.ALL_OCCUPANTS to set the state of a participant within a MUC.
172 @param state: the new chat state 234 @param state: the new chat state
173 @param nick: None for one2one, the MUC user nick or ALL_OCCUPANTS 235 """
174 """ 236 raise NotImplementedError
175 raise NotImplementedError 237
176 238 def addGamePanel(self, widget):
239 """Insert a game panel to this Chat dialog.
240
241 @param widget (Widget): the game panel
242 """
243 raise NotImplementedError
244
245 def removeGamePanel(self, widget):
246 """Remove the game panel from this Chat dialog.
247
248 @param widget (Widget): the game panel
249 """
250 raise NotImplementedError
251
252
253 quick_widgets.register(QuickChat)