summaryrefslogtreecommitdiff
path: root/poezio/tabs/privatetab.py
diff options
context:
space:
mode:
Diffstat (limited to 'poezio/tabs/privatetab.py')
-rw-r--r--poezio/tabs/privatetab.py362
1 files changed, 362 insertions, 0 deletions
diff --git a/poezio/tabs/privatetab.py b/poezio/tabs/privatetab.py
new file mode 100644
index 00000000..a715a922
--- /dev/null
+++ b/poezio/tabs/privatetab.py
@@ -0,0 +1,362 @@
+"""
+Module for the PrivateTab
+
+A PrivateTab is a private conversation opened with someone from a MUC
+(see muctab.py). The conversation happens with both JID being relative
+to the MUC (room@server/nick1 and room@server/nick2).
+
+This tab references his parent room, and is modified to keep track of
+both participant’s nicks. It also has slightly different features than
+the ConversationTab (such as tab-completion on nicks from the room).
+
+"""
+import logging
+log = logging.getLogger(__name__)
+
+import curses
+
+from . import OneToOneTab, MucTab, Tab
+
+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
+from decorators import command_args_parser
+
+class PrivateTab(OneToOneTab):
+ """
+ The tab containg a private conversation (someone from a MUC)
+ """
+ message_type = 'chat'
+ plugin_commands = {}
+ additional_informations = {}
+ plugin_keys = {}
+ def __init__(self, name, nick):
+ OneToOneTab.__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()
+ # 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.name
+
+ def get_dest_jid(self):
+ return self.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.name).full.replace('/', '\\'), log_nb)
+ return logs
+
+ 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)
+
+ @command_args_parser.raw
+ def command_say(self, line, attention=False, correct=False):
+ if not self.on:
+ return
+ msg = self.core.xmpp.make_message(self.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', self.name):
+ 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', self.general_jid) 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.own_nick or self.core.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
+ if self.remote_supports_receipts:
+ msg._add_receipt = True
+ msg.send()
+ self.cancel_paused_delay()
+ self.text_win.refresh()
+ self.input.refresh()
+
+ @command_args_parser.ignored
+ def command_unquery(self):
+ """
+ /unquery
+ """
+ self.core.close_tab()
+
+ @command_args_parser.quoted(0, 1)
+ def command_version(self, args):
+ """
+ /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 args:
+ return self.core.command_version(args[0])
+ jid = safeJID(self.name)
+ fixes.get_version(self.core.xmpp, jid,
+ callback=callback)
+
+ @command_args_parser.quoted(0, 1)
+ def command_info(self, arg):
+ """
+ /info
+ """
+ if arg and arg[0]:
+ self.parent_muc.command_info(arg[0])
+ else:
+ user = safeJID(self.name).resource
+ self.parent_muc.command_info(user)
+
+ def resize(self):
+ self.need_resize = False
+
+ if self.size.tab_degrade_y:
+ info_win_height = 0
+ tab_win_height = 0
+ else:
+ info_win_height = self.core.information_win_size
+ tab_win_height = Tab.tab_win_height()
+
+ self.text_win.resize(self.height - 2 - info_win_height - 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 - info_win_height
+ - 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__)
+ display_info_win = not self.size.tab_degrade_y
+
+ self.text_win.refresh()
+ self.info_header.refresh(self.name, self.text_win, self.chatstate,
+ PrivateTab.additional_informations)
+ if display_info_win:
+ 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_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):
+ if self.input.text:
+ self.state = 'nonempty'
+ else:
+ 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',
+ self.general_jid) 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',
+ self.general_jid,) 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
+
+ @refresh_wrapper.conditional
+ 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
+ return self.core.current_tab() is self
+
+ @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()
+ self.check_features()
+ 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):
+ 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
+ self.remote_wants_chatstates = None
+ if reason:
+ self.add_message(txt=reason, typ=2)
+
+ def matching_names(self):
+ return [(3, safeJID(self.name).resource), (4, self.name)]
+
+