Mercurial > libervia-web
comparison src/browser/sat_browser/chat.py @ 679:a90cc8fc9605
merged branch frontends_multi_profiles
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 18 Mar 2015 16:15:18 +0100 |
parents | src/browser/sat_browser/panels.py@3eb3a2c0c011 src/browser/sat_browser/panels.py@849ffb24d5bf |
children | e876f493dccc |
comparison
equal
deleted
inserted
replaced
590:1bffc4c244c3 | 679:a90cc8fc9605 |
---|---|
1 #!/usr/bin/python | |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # Libervia: a Salut à Toi frontend | |
5 # Copyright (C) 2011, 2012, 2013, 2014 Jérôme Poisson <goffi@goffi.org> | |
6 | |
7 # This program is free software: you can redistribute it and/or modify | |
8 # it under the terms of the GNU Affero General Public License as published by | |
9 # the Free Software Foundation, either version 3 of the License, or | |
10 # (at your option) any later version. | |
11 | |
12 # This program is distributed in the hope that it will be useful, | |
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 # GNU Affero General Public License for more details. | |
16 | |
17 # You should have received a copy of the GNU Affero General Public License | |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | |
20 from sat.core.log import getLogger | |
21 log = getLogger(__name__) | |
22 | |
23 # from sat_frontends.tools.games import SYMBOLS | |
24 from sat_frontends.tools import strings | |
25 from sat_frontends.tools import jid | |
26 from sat_frontends.quick_frontend import quick_widgets, quick_games, quick_menus | |
27 from sat_frontends.quick_frontend.quick_chat import QuickChat | |
28 from sat.core.i18n import _ | |
29 | |
30 from pyjamas.ui.AbsolutePanel import AbsolutePanel | |
31 from pyjamas.ui.VerticalPanel import VerticalPanel | |
32 from pyjamas.ui.HorizontalPanel import HorizontalPanel | |
33 from pyjamas.ui.Label import Label | |
34 from pyjamas.ui.HTML import HTML | |
35 from pyjamas.ui.KeyboardListener import KEY_ENTER, KeyboardHandler | |
36 from pyjamas.ui.HTMLPanel import HTMLPanel | |
37 from pyjamas import DOM | |
38 | |
39 from datetime import datetime | |
40 from time import time | |
41 | |
42 import html_tools | |
43 import libervia_widget | |
44 import base_panel | |
45 import contact_panel | |
46 import editor_widget | |
47 import contact_list | |
48 from constants import Const as C | |
49 import plugin_xep_0085 | |
50 import game_tarot | |
51 import game_radiocol | |
52 | |
53 | |
54 unicode = str # FIXME: pyjamas workaround | |
55 | |
56 | |
57 class ChatText(HTMLPanel): | |
58 | |
59 def __init__(self, nick, mymess, msg, extra): | |
60 try: | |
61 timestamp = float(extra['timestamp']) | |
62 except KeyError: | |
63 timestamp=None | |
64 xhtml = extra.get('xhtml') | |
65 _date = datetime.fromtimestamp(float(timestamp or time())) | |
66 _msg_class = ["chat_text_msg"] | |
67 if mymess: | |
68 _msg_class.append("chat_text_mymess") | |
69 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>" % | |
70 {"timestamp": _date.strftime("%H:%M"), | |
71 "nick": "[%s]" % html_tools.html_sanitize(nick), | |
72 "msg_class": ' '.join(_msg_class), | |
73 "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 | |
74 ) | |
75 self.setStyleName('chatText') | |
76 | |
77 | |
78 class Chat(QuickChat, libervia_widget.LiberviaWidget, KeyboardHandler): | |
79 | |
80 def __init__(self, host, target, type_=C.CHAT_ONE2ONE, profiles=None): | |
81 """Panel used for conversation (one 2 one or group chat) | |
82 | |
83 @param host: SatWebFrontend instance | |
84 @param target: entity (jid.JID) with who we have a conversation (contact's jid for one 2 one chat, or MUC room) | |
85 @param type: one2one for simple conversation, group for MUC | |
86 """ | |
87 QuickChat.__init__(self, host, target, type_, profiles=profiles) | |
88 self.vpanel = VerticalPanel() | |
89 self.vpanel.setSize('100%', '100%') | |
90 | |
91 # FIXME: temporary dirty initialization to display the OTR state | |
92 header_info = host.plugins['otr'].getInfoTextForUser(target) if (type_ == C.CHAT_ONE2ONE and 'otr' in host.plugins) else None | |
93 | |
94 libervia_widget.LiberviaWidget.__init__(self, host, title=unicode(target.bare), info=header_info, selectable=True) | |
95 self._body = AbsolutePanel() | |
96 self._body.setStyleName('chatPanel_body') | |
97 chat_area = HorizontalPanel() | |
98 chat_area.setStyleName('chatArea') | |
99 if type_ == C.CHAT_GROUP: | |
100 self.occupants_panel = contact_panel.ContactsPanel(host, merge_resources=False, | |
101 contacts_style="muc_contact", | |
102 contacts_menus=(C.MENU_JID_CONTEXT), | |
103 contacts_display=('resource',)) | |
104 chat_area.add(self.occupants_panel) | |
105 DOM.setAttribute(chat_area.getWidgetTd(self.occupants_panel), "className", "occupantsPanelCell") | |
106 self._body.add(chat_area) | |
107 self.content = AbsolutePanel() | |
108 self.content.setStyleName('chatContent') | |
109 self.content_scroll = base_panel.ScrollPanelWrapper(self.content) | |
110 chat_area.add(self.content_scroll) | |
111 chat_area.setCellWidth(self.content_scroll, '100%') | |
112 self.vpanel.add(self._body) | |
113 self.vpanel.setCellHeight(self._body, '100%') | |
114 self.addStyleName('chatPanel') | |
115 self.setWidget(self.vpanel) | |
116 self.state_machine = plugin_xep_0085.ChatStateMachine(self.host, unicode(self.target)) | |
117 self._state = None | |
118 self.refresh() | |
119 if type_ == C.CHAT_ONE2ONE: | |
120 self.historyPrint(profile=self.profile) | |
121 | |
122 @property | |
123 def target(self): | |
124 # FIXME: for unknow reason, pyjamas doesn't use the method inherited from QuickChat | |
125 # FIXME: must remove this when either pyjamas is fixed, or we use an alternative | |
126 if self.type == C.CHAT_GROUP: | |
127 return self.current_target.bare | |
128 return self.current_target | |
129 | |
130 @property | |
131 def profile(self): | |
132 # FIXME: for unknow reason, pyjamas doesn't use the method inherited from QuickWidget | |
133 # FIXME: must remove this when either pyjamas is fixed, or we use an alternative | |
134 assert len(self.profiles) == 1 and not self.PROFILES_MULTIPLE and not self.PROFILES_ALLOW_NONE | |
135 return list(self.profiles)[0] | |
136 | |
137 @property | |
138 def plugin_menu_context(self): | |
139 return (C.MENU_ROOM,) if self.type == C.CHAT_GROUP else (C.MENU_SINGLE,) | |
140 | |
141 # @classmethod | |
142 # def createPanel(cls, host, item, type_=C.CHAT_ONE2ONE): | |
143 # assert(item) | |
144 # _contact = item if isinstance(item, jid.JID) else jid.JID(item) | |
145 # host.contact_panel.setContactMessageWaiting(_contact.bare, False) | |
146 # _new_panel = Chat(host, _contact, type_) # XXX: pyjamas doesn't seems to support creating with cls directly | |
147 # _new_panel.historyPrint() | |
148 # host.setSelected(_new_panel) | |
149 # _new_panel.refresh() | |
150 # return _new_panel | |
151 | |
152 def refresh(self): | |
153 """Refresh the display of this widget. If the unibox is disabled, | |
154 add a message box at the bottom of the panel""" | |
155 # FIXME: must be checked | |
156 # self.host.contact_panel.setContactMessageWaiting(self.target.bare, False) | |
157 # self.content_scroll.scrollToBottom() | |
158 | |
159 enable_box = self.host.uni_box is None | |
160 if hasattr(self, 'message_box'): | |
161 self.message_box.setVisible(enable_box) | |
162 elif enable_box: | |
163 self.message_box = editor_widget.MessageBox(self.host) | |
164 self.message_box.onSelectedChange(self) | |
165 self.message_box.addKeyboardListener(self) | |
166 self.vpanel.add(self.message_box) | |
167 | |
168 def onKeyDown(self, sender, keycode, modifiers): | |
169 if keycode == KEY_ENTER: | |
170 self.host.showWarning(None, None) | |
171 else: | |
172 self.host.showWarning(*self.getWarningData()) | |
173 | |
174 def getWarningData(self): | |
175 if self.type not in [C.CHAT_ONE2ONE, C.CHAT_GROUP]: | |
176 raise Exception("Unmanaged type !") | |
177 if self.type == C.CHAT_ONE2ONE: | |
178 msg = "This message will be sent to your contact <span class='warningTarget'>%s</span>" % self.target | |
179 elif self.type == C.CHAT_GROUP: | |
180 msg = "This message will be sent to all the participants of the multi-user room <span class='warningTarget'>%s</span>" % self.target | |
181 return ("ONE2ONE" if self.type == C.CHAT_ONE2ONE else "GROUP", msg) | |
182 | |
183 def onTextEntered(self, text): | |
184 self.host.sendMessage(self.target, | |
185 text, | |
186 mess_type=C.MESS_TYPE_GROUPCHAT if self.type == C.CHAT_GROUP else C.MESS_TYPE_CHAT, | |
187 errback=self.host.sendError, | |
188 profile_key=C.PROF_KEY_NONE | |
189 ) | |
190 self.state_machine._onEvent("active") | |
191 | |
192 def onQuit(self): | |
193 libervia_widget.LiberviaWidget.onQuit(self) | |
194 if self.type == C.CHAT_GROUP: | |
195 self.host.bridge.call('mucLeave', None, unicode(self.target.bare)) | |
196 | |
197 def setUserNick(self, nick): | |
198 """Set the nick of the user, usefull for e.g. change the color of the user""" | |
199 self.nick = nick | |
200 | |
201 def setPresents(self, nicks): | |
202 """Set the occupants of a group chat. | |
203 | |
204 @param nicks (list[unicode]): sorted list of nicknames | |
205 """ | |
206 QuickChat.setPresents(self, nicks) | |
207 self.occupants_panel.setList([jid.JID(u"%s/%s" % (self.target, nick)) for nick in nicks]) | |
208 | |
209 def replaceUser(self, nick, show_info=True): | |
210 """Add user if it is not in the group list""" | |
211 QuickChat.replaceUser(self, nick, show_info) | |
212 occupant_jid = jid.JID("%s/%s" % (unicode(self.target), nick)) | |
213 self.occupants_panel.addContact(occupant_jid) | |
214 | |
215 def removeUser(self, nick, show_info=True): | |
216 """Remove a user from the group list""" | |
217 QuickChat.removeUser(self, nick, show_info) | |
218 occupant_jid = jid.JID("%s/%s" % (unicode(self.target), nick)) | |
219 self.occupants_panel.removeContact(occupant_jid) | |
220 | |
221 def changeUserNick(self, old_nick, new_nick): | |
222 assert self.type == C.CHAT_GROUP | |
223 # FIXME | |
224 # self.occupants_panel.removeOccupant(old_nick) | |
225 # self.occupants_panel.addOccupant(new_nick) | |
226 self.printInfo(_("%(old_nick)s is now known as %(new_nick)s") % {'old_nick': old_nick, 'new_nick': new_nick}) | |
227 | |
228 # def historyPrint(self, size=C.HISTORY_LIMIT_DEFAULT): | |
229 # """Print the initial history""" | |
230 # def getHistoryCB(history): | |
231 # # display day change | |
232 # day_format = "%A, %d %b %Y" | |
233 # previous_day = datetime.now().strftime(day_format) | |
234 # for line in history: | |
235 # timestamp, from_jid_s, to_jid_s, message, mess_type, extra = line | |
236 # message_day = datetime.fromtimestamp(float(timestamp or time())).strftime(day_format) | |
237 # if previous_day != message_day: | |
238 # self.printInfo("* " + message_day) | |
239 # previous_day = message_day | |
240 # self.printMessage(jid.JID(from_jid_s), message, extra, timestamp) | |
241 # self.host.bridge.call('getHistory', getHistoryCB, self.host.whoami.bare, self.target.bare, size, True) | |
242 | |
243 def printInfo(self, msg, type_='normal', extra=None, link_cb=None): | |
244 """Print general info | |
245 @param msg: message to print | |
246 @param type_: one of: | |
247 "normal": general info like "toto has joined the room" (will be sanitized) | |
248 "link": general info that is clickable like "click here to join the main room" (no sanitize done) | |
249 "me": "/me" information like "/me clenches his fist" ==> "toto clenches his fist" (will stay on one line) | |
250 @param extra (dict): message data | |
251 @param link_cb: method to call when the info is clicked, ignored if type_ is not 'link' | |
252 """ | |
253 if extra is None: | |
254 extra = {} | |
255 if type_ == 'normal': | |
256 _wid = HTML(strings.addURLToText(html_tools.XHTML2Text(msg))) | |
257 _wid.setStyleName('chatTextInfo') | |
258 elif type_ == 'link': | |
259 _wid = HTML(msg) | |
260 _wid.setStyleName('chatTextInfo-link') | |
261 if link_cb: | |
262 _wid.addClickListener(link_cb) | |
263 elif type_ == 'me': | |
264 _wid = Label(msg) | |
265 _wid.setStyleName('chatTextMe') | |
266 else: | |
267 raise ValueError("Unknown printInfo type %s" % type_) | |
268 self.content.add(_wid) | |
269 self.content_scroll.scrollToBottom() | |
270 | |
271 def printMessage(self, from_jid, msg, extra=None, profile=C.PROF_KEY_NONE): | |
272 if extra is None: | |
273 extra = {} | |
274 try: | |
275 nick, mymess = QuickChat.printMessage(self, from_jid, msg, extra, profile) | |
276 except TypeError: | |
277 # None is returned, the message is managed | |
278 return | |
279 self.content.add(ChatText(nick, mymess, msg, extra)) | |
280 self.content_scroll.scrollToBottom() | |
281 | |
282 def setState(self, state, nick=None): | |
283 """Set the chat state (XEP-0085) of the contact. Leave nick to None | |
284 to set the state for a one2one conversation, or give a nickname or | |
285 C.ALL_OCCUPANTS to set the state of a participant within a MUC. | |
286 @param state: the new chat state | |
287 @param nick: ignored for one2one, otherwise the MUC user nick or C.ALL_OCCUPANTS | |
288 """ | |
289 return # FIXME | |
290 if self.type == C.CHAT_GROUP: | |
291 assert(nick) | |
292 if nick == C.ALL_OCCUPANTS: | |
293 occupants = self.occupants_panel.occupants_panel.keys() | |
294 else: | |
295 occupants = [nick] if nick in self.occupants_panel.occupants_panel else [] | |
296 for occupant in occupants: | |
297 self.occupants_panel.occupants_panel[occupant].setState(state) | |
298 else: | |
299 self._state = state | |
300 self.refreshTitle() | |
301 self.state_machine.started = not not state # start to send "composing" state from now | |
302 | |
303 def refreshTitle(self): | |
304 """Refresh the title of this Chat dialog""" | |
305 title = unicode(self.target.bare) | |
306 if self._state: | |
307 title += " (%s)".format(self._state) | |
308 self.setTitle(title) | |
309 | |
310 def setConnected(self, jid_s, resource, availability, priority, statuses): | |
311 """Set connection status | |
312 @param jid_s (unicode): JID userhost as unicode | |
313 """ | |
314 raise Exception("should not be there") # FIXME | |
315 assert(jid_s == self.target.bare) | |
316 if self.type != C.CHAT_GROUP: | |
317 return | |
318 box = self.occupants_panel.getOccupantBox(resource) | |
319 if box: | |
320 contact_list.setPresenceStyle(box, availability) | |
321 | |
322 def updateChatState(self, from_jid, state): | |
323 #TODO | |
324 pass | |
325 | |
326 def addGamePanel(self, widget): | |
327 """Insert a game panel to this Chat dialog. | |
328 | |
329 @param widget (Widget): the game panel | |
330 """ | |
331 self.vpanel.insert(widget, 0) | |
332 self.vpanel.setCellHeight(widget, widget.getHeight()) | |
333 | |
334 def removeGamePanel(self, widget): | |
335 """Remove the game panel from this Chat dialog. | |
336 | |
337 @param widget (Widget): the game panel | |
338 """ | |
339 self.vpanel.remove(widget) | |
340 | |
341 | |
342 quick_widgets.register(QuickChat, Chat) | |
343 quick_widgets.register(quick_games.Tarot, game_tarot.TarotPanel) | |
344 quick_widgets.register(quick_games.Radiocol, game_radiocol.RadioColPanel) | |
345 libervia_widget.LiberviaWidget.addDropKey("CONTACT", lambda host, item: host.displayWidget(Chat, jid.JID(item), dropped=True)) | |
346 quick_menus.QuickMenusManager.addDataCollector(C.MENU_ROOM, {'room_jid': 'target'}) | |
347 quick_menus.QuickMenusManager.addDataCollector(C.MENU_SINGLE, {'jid': 'target'}) |