diff options
Diffstat (limited to 'src/tabs/privatetab.py')
-rw-r--r-- | src/tabs/privatetab.py | 342 |
1 files changed, 342 insertions, 0 deletions
diff --git a/src/tabs/privatetab.py b/src/tabs/privatetab.py new file mode 100644 index 00000000..574a2218 --- /dev/null +++ b/src/tabs/privatetab.py @@ -0,0 +1,342 @@ +from gettext import gettext as _ + +import logging +log = logging.getLogger(__name__) + +import curses + +from . import ChatTab, MucTab, Tab + +import common +import fixes +import windows +import xhtml +from common import safeJID +from config import config +from decorators import refresh_wrapper +from logger import logger +from theming import get_theme, dump_tuple + +class PrivateTab(ChatTab): + """ + The tab containg a private conversation (someone from a MUC) + """ + message_type = 'chat' + plugin_commands = {} + additional_informations = {} + plugin_keys = {} + def __init__(self, name, nick): + ChatTab.__init__(self, name) + self.own_nick = nick + self.name = name + self.text_win = windows.TextWin() + self._text_buffer.add_window(self.text_win) + self.info_header = windows.PrivateInfoWin() + self.input = windows.MessageInput() + self.check_attention() + # keys + self.key_func['^I'] = self.completion + # commands + self.register_command('info', self.command_info, + desc=_('Display some information about the user in the MUC: its/his/her role, affiliation, status and status message.'), + shortdesc=_('Info about the user.')) + self.register_command('unquery', self.command_unquery, + shortdesc=_('Close the tab.')) + self.register_command('close', self.command_unquery, + shortdesc=_('Close the tab.')) + self.register_command('version', self.command_version, + desc=_('Get the software version of the current interlocutor (usually its XMPP client and Operating System).'), + shortdesc=_('Get the software version of a jid.')) + self.resize() + self.parent_muc = self.core.get_tab_by_name(safeJID(name).bare, MucTab) + self.on = True + self.update_commands() + self.update_keys() + + @property + def general_jid(self): + return self.get_name() + + @property + def nick(self): + return self.get_nick() + + @staticmethod + def add_information_element(plugin_name, callback): + """ + Lets a plugin add its own information to the PrivateInfoWin + """ + PrivateTab.additional_informations[plugin_name] = callback + + @staticmethod + def remove_information_element(plugin_name): + del PrivateTab.additional_informations[plugin_name] + + def load_logs(self, log_nb): + logs = logger.get_logs(safeJID(self.get_name()).full.replace('/', '\\'), log_nb) + + def log_message(self, txt, nickname, time=None, typ=1): + """ + Log the messages in the archives. + """ + if not logger.log_message(self.name, nickname, txt, date=time, typ=typ): + self.core.information(_('Unable to write in the log file'), 'Error') + + def on_close(self): + self.parent_muc.privates.remove(self) + + def completion(self): + """ + Called when Tab is pressed, complete the nickname in the input + """ + if self.complete_commands(self.input): + return + + # If we are not completing a command or a command's argument, complete a nick + compare_users = lambda x: x.last_talked + word_list = [user.nick for user in sorted(self.parent_muc.users, key=compare_users, reverse=True)\ + if user.nick != self.own_nick] + after = config.get('after_completion', ',')+" " + input_pos = self.input.pos + if ' ' not in self.input.get_text()[:input_pos] or (self.input.last_completion and\ + self.input.get_text()[:input_pos] == self.input.last_completion + after): + add_after = after + else: + add_after = '' + self.input.auto_completion(word_list, add_after, quotify=False) + empty_after = self.input.get_text() == '' or (self.input.get_text().startswith('/') and not self.input.get_text().startswith('//')) + self.send_composing_chat_state(empty_after) + + def command_say(self, line, attention=False, correct=False): + if not self.on: + return + msg = self.core.xmpp.make_message(self.get_name()) + msg['type'] = 'chat' + msg['body'] = line + # trigger the event BEFORE looking for colors. + # This lets a plugin insert \x19xxx} colors, that will + # be converted in xhtml. + self.core.events.trigger('private_say', msg, self) + if not msg['body']: + self.cancel_paused_delay() + self.text_win.refresh() + self.input.refresh() + return + user = self.parent_muc.get_user_by_name(self.own_nick) + replaced = False + if correct or msg['replace']['id']: + msg['replace']['id'] = self.last_sent_message['id'] + if config.get_by_tabname('group_corrections', 'true', self.get_name()).lower() != 'false': + try: + self.modify_message(msg['body'], self.last_sent_message['id'], msg['id'], + user=user, jid=self.core.xmpp.boundjid, nickname=self.own_nick) + replaced = True + except: + log.error('Unable to correct a message', exc_info=True) + else: + del msg['replace'] + + if msg['body'].find('\x19') != -1: + msg.enable('html') + msg['html']['body'] = xhtml.poezio_colors_to_html(msg['body']) + msg['body'] = xhtml.clean_text(msg['body']) + if config.get_by_tabname('send_chat_states', 'true', self.general_jid, True) == 'true' and self.remote_wants_chatstates is not False: + needed = 'inactive' if self.inactive else 'active' + msg['chat_state'] = needed + if attention and self.remote_supports_attention: + msg['attention'] = True + self.core.events.trigger('private_say_after', msg, self) + if not msg['body']: + self.cancel_paused_delay() + self.text_win.refresh() + self.input.refresh() + return + if not replaced: + self.add_message(msg['body'], + nickname=self.core.own_nick or self.own_nick, + forced_user=user, + nick_color=get_theme().COLOR_OWN_NICK, + identifier=msg['id'], + jid=self.core.xmpp.boundjid, + typ=1) + + self.last_sent_message = msg + msg.send() + self.cancel_paused_delay() + self.text_win.refresh() + self.input.refresh() + + def command_attention(self, message=''): + if message is not '': + self.command_say(message, attention=True) + else: + msg = self.core.xmpp.make_message(self.get_name()) + msg['type'] = 'chat' + msg['attention'] = True + msg.send() + + def check_attention(self): + self.core.xmpp.plugin['xep_0030'].get_info(jid=self.get_name(), block=False, timeout=5, callback=self.on_attention_checked) + + def on_attention_checked(self, iq): + if 'urn:xmpp:attention:0' in iq['disco_info'].get_features(): + self.core.information('Attention is supported', 'Info') + self.remote_supports_attention = True + self.commands['attention'] = (self.command_attention, _('Usage: /attention [message]\nAttention: Require the attention of the contact. Can also send a message along with the attention.'), None) + else: + self.remote_supports_attention = False + + def command_unquery(self, arg): + """ + /unquery + """ + self.core.close_tab() + + def command_version(self, arg): + """ + /version + """ + def callback(res): + if not res: + return self.core.information('Could not get the software version from %s' % (jid,), 'Warning') + version = '%s is running %s version %s on %s' % (jid, + res.get('name') or _('an unknown software'), + res.get('version') or _('unknown'), + res.get('os') or _('an unknown platform')) + self.core.information(version, 'Info') + if arg: + return self.core.command_version(arg) + jid = safeJID(self.name) + fixes.get_version(self.core.xmpp, jid, callback=callback) + + def command_info(self, arg): + """ + /info + """ + if arg: + self.parent_muc.command_info(arg) + else: + user = safeJID(self.name).resource + self.parent_muc.command_info(user) + + def resize(self): + if self.core.information_win_size >= self.height-3 or not self.visible: + return + self.need_resize = False + self.text_win.resize(self.height-2-self.core.information_win_size - Tab.tab_win_height(), self.width, 0, 0) + self.text_win.rebuild_everything(self._text_buffer) + self.info_header.resize(1, self.width, self.height-2-self.core.information_win_size - Tab.tab_win_height(), 0) + self.input.resize(1, self.width, self.height-1, 0) + + def refresh(self): + if self.need_resize: + self.resize() + log.debug(' TAB Refresh: %s',self.__class__.__name__) + self.text_win.refresh() + self.info_header.refresh(self.name, self.text_win, self.chatstate, PrivateTab.additional_informations) + self.info_win.refresh() + self.refresh_tab_win() + self.input.refresh() + + def refresh_info_header(self): + self.info_header.refresh(self.name, self.text_win, self.chatstate, PrivateTab.additional_informations) + self.input.refresh() + + def get_name(self): + return self.name + + def get_nick(self): + return safeJID(self.name).resource + + def on_input(self, key, raw): + if not raw and key in self.key_func: + self.key_func[key]() + return False + self.input.do_command(key, raw=raw) + if not self.on: + return False + empty_after = self.input.get_text() == '' or (self.input.get_text().startswith('/') and not self.input.get_text().startswith('//')) + tab = self.core.get_tab_by_name(safeJID(self.name).bare, MucTab) + if tab and tab.joined: + self.send_composing_chat_state(empty_after) + return False + + def on_lose_focus(self): + self.state = 'normal' + self.text_win.remove_line_separator() + self.text_win.add_line_separator(self._text_buffer) + tab = self.core.get_tab_by_name(safeJID(self.name).bare, MucTab) + if tab and tab.joined and config.get_by_tabname( + 'send_chat_states', 'true', self.general_jid, True) == 'true'\ + and not self.input.get_text() and self.on: + self.send_chat_state('inactive') + self.check_scrolled() + + def on_gain_focus(self): + self.state = 'current' + curses.curs_set(1) + tab = self.core.get_tab_by_name(safeJID(self.name).bare, MucTab) + if tab and tab.joined and config.get_by_tabname( + 'send_chat_states', 'true', self.general_jid, True) == 'true'\ + and not self.input.get_text() and self.on: + self.send_chat_state('active') + + def on_info_win_size_changed(self): + if self.core.information_win_size >= self.height-3: + return + self.text_win.resize(self.height-2-self.core.information_win_size - Tab.tab_win_height(), self.width, 0, 0) + self.info_header.resize(1, self.width, self.height-2-self.core.information_win_size - Tab.tab_win_height(), 0) + + def get_text_window(self): + return self.text_win + + def rename_user(self, old_nick, new_nick): + """ + The user changed her nick in the corresponding muc: update the tab’s name and + display a message. + """ + self.add_message('\x193}%(old)s\x19%(info_col)s} is now known as \x193}%(new)s' % {'old':old_nick, 'new':new_nick, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, typ=2) + new_jid = safeJID(self.name).bare+'/'+new_nick + self.name = new_jid + + @refresh_wrapper.conditional + def user_left(self, status_message, from_nick): + """ + The user left the associated MUC + """ + self.deactivate() + if not status_message: + self.add_message(_('\x191}%(spec)s \x193}%(nick)s\x19%(info_col)s} has left the room') % {'nick':from_nick, 'spec':get_theme().CHAR_QUIT, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, typ=2) + else: + self.add_message(_('\x191}%(spec)s \x193}%(nick)s\x19%(info_col)s} has left the room (%(status)s)"') % {'nick':from_nick, 'spec':get_theme().CHAR_QUIT, 'status': status_message, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, typ=2) + return self.core.current_tab() is self + + @refresh_wrapper.conditional + def user_rejoined(self, nick): + """ + The user (or at least someone with the same nick) came back in the MUC + """ + self.activate() + tab = self.core.get_tab_by_name(safeJID(self.name).bare, MucTab) + color = 3 + if tab and config.get_by_tabname('display_user_color_in_join_part', '', self.general_jid, True): + user = tab.get_user_by_name(nick) + if user: + color = dump_tuple(user.color) + self.add_message('\x194}%(spec)s \x19%(color)s}%(nick)s\x19%(info_col)s} joined the room' % {'nick':nick, 'color': color, 'spec':get_theme().CHAR_JOIN, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, typ=2) + return self.core.current_tab() is self + + def activate(self, reason=None): + self.on = True + if reason: + self.add_message(txt=reason, typ=2) + + def deactivate(self, reason=None): + self.on = False + if reason: + self.add_message(txt=reason, typ=2) + + def matching_names(self): + return [(3, safeJID(self.get_name()).resource), (4, self.get_name())] + + |