Mercurial > libervia-backend
comparison frontends/src/primitivus/chat.py @ 1963:a2bc5089c2eb
backend, frontends: message refactoring (huge commit):
/!\ several features are temporarily disabled, like notifications in frontends
next step in refactoring, with the following changes:
- jp: updated jp message to follow changes in backend/bridge
- jp: added --lang, --subject, --subject_lang, and --type options to jp message + fixed unicode handling for jid
- quick_frontend (QuickApp, QuickChat):
- follow backend changes
- refactored chat, message are now handled in OrderedDict and uid are kept so they can be updated
- Message and Occupant classes handle metadata, so frontend just have to display them
- Primitivus (Chat):
- follow backend/QuickFrontend changes
- info & standard messages are handled in the same MessageWidget class
- improved/simplified handling of messages, removed update() method
- user joined/left messages are merged when next to each other
- a separator is shown when message is received while widget is out of focus, so user can quickly see the new messages
- affiliation/role are shown (in a basic way for now) in occupants panel
- removed "/me" messages handling, as it will be done by a backend plugin
- message language is displayed when available (only one language per message for now)
- fixed :history and :search commands
- core (constants): new constants for messages type, XML namespace, entity type
- core: *Message methods renamed to follow new code sytle (e.g. sendMessageToBridge => messageSendToBridge)
- core (messages handling): fixed handling of language
- core (messages handling): mes_data['from'] and ['to'] are now jid.JID
- core (core.xmpp): reorganised message methods, added getNick() method to client.roster
- plugin text commands: fixed plugin and adapted to new messages behaviour. client is now used in arguments instead of profile
- plugins: added information for cancellation reason in CancelError calls
- plugin XEP-0045: various improvments, but this plugin still need work:
- trigger is used to avoid message already handled by the plugin to be handled a second time
- changed the way to handle history, the last message from DB is checked and we request only messages since this one, in seconds (thanks Poezio folks :))
- subject reception is waited before sending the roomJoined signal, this way we are sure that everything including history is ready
- cmd_* method now follow the new convention with client instead of profile
- roomUserJoined and roomUserLeft messages are removed, the events are now handled with info message with a "ROOM_USER_JOINED" info subtype
- probably other forgotten stuffs :p
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 20 Jun 2016 18:41:53 +0200 |
parents | 633b5c21aefd |
children | d727aab9a80e |
comparison
equal
deleted
inserted
replaced
1962:a45235d8dc93 | 1963:a2bc5089c2eb |
---|---|
21 from sat.core import log as logging | 21 from sat.core import log as logging |
22 log = logging.getLogger(__name__) | 22 log = logging.getLogger(__name__) |
23 import urwid | 23 import urwid |
24 from urwid_satext import sat_widgets | 24 from urwid_satext import sat_widgets |
25 from sat_frontends.quick_frontend import quick_widgets | 25 from sat_frontends.quick_frontend import quick_widgets |
26 from sat_frontends.quick_frontend.quick_chat import QuickChat | 26 from sat_frontends.quick_frontend import quick_chat |
27 from sat_frontends.quick_frontend import quick_games | 27 from sat_frontends.quick_frontend import quick_games |
28 from sat_frontends.primitivus import game_tarot | 28 from sat_frontends.primitivus import game_tarot |
29 from sat_frontends.primitivus.constants import Const as C | 29 from sat_frontends.primitivus.constants import Const as C |
30 from sat_frontends.primitivus.keys import action_key_map as a_key | 30 from sat_frontends.primitivus.keys import action_key_map as a_key |
31 from sat_frontends.primitivus.widget import PrimitivusWidget | 31 from sat_frontends.primitivus.widget import PrimitivusWidget |
32 import time | 32 import time |
33 from sat_frontends.tools import jid | 33 from sat_frontends.tools import jid |
34 | 34 from functools import total_ordering |
35 | 35 import bisect |
36 class ChatText(urwid.FlowWidget): | 36 |
37 """Manage the printing of chat message""" | 37 |
38 | 38 class MessageWidget(urwid.WidgetWrap): |
39 def __init__(self, parent, timestamp, nick, my_mess, message, align='left', is_info=False): | 39 |
40 self.parent = parent | 40 def __init__(self, mess_data): |
41 self.timestamp = time.localtime(timestamp) | 41 """ |
42 self.nick = nick | 42 @param mess_data(quick_chat.Message, None): message data |
43 self.my_mess = my_mess | 43 None: used only for non text widgets (e.g.: focus separator) |
44 self.message = unicode(message) | 44 """ |
45 self.align = align | 45 self.mess_data = mess_data |
46 self.is_info = is_info | 46 mess_data.widgets.add(self) |
47 self.timestamp = time.localtime(mess_data.timestamp) | |
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 message = self.mess_data.message | |
66 if self.parent.lang in message: | |
67 self.selected_lang = self.parent.lang | |
68 return message[self.parent.lang] | |
69 try: | |
70 self.selected_lang = '' | |
71 return message[''] | |
72 except KeyError: | |
73 try: | |
74 lang, mess = message.iteritems().next() | |
75 self.selected_lang = lang | |
76 return mess | |
77 except StopIteration: | |
78 log.error(u"Can't find message for uid {}".format(self.mess_data.uid)) | |
79 | |
80 @message.setter | |
81 def message(self, value): | |
82 self.mess_data.message = {'':value} | |
83 self._w.set_text(self.markup) | |
84 | |
85 @property | |
86 def type(self): | |
87 try: | |
88 return self.mess_data.type | |
89 except AttributeError: | |
90 return C.MESS_TYPE_INFO | |
47 | 91 |
48 def selectable(self): | 92 def selectable(self): |
49 return True | 93 return True |
50 | 94 |
51 def keypress(self, size, key): | 95 def keypress(self, size, key): |
52 return key | 96 return key |
53 | 97 |
54 def rows(self, size, focus=False): | 98 def get_cursor_coords(self, size): |
55 return self.display_widget(size, focus).rows(size, focus) | 99 return 0, 0 |
56 | 100 |
57 def render(self, size, focus=False): | 101 def render(self, size, focus=False): |
58 canvas = urwid.CompositeCanvas(self.display_widget(size, focus).render(size, focus)) | 102 # Text widget doesn't render cursor, but we want one |
103 # so we add it here | |
104 canvas = urwid.CompositeCanvas(self._w.render(size, focus)) | |
59 if focus: | 105 if focus: |
60 canvas.set_cursor(self.get_cursor_coords(size)) | 106 canvas.set_cursor(self.get_cursor_coords(size)) |
61 return canvas | 107 return canvas |
62 | 108 |
109 def _generateInfoMarkup(self): | |
110 return ('info_msg', self.message) | |
111 | |
112 def _generateMarkup(self): | |
113 """Generate text markup according to message data and Widget options""" | |
114 markup = [] | |
115 d = self.mess_data | |
116 | |
117 # timestamp | |
118 if self.parent.show_timestamp: | |
119 # if the message was sent before today, we print the full date | |
120 time_format = "%c" if self.timestamp < self.parent.day_change else "%H:%M" | |
121 markup.append(('date', "[{}]".format(time.strftime(time_format, self.timestamp).decode('utf-8')))) | |
122 | |
123 # nickname | |
124 if self.parent.show_short_nick: | |
125 markup.append(('my_nick' if d.own_mess else 'other_nick', "**" if d.own_mess else "*")) | |
126 else: | |
127 markup.append(('my_nick' if d.own_mess else 'other_nick', u"[{}] ".format(d.nick or ''))) | |
128 | |
129 msg = self.message # needed to generate self.selected_lang | |
130 | |
131 if self.selected_lang: | |
132 markup.append(("msg_lang", u"[{}] ".format(self.selected_lang))) | |
133 | |
134 # message body | |
135 markup.append(msg) | |
136 | |
137 return markup | |
138 | |
139 @total_ordering | |
140 class OccupantWidget(urwid.WidgetWrap): | |
141 | |
142 def __init__(self, occupant_data): | |
143 self.occupant_data = occupant_data | |
144 occupant_data.widgets.add(self) | |
145 markup = self._generateMarkup() | |
146 super(OccupantWidget, self).__init__(urwid.Text(markup)) | |
147 | |
148 def __eq__(self, other): | |
149 return self.occupant_data.nick == other.occupant_data.nick | |
150 | |
151 def __lt__(self, other): | |
152 return self.occupant_data.nick.lower() < other.occupant_data.nick.lower() | |
153 | |
154 @property | |
155 def parent(self): | |
156 return self.mess_data.parent | |
157 | |
158 def selectable(self): | |
159 return True | |
160 | |
161 def keypress(self, size, key): | |
162 return key | |
163 | |
63 def get_cursor_coords(self, size): | 164 def get_cursor_coords(self, size): |
64 return 0, 0 | 165 return 0, 0 |
65 | 166 |
66 def display_widget(self, size, focus): | 167 def render(self, size, focus=False): |
67 render_txt = [] | 168 # Text widget doesn't render cursor, but we want one |
68 if not self.is_info: | 169 # so we add it here |
69 if self.parent.show_timestamp: | 170 canvas = urwid.CompositeCanvas(self._w.render(size, focus)) |
70 time_format = "%c" if self.timestamp < self.parent.day_change else "%H:%M" # if the message was sent before today, we print the full date | 171 if focus: |
71 render_txt.append(('date', "[%s]" % time.strftime(time_format, self.timestamp).decode('utf-8'))) | 172 canvas.set_cursor(self.get_cursor_coords(size)) |
72 if self.parent.show_short_nick: | 173 return canvas |
73 render_txt.append(('my_nick' if self.my_mess else 'other_nick', "**" if self.my_mess else "*")) | 174 |
74 else: | 175 def _generateMarkup(self): |
75 render_txt.append(('my_nick' if self.my_mess else 'other_nick', "[%s] " % (self.nick or ''))) | 176 # TODO: role and affiliation are shown in a Q&D way |
76 render_txt.append(self.message) | 177 # should be more intuitive and themable |
77 txt_widget = urwid.Text(render_txt, align=self.align) | 178 o = self.occupant_data |
78 if self.is_info: | 179 markup = [] |
79 return urwid.AttrMap(txt_widget, 'info_msg') | 180 markup.append(('info_msg', '{}{} '.format( |
80 return txt_widget | 181 o.role[0].upper(), |
81 | 182 o.affiliation[0].upper(), |
82 | 183 ))) |
83 class Chat(PrimitivusWidget, QuickChat): | 184 markup.append(o.nick) |
84 | 185 return markup |
85 def __init__(self, host, target, type_=C.CHAT_ONE2ONE, profiles=None): | 186 |
86 QuickChat.__init__(self, host, target, type_, profiles=profiles) | 187 |
87 self.content = urwid.SimpleListWalker([]) | 188 class Chat(PrimitivusWidget, quick_chat.QuickChat): |
88 self.text_list = urwid.ListBox(self.content) | 189 |
89 self.chat_widget = urwid.Frame(self.text_list) | 190 def __init__(self, host, target, type_=C.CHAT_ONE2ONE, occupants=None, subject=None, profiles=None): |
191 quick_chat.QuickChat.__init__(self, host, target, type_, occupants, subject, profiles=profiles) | |
192 self.mess_walker = urwid.SimpleListWalker([]) | |
193 self.mess_widgets = urwid.ListBox(self.mess_walker) | |
194 self.chat_widget = urwid.Frame(self.mess_widgets) | |
90 self.chat_colums = urwid.Columns([('weight', 8, self.chat_widget)]) | 195 self.chat_colums = urwid.Columns([('weight', 8, self.chat_widget)]) |
91 self.pile = urwid.Pile([self.chat_colums]) | 196 self.pile = urwid.Pile([self.chat_colums]) |
92 PrimitivusWidget.__init__(self, self.pile, self.target) | 197 PrimitivusWidget.__init__(self, self.pile, self.target) |
93 | 198 |
94 # we must adapt the behaviour with the type | 199 # we must adapt the behaviour with the type |
95 if type_ == C.CHAT_GROUP: | 200 if type_ == C.CHAT_GROUP: |
96 if len(self.chat_colums.contents) == 1: | 201 if len(self.chat_colums.contents) == 1: |
97 self.occupants_list = sat_widgets.GenericList([], option_type=sat_widgets.ClickableText, on_click=self._occupantsClicked) | 202 self.occupants_walker = urwid.SimpleListWalker([]) |
98 self.occupants_panel = sat_widgets.VerticalSeparator(self.occupants_list) | 203 # TODO: put a real ContactPanel class here, based on FocusWidget ? |
204 self.occupants_widgets = urwid.ListBox(self.occupants_walker) | |
205 # FIXME | |
206 # , option_type=sat_widgets.ClickableText, on_click=self._occupantsClicked) | |
207 self.occupants_panel = sat_widgets.VerticalSeparator(self.occupants_widgets) | |
99 self._appendOccupantsPanel() | 208 self._appendOccupantsPanel() |
209 occupants_list = sorted(self.occupants.keys(), key=lambda o:o.lower()) | |
210 for occupant in occupants_list: | |
211 occupant_data = self.occupants[occupant] | |
212 self.occupants_walker.append(OccupantWidget(occupant_data)) | |
213 | |
100 self.host.addListener('presence', self.presenceListener, [profiles]) | 214 self.host.addListener('presence', self.presenceListener, [profiles]) |
101 | 215 |
216 # focus marker is a separator indicated last visible message before focus was lost | |
217 self.focus_marker = None # link to current marker | |
218 self.focus_marker_set = None # True if a new marker has been inserted | |
102 self.day_change = time.strptime(time.strftime("%a %b %d 00:00:00 %Y")) # struct_time of day changing time | 219 self.day_change = time.strptime(time.strftime("%a %b %d 00:00:00 %Y")) # struct_time of day changing time |
103 self.show_timestamp = True | 220 self.show_timestamp = True |
104 self.show_short_nick = False | 221 self.show_short_nick = False |
105 self.show_title = 1 # 0: clip title; 1: full title; 2: no title | 222 self.show_title = 1 # 0: clip title; 1: full title; 2: no title |
106 self.subject = None | 223 self.postInit() |
107 | 224 |
108 def keypress(self, size, key): | 225 def keypress(self, size, key): |
109 if key == a_key['OCCUPANTS_HIDE']: # user wants to (un)hide the occupants panel | 226 if key == a_key['OCCUPANTS_HIDE']: # user wants to (un)hide the occupants panel |
110 if self.type == C.CHAT_GROUP: | 227 if self.type == C.CHAT_GROUP: |
111 widgets = [widget for (widget, options) in self.chat_colums.contents] | 228 widgets = [widget for (widget, options) in self.chat_colums.contents] |
113 self._removeOccupantsPanel() | 230 self._removeOccupantsPanel() |
114 else: | 231 else: |
115 self._appendOccupantsPanel() | 232 self._appendOccupantsPanel() |
116 elif key == a_key['TIMESTAMP_HIDE']: # user wants to (un)hide timestamp | 233 elif key == a_key['TIMESTAMP_HIDE']: # user wants to (un)hide timestamp |
117 self.show_timestamp = not self.show_timestamp | 234 self.show_timestamp = not self.show_timestamp |
118 for wid in self.content: | 235 for wid in self.mess_walker: |
119 wid._invalidate() | 236 wid._invalidate() |
120 elif key == a_key['SHORT_NICKNAME']: # user wants to (not) use short nick | 237 elif key == a_key['SHORT_NICKNAME']: # user wants to (not) use short nick |
121 self.show_short_nick = not self.show_short_nick | 238 self.show_short_nick = not self.show_short_nick |
122 for wid in self.content: | 239 for wid in self.mess_walker: |
123 wid._invalidate() | 240 wid._invalidate() |
124 elif key == a_key['SUBJECT_SWITCH']: # user wants to (un)hide group's subject or change its apperance | 241 elif key == a_key['SUBJECT_SWITCH']: # user wants to (un)hide group's subject or change its apperance |
125 if self.subject: | 242 if self.subject: |
126 self.show_title = (self.show_title + 1) % 3 | 243 self.show_title = (self.show_title + 1) % 3 |
127 if self.show_title == 0: | 244 if self.show_title == 0: |
158 @param show: availability | 275 @param show: availability |
159 @param priority: resource's priority | 276 @param priority: resource's priority |
160 @param statuses: dict of statuses | 277 @param statuses: dict of statuses |
161 @param profile: %(doc_profile)s | 278 @param profile: %(doc_profile)s |
162 """ | 279 """ |
163 assert self.type == C.CHAT_GROUP | 280 # FIXME: disable for refactoring, need to be checked and re-enabled |
164 if entity.bare != self.target: | 281 return |
165 return | 282 # assert self.type == C.CHAT_GROUP |
166 self.update(entity) | 283 # if entity.bare != self.target: |
167 | 284 # return |
168 def update(self, entity=None): | 285 # self.update(entity) |
169 """Update one or all entities. | 286 |
170 | 287 def createMessage(self, message): |
171 @param entity (jid.JID): entity to update | 288 self.appendMessage(message) |
172 """ | 289 |
173 contact_list = self.host.contact_lists[self.profile] | 290 def _user_moved(self, message): |
174 | 291 """return true if message is a user left/joined message |
175 if self.type == C.CHAT_ONE2ONE: # only update the chat title | 292 |
176 states = self.getEntityStates(self.target) | 293 @param message(quick_chat.Message): message to add |
177 self.title_dynamic = ' '.join([u'({})'.format(state) for state in states.values()]) | 294 """ |
178 self.host.redraw() | 295 if message.type != C.MESS_TYPE_INFO: |
179 return | 296 return False |
180 | 297 try: |
181 nicks = list(self.occupants) | 298 info_type = message.extra['info_type'] |
182 if entity is None: # rebuild all the occupants list | 299 except KeyError: |
183 values = [] | 300 return False |
184 nicks.sort() | 301 else: |
185 for nick in nicks: | 302 return info_type in quick_chat.ROOM_USER_MOVED |
186 values.append(self._buildOccupantMarkup(jid.newResource(self.target, nick))) | 303 |
187 self.occupants_list.changeValues(values) | 304 def appendMessage(self, message): |
188 else: # add, remove or update only one occupant | 305 """Create a MessageWidget and append it |
189 nick = entity.resource | 306 |
190 show = contact_list.getCache(entity, C.PRESENCE_SHOW) | 307 Can merge messages together is desirable (e.g.: multiple joined/leave) |
191 if show == C.PRESENCE_UNAVAILABLE or show is None: | 308 @param message(quick_chat.Message): message to add |
192 try: | 309 """ |
193 self.occupants_list.deleteValue(nick) | 310 if self._user_moved(message): |
194 except ValueError: | 311 for wid in reversed(self.mess_walker): |
195 pass | 312 # we merge in/out messages if no message was sent meanwhile |
196 else: | 313 if not isinstance(wid, MessageWidget): |
197 values = self.occupants_list.getAllValues() | 314 continue |
198 markup = self._buildOccupantMarkup(entity) | 315 if wid.mess_data.type != C.MESS_TYPE_INFO: |
199 if not values: # room has just been created | 316 break |
200 values = [markup] | 317 if wid.info_type in quick_chat.ROOM_USER_MOVED and wid.mess_data.nick == message.nick: |
201 else: # add or update the occupant, keep the list sorted | 318 try: |
202 index = 0 | 319 count = wid.reentered_count |
203 for entry in values: | 320 except AttributeError: |
204 order = cmp(entry.value if hasattr(entry, 'value') else entry, nick) | 321 count = wid.reentered_count = 1 |
205 if order < 0: | 322 nick = wid.mess_data.nick |
206 index += 1 | 323 if message.info_type == quick_chat.ROOM_USER_LEFT: |
207 continue | 324 wid.message = _(u"<= {nick} has left the room ({count})").format(nick=nick, count=count) |
208 if order > 0: # insert the occupant | 325 else: |
209 values.insert(index, markup) | 326 wid.message = _(u"<=> {nick} re-entered the room ({count})") .format(nick=nick, count=count) |
210 else: # update an existing occupant | 327 wid.reentered_count+=1 |
211 values[index] = markup | 328 return |
212 break | 329 |
213 if index == len(values): # add to the end of the list | 330 if ((self.host.selected_widget != self or not self.host.x_notify.hasFocus()) |
214 values.append(markup) | 331 and self.focus_marker_set is not None): |
215 self.occupants_list.changeValues(values) | 332 if not self.focus_marker_set and not self._locked and self.mess_walker: |
216 self.host.redraw() | 333 if self.focus_marker is not None: |
217 | 334 self.mess_walker.remove(self.focus_marker) |
218 def _buildOccupantMarkup(self, entity): | 335 self.focus_marker = urwid.Divider('—') |
219 """Return the option attributes for a MUC occupant. | 336 self.mess_walker.append(self.focus_marker) |
220 | 337 self.focus_marker_set = True |
221 @param nick (unicode): occupant nickname | 338 else: |
222 """ | 339 if self.focus_marker_set: |
223 # TODO: for now it's not a markup but a simple text, the problem is that ListOption is unicode and not urwid.Text | 340 self.focus_marker_set = False |
224 contact_list = self.host.contact_lists[self.profile] | 341 |
225 show = contact_list.getCache(entity, C.PRESENCE_SHOW) | 342 if not message.message: |
226 states = self.getEntityStates(entity) | 343 log.error(u"Received an empty message for uid {}".format(message.uid)) |
227 nick = entity.resource | 344 else: |
228 show_icon, entity_attr = C.PRESENCE.get(show, (u'', u'default')) # TODO: use entity_attr and return (nick, markup) | 345 self.mess_walker.append(MessageWidget(message)) |
229 text = "%s%s %s" % (u''.join(states.values()), show_icon, nick) | 346 self.mess_widgets.focus_position = len(self.mess_walker) - 1 # scroll down |
230 return (nick, text) | 347 self.host.redraw() # FIXME: should not be necessary |
348 | |
349 def addUser(self, nick): | |
350 occupant = super(Chat, self).addUser(nick) | |
351 bisect.insort(self.occupants_walker, OccupantWidget(occupant)) | |
352 | |
353 def removeUser(self, occupant_data): | |
354 occupant = super(Chat, self).removeUser(occupant_data) | |
355 if occupant is not None: | |
356 for widget in occupant.widgets: | |
357 self.occupants_walker.remove(widget) | |
231 | 358 |
232 def _occupantsClicked(self, list_wid, clicked_wid): | 359 def _occupantsClicked(self, list_wid, clicked_wid): |
360 # FIXME: not called anymore after refactoring | |
233 assert self.type == C.CHAT_GROUP | 361 assert self.type == C.CHAT_GROUP |
234 nick = clicked_wid.getValue().value | 362 nick = clicked_wid.getValue().value |
235 if nick == self.nick: | 363 if nick == self.nick: |
236 # We ignore clicks on our own nick | 364 # We ignore clicks on our own nick |
237 return | 365 return |
272 del self.pile.contents[0] | 400 del self.pile.contents[0] |
273 self.host.redraw() | 401 self.host.redraw() |
274 | 402 |
275 def setSubject(self, subject, wrap='space'): | 403 def setSubject(self, subject, wrap='space'): |
276 """Set title for a group chat""" | 404 """Set title for a group chat""" |
277 QuickChat.setSubject(self, subject) | 405 quick_chat.QuickChat.setSubject(self, subject) |
278 self.subject = subject | |
279 self.subj_wid = urwid.Text(unicode(subject.replace('\n', '|') if wrap == 'clip' else subject), | 406 self.subj_wid = urwid.Text(unicode(subject.replace('\n', '|') if wrap == 'clip' else subject), |
280 align='left' if wrap == 'clip' else 'center', wrap=wrap) | 407 align='left' if wrap == 'clip' else 'center', wrap=wrap) |
281 self.chat_widget.header = urwid.AttrMap(self.subj_wid, 'title') | 408 self.chat_widget.header = urwid.AttrMap(self.subj_wid, 'title') |
282 self.host.redraw() | 409 self.host.redraw() |
283 | 410 |
284 def clearHistory(self): | 411 ## Messages |
285 """Clear the content of this chat.""" | 412 |
286 del self.content[:] | 413 def updateHistory(self, size=C.HISTORY_LIMIT_DEFAULT, search='', profile='@NONE@'): |
287 | 414 del self.mess_walker[:] |
288 def afterHistoryPrint(self): | 415 if search: |
416 self.mess_walker.append(urwid.Text(_(u"Results for searching the globbing pattern: {}").format(search))) | |
417 self.mess_walker.append(urwid.Text(_(u"Type ':history <lines>' to reset the chat history").format(search))) | |
418 super(Chat, self).updateHistory(size, search, profile) | |
419 | |
420 def _onHistoryPrinted(self): | |
289 """Refresh or scroll down the focus after the history is printed""" | 421 """Refresh or scroll down the focus after the history is printed""" |
290 if len(self.content): | 422 for message in self.messages.itervalues(): |
291 self.text_list.focus_position = len(self.content) - 1 # scroll down | 423 self.appendMessage(message) |
292 self.host.redraw() | 424 super(Chat, self)._onHistoryPrinted() |
293 | 425 |
294 def onPrivateCreated(self, widget): | 426 def onPrivateCreated(self, widget): |
295 self.host.contact_lists[widget.profile].specialResourceVisible(widget.target) | 427 self.host.contact_lists[widget.profile].specialResourceVisible(widget.target) |
296 | 428 |
297 def printMessage(self, nick, my_message, message, timestamp, extra=None, profile=C.PROF_KEY_NONE): | 429 def onSelected(self): |
298 """Print message in chat window. | 430 self.focus_marker_set = False |
299 | |
300 @param nick (unicode): author nick | |
301 @param my_message (boolean): True if profile is the author | |
302 @param message (unicode): message content | |
303 @param extra (dict): extra data | |
304 """ | |
305 new_text = ChatText(self, timestamp, nick, my_message, message) | |
306 self.content.append(new_text) | |
307 QuickChat.printMessage(self, nick, my_message, message, timestamp, extra, profile) | |
308 | |
309 def printInfo(self, msg, type_='normal', extra=None): | |
310 """Print general info | |
311 @param msg: message to print | |
312 @type_: one of: | |
313 normal: general info like "toto has joined the room" | |
314 me: "/me" information like "/me clenches his fist" ==> "toto clenches his fist" | |
315 @param timestamp (float): number of seconds since epoch | |
316 """ | |
317 if extra is None: | |
318 extra = {} | |
319 try: | |
320 timestamp = float(extra['timestamp']) | |
321 except KeyError: | |
322 timestamp = None | |
323 _widget = ChatText(self, timestamp, None, False, msg, is_info=True) | |
324 self.content.append(_widget) | |
325 QuickChat.printInfo(self, msg, type_, extra) | |
326 | 431 |
327 def notify(self, contact="somebody", msg=""): | 432 def notify(self, contact="somebody", msg=""): |
328 """Notify the user of a new message if primitivus doesn't have the focus. | 433 """Notify the user of a new message if primitivus doesn't have the focus. |
329 | 434 |
330 @param contact (unicode): contact who wrote to the users | 435 @param contact (unicode): contact who wrote to the users |
331 @param msg (unicode): the message that has been received | 436 @param msg (unicode): the message that has been received |
332 """ | 437 """ |
438 # FIXME: not called anymore after refactoring | |
333 if msg == "": | 439 if msg == "": |
334 return | 440 return |
335 if self.text_list.get_focus()[1] == len(self.content) - 2: | 441 if self.mess_widgets.get_focus()[1] == len(self.mess_walker) - 2: |
336 # we don't change focus if user is not at the bottom | 442 # we don't change focus if user is not at the bottom |
337 # as that mean that he is probably watching discussion history | 443 # as that mean that he is probably watching discussion history |
338 self.text_list.focus_position = len(self.content) - 1 | 444 self.mess_widgets.focus_position = len(self.mess_walker) - 1 |
339 self.host.redraw() | 445 self.host.redraw() |
340 if not self.host.x_notify.hasFocus(): | 446 if not self.host.x_notify.hasFocus(): |
341 if self.type == C.CHAT_ONE2ONE: | 447 if self.type == C.CHAT_ONE2ONE: |
342 self.host.x_notify.sendNotification(_("Primitivus: %s is talking to you") % contact) | 448 self.host.x_notify.sendNotification(_("Primitivus: %s is talking to you") % contact) |
343 elif self.nick is not None and self.nick.lower() in msg.lower(): | 449 elif self.nick is not None and self.nick.lower() in msg.lower(): |
352 self.host.bridge.tarotGameCreate(self.target, list(self.occupants), self.profile) | 458 self.host.bridge.tarotGameCreate(self.target, list(self.occupants), self.profile) |
353 | 459 |
354 # MISC EVENTS # | 460 # MISC EVENTS # |
355 | 461 |
356 def onDelete(self): | 462 def onDelete(self): |
357 QuickChat.onDelete(self) | 463 # FIXME: to be checked after refactoring |
464 quick_chat.QuickChat.onDelete(self) | |
358 if self.type == C.CHAT_GROUP: | 465 if self.type == C.CHAT_GROUP: |
359 self.host.removeListener('presence', self.presenceListener) | 466 self.host.removeListener('presence', self.presenceListener) |
360 | 467 |
361 | 468 |
362 quick_widgets.register(QuickChat, Chat) | 469 quick_widgets.register(quick_chat.QuickChat, Chat) |
363 quick_widgets.register(quick_games.Tarot, game_tarot.TarotGame) | 470 quick_widgets.register(quick_games.Tarot, game_tarot.TarotGame) |