summaryrefslogtreecommitdiff
path: root/src/tabs/privatetab.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/tabs/privatetab.py')
-rw-r--r--src/tabs/privatetab.py342
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())]
+
+