Mercurial > libervia-backend
comparison sat_frontends/primitivus/chat.py @ 2562:26edcf3a30eb
core, setup: huge cleaning:
- moved directories from src and frontends/src to sat and sat_frontends, which is the recommanded naming convention
- move twisted directory to root
- removed all hacks from setup.py, and added missing dependencies, it is now clean
- use https URL for website in setup.py
- removed "Environment :: X11 Applications :: GTK", as wix is deprecated and removed
- renamed sat.sh to sat and fixed its installation
- added python_requires to specify Python version needed
- replaced glib2reactor which use deprecated code by gtk3reactor
sat can now be installed directly from virtualenv without using --system-site-packages anymore \o/
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 02 Apr 2018 19:44:50 +0200 |
parents | frontends/src/primitivus/chat.py@0046283a285d |
children | 56f94936df1e |
comparison
equal
deleted
inserted
replaced
2561:bd30dc3ffe5a | 2562:26edcf3a30eb |
---|---|
1 #!/usr/bin/env python2 | |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # Primitivus: a SAT frontend | |
5 # Copyright (C) 2009-2018 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.i18n import _ | |
21 from sat.core import log as logging | |
22 log = logging.getLogger(__name__) | |
23 import urwid | |
24 from urwid_satext import sat_widgets | |
25 from sat_frontends.quick_frontend import quick_widgets | |
26 from sat_frontends.quick_frontend import quick_chat | |
27 from sat_frontends.quick_frontend import quick_games | |
28 from sat_frontends.primitivus import game_tarot | |
29 from sat_frontends.primitivus.constants import Const as C | |
30 from sat_frontends.primitivus.keys import action_key_map as a_key | |
31 from sat_frontends.primitivus.widget import PrimitivusWidget | |
32 from sat_frontends.primitivus.contact_list import ContactList | |
33 from functools import total_ordering | |
34 import bisect | |
35 | |
36 | |
37 OCCUPANTS_FOOTER = _(u"{} occupants") | |
38 | |
39 class MessageWidget(urwid.WidgetWrap): | |
40 | |
41 def __init__(self, mess_data): | |
42 """ | |
43 @param mess_data(quick_chat.Message, None): message data | |
44 None: used only for non text widgets (e.g.: focus separator) | |
45 """ | |
46 self.mess_data = mess_data | |
47 mess_data.widgets.add(self) | |
48 super(MessageWidget, self).__init__(urwid.Text(self.markup)) | |
49 | |
50 @property | |
51 def markup(self): | |
52 return self._generateInfoMarkup() if self.mess_data.type == C.MESS_TYPE_INFO else self._generateMarkup() | |
53 | |
54 @property | |
55 def info_type(self): | |
56 return self.mess_data.info_type | |
57 | |
58 @property | |
59 def parent(self): | |
60 return self.mess_data.parent | |
61 | |
62 @property | |
63 def message(self): | |
64 """Return currently displayed message""" | |
65 return self.mess_data.main_message | |
66 | |
67 @message.setter | |
68 def message(self, value): | |
69 self.mess_data.message = {'':value} | |
70 self.redraw() | |
71 | |
72 @property | |
73 def type(self): | |
74 try: | |
75 return self.mess_data.type | |
76 except AttributeError: | |
77 return C.MESS_TYPE_INFO | |
78 | |
79 def redraw(self): | |
80 self._w.set_text(self.markup) | |
81 self.mess_data.parent.host.redraw() # FIXME: should not be necessary | |
82 | |
83 def selectable(self): | |
84 return True | |
85 | |
86 def keypress(self, size, key): | |
87 return key | |
88 | |
89 def get_cursor_coords(self, size): | |
90 return 0, 0 | |
91 | |
92 def render(self, size, focus=False): | |
93 # Text widget doesn't render cursor, but we want one | |
94 # so we add it here | |
95 canvas = urwid.CompositeCanvas(self._w.render(size, focus)) | |
96 if focus: | |
97 canvas.set_cursor(self.get_cursor_coords(size)) | |
98 return canvas | |
99 | |
100 def _generateInfoMarkup(self): | |
101 return ('info_msg', self.message) | |
102 | |
103 def _generateMarkup(self): | |
104 """Generate text markup according to message data and Widget options""" | |
105 markup = [] | |
106 d = self.mess_data | |
107 mention = d.mention | |
108 | |
109 # message status | |
110 if d.status is None: | |
111 markup.append(u' ') | |
112 elif d.status == "delivered": | |
113 markup.append(('msg_status_received', u'✔')) | |
114 else: | |
115 log.warning(u"Unknown status: {}".format(d.status)) | |
116 | |
117 # timestamp | |
118 if self.parent.show_timestamp: | |
119 attr = 'msg_mention' if mention else 'date' | |
120 markup.append((attr, u"[{}]".format(d.time_text))) | |
121 else: | |
122 if mention: | |
123 markup.append(('msg_mention', '[*]')) | |
124 | |
125 # nickname | |
126 if self.parent.show_short_nick: | |
127 markup.append(('my_nick' if d.own_mess else 'other_nick', "**" if d.own_mess else "*")) | |
128 else: | |
129 markup.append(('my_nick' if d.own_mess else 'other_nick', u"[{}] ".format(d.nick or ''))) | |
130 | |
131 msg = self.message # needed to generate self.selected_lang | |
132 | |
133 if d.selected_lang: | |
134 markup.append(("msg_lang", u"[{}] ".format(d.selected_lang))) | |
135 | |
136 # message body | |
137 markup.append(msg) | |
138 | |
139 return markup | |
140 | |
141 # events | |
142 def update(self, update_dict=None): | |
143 """update all the linked message widgets | |
144 | |
145 @param update_dict(dict, None): key=attribute updated value=new_value | |
146 """ | |
147 self.redraw() | |
148 | |
149 @total_ordering | |
150 class OccupantWidget(urwid.WidgetWrap): | |
151 | |
152 def __init__(self, occupant_data): | |
153 self.occupant_data = occupant_data | |
154 occupant_data.widgets.add(self) | |
155 markup = self._generateMarkup() | |
156 text = sat_widgets.ClickableText(markup) | |
157 urwid.connect_signal(text, | |
158 'click', | |
159 self.occupant_data.parent._occupantsClicked, | |
160 user_args=[self.occupant_data]) | |
161 super(OccupantWidget, self).__init__(text) | |
162 | |
163 def __eq__(self, other): | |
164 if other is None: | |
165 return False | |
166 return self.occupant_data.nick == other.occupant_data.nick | |
167 | |
168 def __lt__(self, other): | |
169 return self.occupant_data.nick.lower() < other.occupant_data.nick.lower() | |
170 | |
171 @property | |
172 def markup(self): | |
173 return self._generateMarkup() | |
174 | |
175 @property | |
176 def parent(self): | |
177 return self.mess_data.parent | |
178 | |
179 @property | |
180 def nick(self): | |
181 return self.occupant_data.nick | |
182 | |
183 def redraw(self): | |
184 self._w.set_text(self.markup) | |
185 self.occupant_data.parent.host.redraw() # FIXME: should not be necessary | |
186 | |
187 def selectable(self): | |
188 return True | |
189 | |
190 def keypress(self, size, key): | |
191 return key | |
192 | |
193 def get_cursor_coords(self, size): | |
194 return 0, 0 | |
195 | |
196 def render(self, size, focus=False): | |
197 # Text widget doesn't render cursor, but we want one | |
198 # so we add it here | |
199 canvas = urwid.CompositeCanvas(self._w.render(size, focus)) | |
200 if focus: | |
201 canvas.set_cursor(self.get_cursor_coords(size)) | |
202 return canvas | |
203 | |
204 def _generateMarkup(self): | |
205 # TODO: role and affiliation are shown in a Q&D way | |
206 # should be more intuitive and themable | |
207 o = self.occupant_data | |
208 markup = [] | |
209 markup.append(('info_msg', u'{}{} '.format( | |
210 o.role[0].upper(), | |
211 o.affiliation[0].upper(), | |
212 ))) | |
213 markup.append(o.nick) | |
214 if o.state is not None: | |
215 markup.append(u' {}'.format(C.CHAT_STATE_ICON[o.state])) | |
216 return markup | |
217 | |
218 # events | |
219 def update(self, update_dict=None): | |
220 self.redraw() | |
221 | |
222 | |
223 class OccupantsWidget(urwid.WidgetWrap): | |
224 | |
225 def __init__(self, parent): | |
226 self.parent = parent | |
227 self.occupants_walker = urwid.SimpleListWalker([]) | |
228 self.occupants_footer = urwid.Text('', align='center') | |
229 self.updateFooter() | |
230 occupants_widget = urwid.Frame(urwid.ListBox(self.occupants_walker), footer=self.occupants_footer) | |
231 super(OccupantsWidget, self).__init__(occupants_widget) | |
232 occupants_list = sorted(self.parent.occupants.keys(), key=lambda o:o.lower()) | |
233 for occupant in occupants_list: | |
234 occupant_data = self.parent.occupants[occupant] | |
235 self.occupants_walker.append(OccupantWidget(occupant_data)) | |
236 | |
237 def updateFooter(self): | |
238 """update footer widget""" | |
239 txt = OCCUPANTS_FOOTER.format(len(self.parent.occupants)) | |
240 self.occupants_footer.set_text(txt) | |
241 | |
242 def getNicks(self, start=u''): | |
243 """Return nicks of all occupants | |
244 | |
245 @param start(unicode): only return nicknames which start with this text | |
246 """ | |
247 return [w.nick for w in self.occupants_walker if isinstance(w, OccupantWidget) and w.nick.startswith(start)] | |
248 | |
249 def addUser(self, occupant_data): | |
250 """add a user to the list""" | |
251 bisect.insort(self.occupants_walker, OccupantWidget(occupant_data)) | |
252 self.updateFooter() | |
253 self.parent.host.redraw() # FIXME: should not be necessary | |
254 | |
255 def removeUser(self, occupant_data): | |
256 """remove a user from the list""" | |
257 for widget in occupant_data.widgets: | |
258 self.occupants_walker.remove(widget) | |
259 self.updateFooter() | |
260 self.parent.host.redraw() # FIXME: should not be necessary | |
261 | |
262 | |
263 class Chat(PrimitivusWidget, quick_chat.QuickChat): | |
264 | |
265 def __init__(self, host, target, type_=C.CHAT_ONE2ONE, nick=None, occupants=None, subject=None, profiles=None): | |
266 quick_chat.QuickChat.__init__(self, host, target, type_, nick, occupants, subject, profiles=profiles) | |
267 self.filters = [] # list of filter callbacks to apply | |
268 self.mess_walker = urwid.SimpleListWalker([]) | |
269 self.mess_widgets = urwid.ListBox(self.mess_walker) | |
270 self.chat_widget = urwid.Frame(self.mess_widgets) | |
271 self.chat_colums = urwid.Columns([('weight', 8, self.chat_widget)]) | |
272 self.pile = urwid.Pile([self.chat_colums]) | |
273 PrimitivusWidget.__init__(self, self.pile, self.target) | |
274 | |
275 # we must adapt the behaviour with the type | |
276 if type_ == C.CHAT_GROUP: | |
277 if len(self.chat_colums.contents) == 1: | |
278 self.occupants_widget = OccupantsWidget(self) | |
279 self.occupants_panel = sat_widgets.VerticalSeparator(self.occupants_widget) | |
280 self._appendOccupantsPanel() | |
281 self.host.addListener('presence', self.presenceListener, [profiles]) | |
282 | |
283 # focus marker is a separator indicated last visible message before focus was lost | |
284 self.focus_marker = None # link to current marker | |
285 self.focus_marker_set = None # True if a new marker has been inserted | |
286 self.show_timestamp = True | |
287 self.show_short_nick = False | |
288 self.show_title = 1 # 0: clip title; 1: full title; 2: no title | |
289 self.postInit() | |
290 | |
291 def keypress(self, size, key): | |
292 if key == a_key['OCCUPANTS_HIDE']: # user wants to (un)hide the occupants panel | |
293 if self.type == C.CHAT_GROUP: | |
294 widgets = [widget for (widget, options) in self.chat_colums.contents] | |
295 if self.occupants_panel in widgets: | |
296 self._removeOccupantsPanel() | |
297 else: | |
298 self._appendOccupantsPanel() | |
299 elif key == a_key['TIMESTAMP_HIDE']: # user wants to (un)hide timestamp | |
300 self.show_timestamp = not self.show_timestamp | |
301 self.redraw() | |
302 elif key == a_key['SHORT_NICKNAME']: # user wants to (not) use short nick | |
303 self.show_short_nick = not self.show_short_nick | |
304 self.redraw() | |
305 elif key == a_key['SUBJECT_SWITCH']: # user wants to (un)hide group's subject or change its apperance | |
306 if self.subject: | |
307 self.show_title = (self.show_title + 1) % 3 | |
308 if self.show_title == 0: | |
309 self.setSubject(self.subject, 'clip') | |
310 elif self.show_title == 1: | |
311 self.setSubject(self.subject, 'space') | |
312 elif self.show_title == 2: | |
313 self.chat_widget.header = None | |
314 self._invalidate() | |
315 elif key == a_key['GOTO_BOTTOM']: # user wants to focus last message | |
316 self.mess_widgets.focus_position = len(self.mess_walker) - 1 | |
317 | |
318 return super(Chat, self).keypress(size, key) | |
319 | |
320 def completion(self, text, completion_data): | |
321 """Completion method which complete nicknames in group chat | |
322 | |
323 for params, see [sat_widgets.AdvancedEdit] | |
324 """ | |
325 if self.type != C.CHAT_GROUP: | |
326 return text | |
327 | |
328 space = text.rfind(" ") | |
329 start = text[space + 1:] | |
330 words = self.occupants_widget.getNicks(start) | |
331 if not words: | |
332 return text | |
333 try: | |
334 word_idx = words.index(completion_data['last_word']) + 1 | |
335 except (KeyError, ValueError): | |
336 word_idx = 0 | |
337 else: | |
338 if word_idx == len(words): | |
339 word_idx = 0 | |
340 word = completion_data['last_word'] = words[word_idx] | |
341 return u"{}{}{}".format(text[:space + 1], word, ': ' if space < 0 else '') | |
342 | |
343 def getMenu(self): | |
344 """Return Menu bar""" | |
345 menu = sat_widgets.Menu(self.host.loop) | |
346 if self.type == C.CHAT_GROUP: | |
347 self.host.addMenus(menu, C.MENU_ROOM, {'room_jid': self.target.bare}) | |
348 game = _("Game") | |
349 menu.addMenu(game, "Tarot", self.onTarotRequest) | |
350 elif self.type == C.CHAT_ONE2ONE: | |
351 # FIXME: self.target is a bare jid, we need to check that | |
352 contact_list = self.host.contact_lists[self.profile] | |
353 if not self.target.resource: | |
354 full_jid = contact_list.getFullJid(self.target) | |
355 else: | |
356 full_jid = self.target | |
357 self.host.addMenus(menu, C.MENU_SINGLE, {'jid': full_jid}) | |
358 return menu | |
359 | |
360 def setFilter(self, args): | |
361 """set filtering of messages | |
362 | |
363 @param args(list[unicode]): filters following syntax "[filter]=[value]" | |
364 empty list to clear all filters | |
365 only lang=XX is handled for now | |
366 """ | |
367 del self.filters[:] | |
368 if args: | |
369 if args[0].startswith("lang="): | |
370 lang = args[0][5:].strip() | |
371 self.filters.append(lambda mess_data: lang in mess_data.message) | |
372 | |
373 self.printMessages() | |
374 | |
375 def presenceListener(self, entity, show, priority, statuses, profile): | |
376 """Update entity's presence status | |
377 | |
378 @param entity (jid.JID): entity updated | |
379 @param show: availability | |
380 @param priority: resource's priority | |
381 @param statuses: dict of statuses | |
382 @param profile: %(doc_profile)s | |
383 """ | |
384 # FIXME: disable for refactoring, need to be checked and re-enabled | |
385 return | |
386 # assert self.type == C.CHAT_GROUP | |
387 # if entity.bare != self.target: | |
388 # return | |
389 # self.update(entity) | |
390 | |
391 def createMessage(self, message): | |
392 self.appendMessage(message) | |
393 | |
394 def _user_moved(self, message): | |
395 """return true if message is a user left/joined message | |
396 | |
397 @param message(quick_chat.Message): message to add | |
398 """ | |
399 if message.type != C.MESS_TYPE_INFO: | |
400 return False | |
401 try: | |
402 info_type = message.extra['info_type'] | |
403 except KeyError: | |
404 return False | |
405 else: | |
406 return info_type in quick_chat.ROOM_USER_MOVED | |
407 | |
408 def _scrollDown(self): | |
409 """scroll down message only if we are already at the bottom (minus 1)""" | |
410 current_focus = self.mess_widgets.focus_position | |
411 bottom = len(self.mess_walker) - 1 | |
412 if current_focus == bottom - 1: | |
413 self.mess_widgets.focus_position = bottom # scroll down | |
414 self.host.redraw() # FIXME: should not be necessary | |
415 | |
416 def appendMessage(self, message): | |
417 """Create a MessageWidget and append it | |
418 | |
419 Can merge messages together is desirable (e.g.: multiple joined/leave) | |
420 @param message(quick_chat.Message): message to add | |
421 """ | |
422 if self.filters: | |
423 if not all([f(message) for f in self.filters]): | |
424 return | |
425 if self._user_moved(message): | |
426 for wid in reversed(self.mess_walker): | |
427 # we merge in/out messages if no message was sent meanwhile | |
428 if not isinstance(wid, MessageWidget): | |
429 continue | |
430 if wid.mess_data.type != C.MESS_TYPE_INFO: | |
431 break | |
432 if wid.info_type in quick_chat.ROOM_USER_MOVED and wid.mess_data.nick == message.nick: | |
433 try: | |
434 count = wid.reentered_count | |
435 except AttributeError: | |
436 count = wid.reentered_count = 1 | |
437 nick = wid.mess_data.nick | |
438 if message.info_type == quick_chat.ROOM_USER_LEFT: | |
439 wid.message = _(u"<= {nick} has left the room ({count})").format(nick=nick, count=count) | |
440 else: | |
441 wid.message = _(u"<=> {nick} re-entered the room ({count})") .format(nick=nick, count=count) | |
442 wid.reentered_count+=1 | |
443 return | |
444 | |
445 if ((self.host.selected_widget != self or not self.host.x_notify.hasFocus()) | |
446 and self.focus_marker_set is not None): | |
447 if not self.focus_marker_set and not self._locked and self.mess_walker: | |
448 if self.focus_marker is not None: | |
449 self.mess_walker.remove(self.focus_marker) | |
450 self.focus_marker = urwid.Divider('—') | |
451 self.mess_walker.append(self.focus_marker) | |
452 self.focus_marker_set = True | |
453 self._scrollDown() | |
454 else: | |
455 if self.focus_marker_set: | |
456 self.focus_marker_set = False | |
457 | |
458 if not message.message: | |
459 log.error(u"Received an empty message for uid {}".format(message.uid)) | |
460 else: | |
461 wid = MessageWidget(message) | |
462 self.mess_walker.append(wid) | |
463 self._scrollDown() | |
464 if self._user_moved(message): | |
465 return # no notification for moved messages | |
466 | |
467 # notifications | |
468 | |
469 if self._locked: | |
470 # we don't want notifications when locked | |
471 # because that's history messages | |
472 return | |
473 | |
474 if wid.mess_data.mention: | |
475 from_jid = wid.mess_data.from_jid | |
476 msg = _(u'You have been mentioned by {nick} in {room}'.format( | |
477 nick=wid.mess_data.nick, | |
478 room=self.target, | |
479 )) | |
480 self.host.notify(C.NOTIFY_MENTION, from_jid, msg, widget=self, profile=self.profile) | |
481 elif self.type == C.CHAT_ONE2ONE: | |
482 from_jid = wid.mess_data.from_jid | |
483 msg = _(u'{entity} is talking to you'.format( | |
484 entity=from_jid, | |
485 )) | |
486 self.host.notify(C.NOTIFY_MESSAGE, from_jid, msg, widget=self, profile=self.profile) | |
487 else: | |
488 self.host.notify(C.NOTIFY_MESSAGE, self.target, widget=self, profile=self.profile) | |
489 | |
490 | |
491 def addUser(self, nick): | |
492 occupant = super(Chat, self).addUser(nick) | |
493 self.occupants_widget.addUser(occupant) | |
494 | |
495 def removeUser(self, occupant_data): | |
496 occupant = super(Chat, self).removeUser(occupant_data) | |
497 if occupant is not None: | |
498 self.occupants_widget.removeUser(occupant) | |
499 | |
500 def _occupantsClicked(self, occupant, clicked_wid): | |
501 assert self.type == C.CHAT_GROUP | |
502 contact_list = self.host.contact_lists[self.profile] | |
503 | |
504 # we have a click on a nick, we need to create the widget if it doesn't exists | |
505 self.getOrCreatePrivateWidget(occupant.jid) | |
506 | |
507 # now we select the new window | |
508 for contact_list in self.host.widgets.getWidgets(ContactList, profiles=(self.profile,)): | |
509 contact_list.setFocus(occupant.jid, True) | |
510 | |
511 def _appendOccupantsPanel(self): | |
512 self.chat_colums.contents.append((self.occupants_panel, ('weight', 2, False))) | |
513 | |
514 def _removeOccupantsPanel(self): | |
515 for widget, options in self.chat_colums.contents: | |
516 if widget is self.occupants_panel: | |
517 self.chat_colums.contents.remove((widget, options)) | |
518 break | |
519 | |
520 def addGamePanel(self, widget): | |
521 """Insert a game panel to this Chat dialog. | |
522 | |
523 @param widget (Widget): the game panel | |
524 """ | |
525 assert (len(self.pile.contents) == 1) | |
526 self.pile.contents.insert(0, (widget, ('weight', 1))) | |
527 self.pile.contents.insert(1, (urwid.Filler(urwid.Divider('-'), ('fixed', 1)))) | |
528 self.host.redraw() | |
529 | |
530 def removeGamePanel(self, widget): | |
531 """Remove the game panel from this Chat dialog. | |
532 | |
533 @param widget (Widget): the game panel | |
534 """ | |
535 assert (len(self.pile.contents) == 3) | |
536 del self.pile.contents[0] | |
537 self.host.redraw() | |
538 | |
539 def setSubject(self, subject, wrap='space'): | |
540 """Set title for a group chat""" | |
541 quick_chat.QuickChat.setSubject(self, subject) | |
542 self.subj_wid = urwid.Text(unicode(subject.replace('\n', '|') if wrap == 'clip' else subject), | |
543 align='left' if wrap == 'clip' else 'center', wrap=wrap) | |
544 self.chat_widget.header = urwid.AttrMap(self.subj_wid, 'title') | |
545 self.host.redraw() | |
546 | |
547 ## Messages | |
548 | |
549 def printMessages(self, clear=True): | |
550 """generate message widgets | |
551 | |
552 @param clear(bool): clear message before printing if true | |
553 """ | |
554 if clear: | |
555 del self.mess_walker[:] | |
556 for message in self.messages.itervalues(): | |
557 self.appendMessage(message) | |
558 | |
559 def redraw(self): | |
560 """redraw all messages""" | |
561 for w in self.mess_walker: | |
562 try: | |
563 w.redraw() | |
564 except AttributeError: | |
565 pass | |
566 | |
567 def updateHistory(self, size=C.HISTORY_LIMIT_DEFAULT, filters=None, profile='@NONE@'): | |
568 del self.mess_walker[:] | |
569 if filters and 'search' in filters: | |
570 self.mess_walker.append(urwid.Text(_(u"Results for searching the globbing pattern: {}").format(filters['search']))) | |
571 self.mess_walker.append(urwid.Text(_(u"Type ':history <lines>' to reset the chat history"))) | |
572 super(Chat, self).updateHistory(size, filters, profile) | |
573 | |
574 def _onHistoryPrinted(self): | |
575 """Refresh or scroll down the focus after the history is printed""" | |
576 self.printMessages(clear=False) | |
577 super(Chat, self)._onHistoryPrinted() | |
578 | |
579 def onPrivateCreated(self, widget): | |
580 self.host.contact_lists[widget.profile].setSpecial(widget.target, C.CONTACT_SPECIAL_GROUP) | |
581 | |
582 def onSelected(self): | |
583 self.focus_marker_set = False | |
584 | |
585 def notify(self, contact="somebody", msg=""): | |
586 """Notify the user of a new message if primitivus doesn't have the focus. | |
587 | |
588 @param contact (unicode): contact who wrote to the users | |
589 @param msg (unicode): the message that has been received | |
590 """ | |
591 # FIXME: not called anymore after refactoring | |
592 if msg == "": | |
593 return | |
594 if self.mess_widgets.get_focus()[1] == len(self.mess_walker) - 2: | |
595 # we don't change focus if user is not at the bottom | |
596 # as that mean that he is probably watching discussion history | |
597 self.mess_widgets.focus_position = len(self.mess_walker) - 1 | |
598 self.host.redraw() | |
599 if not self.host.x_notify.hasFocus(): | |
600 if self.type == C.CHAT_ONE2ONE: | |
601 self.host.x_notify.sendNotification(_("Primitivus: %s is talking to you") % contact) | |
602 elif self.nick is not None and self.nick.lower() in msg.lower(): | |
603 self.host.x_notify.sendNotification(_("Primitivus: %(user)s mentioned you in room '%(room)s'") % {'user': contact, 'room': self.target}) | |
604 | |
605 # MENU EVENTS # | |
606 def onTarotRequest(self, menu): | |
607 # TODO: move this to plugin_misc_tarot with dynamic menu | |
608 if len(self.occupants) != 4: | |
609 self.host.showPopUp(sat_widgets.Alert(_("Can't start game"), _("You need to be exactly 4 peoples in the room to start a Tarot game"), ok_cb=self.host.removePopUp)) | |
610 else: | |
611 self.host.bridge.tarotGameCreate(self.target, list(self.occupants), self.profile) | |
612 | |
613 # MISC EVENTS # | |
614 | |
615 def onDelete(self): | |
616 # FIXME: to be checked after refactoring | |
617 super(Chat, self).onDelete() | |
618 if self.type == C.CHAT_GROUP: | |
619 self.host.removeListener('presence', self.presenceListener) | |
620 | |
621 def onChatState(self, from_jid, state, profile): | |
622 super(Chat, self).onChatState(from_jid, state, profile) | |
623 if self.type == C.CHAT_ONE2ONE: | |
624 self.title_dynamic = C.CHAT_STATE_ICON[state] | |
625 self.host.redraw() # FIXME: should not be necessary | |
626 | |
627 def _onSubjectDialogCb(self, button, dialog): | |
628 self.changeSubject(dialog.text) | |
629 self.host.removePopUp(dialog) | |
630 | |
631 def onSubjectDialog(self, new_subject=None): | |
632 dialog = sat_widgets.InputDialog( | |
633 _(u'Change title'), | |
634 _(u'Enter the new title'), | |
635 default_txt=new_subject if new_subject is not None else self.subject) | |
636 dialog.setCallback('ok', self._onSubjectDialogCb, dialog) | |
637 dialog.setCallback('cancel', lambda dummy: self.host.removePopUp(dialog)) | |
638 self.host.showPopUp(dialog) | |
639 | |
640 quick_widgets.register(quick_chat.QuickChat, Chat) | |
641 quick_widgets.register(quick_games.Tarot, game_tarot.TarotGame) |