diff options
-rw-r--r-- | plugins/day_change.py | 9 | ||||
-rw-r--r-- | plugins/embed.py | 13 | ||||
-rw-r--r-- | plugins/lastlog.py | 16 | ||||
-rw-r--r-- | plugins/otr.py | 92 | ||||
-rw-r--r-- | poezio/core/core.py | 55 | ||||
-rw-r--r-- | poezio/core/handlers.py | 131 | ||||
-rw-r--r-- | poezio/mam.py | 20 | ||||
-rw-r--r-- | poezio/tabs/basetabs.py | 58 | ||||
-rw-r--r-- | poezio/tabs/conversationtab.py | 25 | ||||
-rw-r--r-- | poezio/tabs/muctab.py | 106 | ||||
-rw-r--r-- | poezio/tabs/privatetab.py | 100 | ||||
-rw-r--r-- | poezio/tabs/rostertab.py | 5 | ||||
-rw-r--r-- | poezio/tabs/xmltab.py | 2 | ||||
-rw-r--r-- | poezio/text_buffer.py | 35 | ||||
-rw-r--r-- | poezio/ui/render.py | 37 | ||||
-rw-r--r-- | poezio/ui/types.py | 25 | ||||
-rw-r--r-- | poezio/user.py | 3 | ||||
-rw-r--r-- | test/test_ui/test_render.py | 13 |
18 files changed, 410 insertions, 335 deletions
diff --git a/plugins/day_change.py b/plugins/day_change.py index 051b447b..5d3ab37c 100644 --- a/plugins/day_change.py +++ b/plugins/day_change.py @@ -4,11 +4,12 @@ date has changed. """ +import datetime from gettext import gettext as _ + +from poezio import timed_events, tabs from poezio.plugin import BasePlugin -import datetime -from poezio import tabs -from poezio import timed_events +from poezio.ui.types import InfoMessage class Plugin(BasePlugin): @@ -30,7 +31,7 @@ class Plugin(BasePlugin): for tab in self.core.tabs: if isinstance(tab, tabs.ChatTab): - tab.add_message(msg) + tab.add_message(InfoMessage(msg)) self.core.refresh_window() self.schedule_event() diff --git a/plugins/embed.py b/plugins/embed.py index 0c4a4a2a..aee7d44b 100644 --- a/plugins/embed.py +++ b/plugins/embed.py @@ -16,6 +16,7 @@ Usage from poezio import tabs from poezio.plugin import BasePlugin from poezio.theming import get_theme +from poezio.ui.types import Message class Plugin(BasePlugin): @@ -37,11 +38,13 @@ class Plugin(BasePlugin): if not isinstance(tab, tabs.MucTab): message['type'] = 'chat' tab.add_message( - message['body'], - nickname=tab.core.own_nick, - nick_color=get_theme().COLOR_OWN_NICK, - identifier=message['id'], - jid=tab.core.xmpp.boundjid, + Message( + message['body'], + nickname=tab.core.own_nick, + nick_color=get_theme().COLOR_OWN_NICK, + identifier=message['id'], + jid=tab.core.xmpp.boundjid, + ), typ=1, ) message.send() diff --git a/plugins/lastlog.py b/plugins/lastlog.py index 104399b4..49efa522 100644 --- a/plugins/lastlog.py +++ b/plugins/lastlog.py @@ -17,7 +17,8 @@ from datetime import datetime from poezio.plugin import BasePlugin from poezio import tabs -from poezio.text_buffer import Message, TextBuffer +from poezio.text_buffer import TextBuffer +from poezio.ui.types import InfoMessage def add_line( @@ -26,18 +27,7 @@ def add_line( datetime: Optional[datetime] = None, ) -> None: """Adds a textual entry in the TextBuffer""" - text_buffer.add_message( - text, - datetime, # Time - None, # Nickname - None, # Nick Color - False, # History - None, # User - False, # Highlight - None, # Identifier - None, # str_time - None, # Jid - ) + text_buffer.add_message(InfoMessage(text, time=datetime)) class Plugin(BasePlugin): diff --git a/plugins/otr.py b/plugins/otr.py index 2ddc332b..81e1621f 100644 --- a/plugins/otr.py +++ b/plugins/otr.py @@ -205,6 +205,7 @@ from poezio.tabs import StaticConversationTab, PrivateTab from poezio.theming import get_theme, dump_tuple from poezio.decorators import command_args_parser from poezio.core.structs import Completion +from poezio.ui.types import InfoMessage, Message POLICY_FLAGS = { 'ALLOW_V1': False, @@ -385,25 +386,30 @@ class PoezioContext(Context): log.debug('OTR conversation with %s refreshed', self.peer) if self.getCurrentTrust(): msg = OTR_REFRESH_TRUSTED % format_dict - tab.add_message(msg, typ=self.log) + tab.add_message(InfoMessage(msg), typ=self.log) else: msg = OTR_REFRESH_UNTRUSTED % format_dict - tab.add_message(msg, typ=self.log) + tab.add_message(InfoMessage(msg), typ=self.log) hl(tab) elif newstate == STATE_FINISHED or newstate == STATE_PLAINTEXT: log.debug('OTR conversation with %s finished', self.peer) if tab: - tab.add_message(OTR_END % format_dict, typ=self.log) + tab.add_message(InfoMessage(OTR_END % format_dict), typ=self.log) hl(tab) elif newstate == STATE_ENCRYPTED and tab: if self.getCurrentTrust(): - tab.add_message(OTR_START_TRUSTED % format_dict, typ=self.log) + tab.add_message(InfoMessage(OTR_START_TRUSTED % format_dict), typ=self.log) else: format_dict['our_fpr'] = self.user.getPrivkey() format_dict['remote_fpr'] = self.getCurrentKey() - tab.add_message(OTR_TUTORIAL % format_dict, typ=0) tab.add_message( - OTR_START_UNTRUSTED % format_dict, typ=self.log) + InfoMessage(OTR_TUTORIAL % format_dict), + typ=0 + ) + tab.add_message( + InfoMessage(OTR_START_UNTRUSTED % format_dict), + typ=self.log, + ) hl(tab) log.debug('Set encryption state of %s to %s', self.peer, @@ -639,7 +645,7 @@ class Plugin(BasePlugin): # Received an OTR error proto_error = err.args[0].error # pylint: disable=no-member format_dict['err'] = proto_error.decode('utf-8', errors='replace') - tab.add_message(OTR_ERROR % format_dict, typ=0) + tab.add_message(InfoMessage(OTR_ERROR % format_dict), typ=0) del msg['body'] del msg['html'] hl(tab) @@ -649,7 +655,7 @@ class Plugin(BasePlugin): # Encrypted message received, but unreadable as we do not have # an OTR session in place. text = MESSAGE_UNREADABLE % format_dict - tab.add_message(text, jid=msg['from'], typ=0) + tab.add_message(InfoMessage(text), typ=0) hl(tab) del msg['body'] del msg['html'] @@ -658,7 +664,7 @@ class Plugin(BasePlugin): except crypt.InvalidParameterError: # Malformed OTR payload and stuff text = MESSAGE_INVALID % format_dict - tab.add_message(text, jid=msg['from'], typ=0) + tab.add_message(InfoMessage(text), typ=0) hl(tab) del msg['body'] del msg['html'] @@ -669,7 +675,7 @@ class Plugin(BasePlugin): import traceback exc = traceback.format_exc() format_dict['exc'] = exc - tab.add_message(POTR_ERROR % format_dict, typ=0) + tab.add_message(InfoMessage(POTR_ERROR % format_dict), typ=0) log.error('Unspecified error in the OTR plugin', exc_info=True) return # No error, proceed with the message @@ -688,10 +694,10 @@ class Plugin(BasePlugin): abort = get_tlv(tlvs, potr.proto.SMPABORTTLV) if abort: ctx.reset_smp() - tab.add_message(SMP_ABORTED_PEER % format_dict, typ=0) + tab.add_message(InfoMessage(SMP_ABORTED_PEER % format_dict), typ=0) elif ctx.in_smp and not ctx.smpIsValid(): ctx.reset_smp() - tab.add_message(SMP_ABORTED % format_dict, typ=0) + tab.add_message(InfoMessage(SMP_ABORTED % format_dict), typ=0) elif smp1 or smp1q: # Received an SMP request (with a question or not) if smp1q: @@ -709,22 +715,22 @@ class Plugin(BasePlugin): # we did not initiate it ctx.smp_own = False format_dict['q'] = question - tab.add_message(SMP_REQUESTED % format_dict, typ=0) + tab.add_message(InfoMessage(SMP_REQUESTED % format_dict), typ=0) elif smp2: # SMP reply received if not ctx.in_smp: ctx.reset_smp() else: - tab.add_message(SMP_PROGRESS % format_dict, typ=0) + tab.add_message(InfoMessage(SMP_PROGRESS % format_dict), typ=0) elif smp3 or smp4: # Type 4 (SMP message 3) or 5 (SMP message 4) TLVs received # in both cases it is the final message of the SMP exchange if ctx.smpIsSuccess(): - tab.add_message(SMP_SUCCESS % format_dict, typ=0) + tab.add_message(InfoMessage(SMP_SUCCESS % format_dict), typ=0) if not ctx.getCurrentTrust(): - tab.add_message(SMP_RECIPROCATE % format_dict, typ=0) + tab.add_message(InfoMessage(SMP_RECIPROCATE % format_dict), typ=0) else: - tab.add_message(SMP_FAIL % format_dict, typ=0) + tab.add_message(InfoMessage(SMP_FAIL % format_dict), typ=0) ctx.reset_smp() hl(tab) self.core.refresh_window() @@ -780,12 +786,15 @@ class Plugin(BasePlugin): if decode_newlines: body = body.replace('<br/>', '\n').replace('<br>', '\n') tab.add_message( - body, - nickname=tab.nick, - jid=msg['from'], - forced_user=user, + Message( + body, + nickname=tab.nick, + jid=msg['from'], + user=user, + nick_color=nick_color + ), typ=ctx.log, - nick_color=nick_color) + ) hl(tab) self.core.refresh_window() del msg['body'] @@ -826,19 +835,22 @@ class Plugin(BasePlugin): tab.send_chat_state('inactive', always_send=True) tab.add_message( - msg['body'], - nickname=self.core.own_nick or tab.own_nick, - nick_color=get_theme().COLOR_OWN_NICK, - identifier=msg['id'], - jid=self.core.xmpp.boundjid, - typ=ctx.log) + Message( + msg['body'], + nickname=self.core.own_nick or tab.own_nick, + nick_color=get_theme().COLOR_OWN_NICK, + identifier=msg['id'], + jid=self.core.xmpp.boundjid, + ), + typ=ctx.log + ) # remove everything from the message so that it doesn’t get sent del msg['body'] del msg['replace'] del msg['html'] elif is_relevant(tab) and ctx and ctx.getPolicy('REQUIRE_ENCRYPTION'): warning_msg = MESSAGE_NOT_SENT % format_dict - tab.add_message(warning_msg, typ=0) + tab.add_message(InfoMessage(warning_msg), typ=0) del msg['body'] del msg['replace'] del msg['html'] @@ -856,7 +868,7 @@ class Plugin(BasePlugin): ('\n - /message %s' % jid) for jid in res) format_dict['help'] = help_msg warning_msg = INCOMPATIBLE_TAB % format_dict - tab.add_message(warning_msg, typ=0) + tab.add_message(InfoMessage(warning_msg), typ=0) del msg['body'] del msg['replace'] del msg['html'] @@ -900,22 +912,22 @@ class Plugin(BasePlugin): self.otr_start(tab, name, format_dict) elif action == 'ourfpr': format_dict['fpr'] = self.account.getPrivkey() - tab.add_message(OTR_OWN_FPR % format_dict, typ=0) + tab.add_message(InfoMessage(OTR_OWN_FPR % format_dict), typ=0) elif action == 'fpr': if name in self.contexts: ctx = self.contexts[name] if ctx.getCurrentKey() is not None: format_dict['fpr'] = ctx.getCurrentKey() - tab.add_message(OTR_REMOTE_FPR % format_dict, typ=0) + tab.add_message(InfoMessage(OTR_REMOTE_FPR % format_dict), typ=0) else: - tab.add_message(OTR_NO_FPR % format_dict, typ=0) + tab.add_message(InfoMessage(OTR_NO_FPR % format_dict), typ=0) elif action == 'drop': # drop the privkey (and obviously, end the current conversations before that) for context in self.contexts.values(): if context.state not in (STATE_FINISHED, STATE_PLAINTEXT): context.disconnect() self.account.drop_privkey() - tab.add_message(KEY_DROPPED % format_dict, typ=0) + tab.add_message(InfoMessage(KEY_DROPPED % format_dict), typ=0) elif action == 'trust': ctx = self.get_context(name) key = ctx.getCurrentKey() @@ -927,7 +939,7 @@ class Plugin(BasePlugin): format_dict['key'] = key ctx.setTrust(fpr, 'verified') self.account.saveTrusts() - tab.add_message(TRUST_ADDED % format_dict, typ=0) + tab.add_message(InfoMessage(TRUST_ADDED % format_dict), typ=0) elif action == 'untrust': ctx = self.get_context(name) key = ctx.getCurrentKey() @@ -939,7 +951,7 @@ class Plugin(BasePlugin): format_dict['key'] = key ctx.setTrust(fpr, '') self.account.saveTrusts() - tab.add_message(TRUST_REMOVED % format_dict, typ=0) + tab.add_message(InfoMessage(TRUST_REMOVED % format_dict), typ=0) self.core.refresh_window() def otr_start(self, tab, name, format_dict): @@ -954,7 +966,7 @@ class Plugin(BasePlugin): if otr.state != STATE_ENCRYPTED: format_dict['secs'] = secs text = OTR_NOT_ENABLED % format_dict - tab.add_message(text, typ=0) + tab.add_message(InfoMessage(text), typ=0) self.core.refresh_window() if secs > 0: @@ -962,7 +974,7 @@ class Plugin(BasePlugin): self.api.add_timed_event(event) body = self.get_context(name).sendMessage(0, b'?OTRv?').decode() self.core.xmpp.send_message(mto=name, mtype='chat', mbody=body) - tab.add_message(OTR_REQUEST % format_dict, typ=0) + tab.add_message(InfoMessage(OTR_REQUEST % format_dict), typ=0) @staticmethod def completion_otr(the_input): @@ -1012,13 +1024,13 @@ class Plugin(BasePlugin): ctx.smpInit(secret, question) else: ctx.smpInit(secret) - tab.add_message(SMP_INITIATED % format_dict, typ=0) + tab.add_message(InfoMessage(SMP_INITIATED % format_dict), typ=0) elif action == 'answer': ctx.smpGotSecret(secret) elif action == 'abort': if ctx.in_smp: ctx.smpAbort() - tab.add_message(SMP_ABORTED % format_dict, typ=0) + tab.add_message(InfoMessage(SMP_ABORTED % format_dict), typ=0) self.core.refresh_window() @staticmethod diff --git a/poezio/core/core.py b/poezio/core/core.py index 2b8252b1..8f25d551 100644 --- a/poezio/core/core.py +++ b/poezio/core/core.py @@ -53,8 +53,15 @@ from poezio.core.completions import CompletionCore from poezio.core.tabs import Tabs from poezio.core.commands import CommandCore from poezio.core.handlers import HandlerCore -from poezio.core.structs import POSSIBLE_SHOW, DEPRECATED_ERRORS, \ - ERROR_AND_STATUS_CODES, Command, Status +from poezio.core.structs import ( + Command, + Status, + DEPRECATED_ERRORS, + ERROR_AND_STATUS_CODES, + POSSIBLE_SHOW, +) + +from poezio.ui.types import Message, InfoMessage log = logging.getLogger(__name__) @@ -1317,7 +1324,7 @@ class Core: """ tab = self.tabs.by_name_and_class(jid, tabs.ConversationTab) if tab is not None: - tab.add_message(msg, typ=2) + tab.add_message(InfoMessage(msg), typ=2) if self.tabs.current_tab is tab: self.refresh_window() @@ -1349,9 +1356,11 @@ class Core: colors = get_theme().INFO_COLORS color = colors.get(typ.lower(), colors.get('default', None)) nb_lines = self.information_buffer.add_message( - txt=msg, - nickname=typ, - nick_color=color + Message( + txt=msg, + nickname=typ, + nick_color=color + ) ) popup_on = config.get('information_buffer_popup_on').split() if isinstance(self.tabs.current_tab, tabs.RosterInfoTab): @@ -1582,17 +1591,6 @@ class Core: self.tab_win.resize(1, tabs.Tab.width, tabs.Tab.height - 2, 0) self.left_tab_win = None - def add_message_to_text_buffer(self, buff, txt, nickname=None): - """ - Add the message to the room if possible, else, add it to the Info window - (in the Info tab of the info window in the RosterTab) - """ - if not buff: - self.information('Trying to add a message in no room: %s' % txt, - 'Error') - return - buff.add_message(txt, nickname=nickname) - def full_screen_redraw(self): """ Completely erase and redraw the screen @@ -2064,15 +2062,18 @@ class Core: return error_message = self.get_error_message(error) tab.add_message( - error_message, - highlight=True, - nickname='Error', - nick_color=get_theme().COLOR_ERROR_MSG, - typ=2) + Message( + error_message, + highlight=True, + nickname='Error', + nick_color=get_theme().COLOR_ERROR_MSG, + ), + typ=2, + ) code = error['error']['code'] if code == '401': msg = 'To provide a password in order to join the room, type "/join / password" (replace "password" by the real password)' - tab.add_message(msg, typ=2) + tab.add_message(InfoMessage(msg), typ=2) if code == '409': if config.get('alternative_nickname') != '': if not tab.joined: @@ -2081,8 +2082,12 @@ class Core: else: if not tab.joined: tab.add_message( - 'You can join the room with an other nick, by typing "/join /other_nick"', - typ=2) + InfoMessage( + 'You can join the room with another nick, ' + 'by typing "/join /other_nick"' + ), + typ=2, + ) self.refresh_window() diff --git a/poezio/core/handlers.py b/poezio/core/handlers.py index 2232f723..cf04d582 100644 --- a/poezio/core/handlers.py +++ b/poezio/core/handlers.py @@ -39,6 +39,7 @@ from poezio.logger import logger from poezio.roster import roster from poezio.text_buffer import CorrectionError, AckError from poezio.theming import dump_tuple, get_theme +from poezio.ui.types import XMLLog, Message as PMessage, BaseMessage, InfoMessage from poezio.core.commands import dumb_callback @@ -326,7 +327,7 @@ class HandlerCore: error = '\x19%s}%s\x19o' % (dump_tuple(get_theme().COLOR_CHAR_NACK), error_msg) if not tab.nack_message('\n' + error, message['id'], message['to']): - tab.add_message(error, typ=0) + tab.add_message(InfoMessage(error), typ=0) self.core.refresh_window() def on_normal_message(self, message): @@ -421,14 +422,17 @@ class HandlerCore: if not try_modify(): conversation.add_message( - body, - date, - nickname=remote_nick, - nick_color=color, - history=delayed, - identifier=message['id'], - jid=jid, - typ=1) + PMessage( + txt=body, + time=date, + nickname=remote_nick, + nick_color=color, + history=delayed, + identifier=message['id'], + jid=jid, + ), + typ=1, + ) if not own and 'private' in config.get('beep_on').split(): if not config.get_by_tabname('disable_beep', conv_jid.bare): @@ -769,12 +773,15 @@ class HandlerCore: except CorrectionError: log.debug('Unable to correct a message', exc_info=True) if not replaced and tab.add_message( - body, - date, - nick_from, - history=delayed, - identifier=message['id'], - jid=message['from'], + PMessage( + txt=body, + time=date, + nickname=nick_from, + history=delayed, + identifier=message['id'], + jid=message['from'], + user=user, + ), typ=1): self.core.events.trigger('highlight', message, tab) @@ -862,14 +869,16 @@ class HandlerCore: log.debug('Unable to correct a message', exc_info=True) if not replaced: tab.add_message( - body, - time=None, - nickname=sender_nick, - nick_color=get_theme().COLOR_OWN_NICK if sent else None, - forced_user=user, - identifier=message['id'], - jid=message['from'], - typ=1) + PMessage( + txt=body, + nickname=sender_nick, + nick_color=get_theme().COLOR_OWN_NICK if sent else None, + user=user, + identifier=message['id'], + jid=message['from'], + ), + typ=1, + ) if sent: tab.set_last_sent_message(message, correct=replaced) else: @@ -1361,36 +1370,52 @@ class HandlerCore: if show_unavailable or hide_unavailable or non_priv or logging_off\ or non_anon or semi_anon or full_anon: tab.add_message( - '\x19%(info_col)s}Info: A configuration change not privacy-related occurred.' % info_col, + InfoMessage( + 'Info: A configuration change not privacy-related occurred.' + ), typ=2) modif = True if show_unavailable: tab.add_message( - '\x19%(info_col)s}Info: The unavailable members are now shown.' % info_col, + InfoMessage( + 'Info: The unavailable members are now shown.' + ), typ=2) elif hide_unavailable: tab.add_message( - '\x19%(info_col)s}Info: The unavailable members are now hidden.' % info_col, + InfoMessage( + 'Info: The unavailable members are now hidden.', + ), typ=2) if non_anon: tab.add_message( - '\x191}Warning:\x19%(info_col)s} The room is now not anonymous. (public JID)' % info_col, + InfoMessage( + '\x191}Warning:\x19%(info_col)s} The room is now not anonymous. (public JID)' % info_col + ), typ=2) elif semi_anon: tab.add_message( - '\x19%(info_col)s}Info: The room is now semi-anonymous. (moderators-only JID)' % info_col, + InfoMessage( + 'Info: The room is now semi-anonymous. (moderators-only JID)', + ), typ=2) elif full_anon: tab.add_message( - '\x19%(info_col)s}Info: The room is now fully anonymous.' % info_col, + InfoMessage( + 'Info: The room is now fully anonymous.', + ), typ=2) if logging_on: tab.add_message( - '\x191}Warning: \x19%(info_col)s}This room is publicly logged' % info_col, + InfoMessage( + '\x191}Warning: \x19%(info_col)s}This room is publicly logged' % info_col + ), typ=2) elif logging_off: tab.add_message( - '\x19%(info_col)s}Info: This room is not logged anymore.' % info_col, + InfoMessage( + 'Info: This room is not logged anymore.', + ), typ=2) if modif: self.core.refresh_window() @@ -1434,15 +1459,17 @@ class HandlerCore: if nick_from: tab.add_message( - "%(user)s set the subject to: \x19%(text_col)s}%(subject)s" - % fmt, - str_time=time, + InfoMessage( + "%(user)s set the subject to: \x19%(text_col)s}%(subject)s" % fmt, + time=time, + ), typ=2) else: tab.add_message( - "\x19%(info_col)s}The subject is: \x19%(text_col)s}%(subject)s" - % fmt, - str_time=time, + InfoMessage( + "The subject is: \x19%(text_col)s}%(subject)s" % fmt, + time=time, + ), typ=2) tab.topic = subject tab.topic_from = nick_from @@ -1506,18 +1533,15 @@ class HandlerCore: xhtml_text, force=True).rstrip('\x19o').strip() else: poezio_colored = str(stanza) - char = get_theme().CHAR_XML_OUT - self.core.add_message_to_text_buffer( - self.core.xml_buffer, - poezio_colored, - nickname=char) + self.core.xml_buffer.add_message( + XMLLog(txt=poezio_colored, incoming=False), + ) try: if self.core.xml_tab.match_stanza( ElementBase(ET.fromstring(stanza))): - self.core.add_message_to_text_buffer( - self.core.xml_tab.filtered_buffer, - poezio_colored, - nickname=char) + self.core.xml_tab.filtered_buffer.add_message( + XMLLog(txt=poezio_colored, incoming=False), + ) except: # Most of the time what gets logged is whitespace pings. Skip. # And also skip tab updates. @@ -1540,17 +1564,14 @@ class HandlerCore: xhtml_text, force=True).rstrip('\x19o').strip() else: poezio_colored = str(stanza) - char = get_theme().CHAR_XML_IN - self.core.add_message_to_text_buffer( - self.core.xml_buffer, - poezio_colored, - nickname=char) + self.core.xml_buffer.add_message( + XMLLog(txt=poezio_colored, incoming=True), + ) try: if self.core.xml_tab.match_stanza(stanza): - self.core.add_message_to_text_buffer( - self.core.xml_tab.filtered_buffer, - poezio_colored, - nickname=char) + self.core.xml_tab.filtered_buffer.add_message( + XMLLog(txt=poezio_colored, incoming=True), + ) except: log.debug('', exc_info=True) if isinstance(self.core.tabs.current_tab, tabs.XMLTab): diff --git a/poezio/mam.py b/poezio/mam.py index 6e13c074..50dad4a3 100644 --- a/poezio/mam.py +++ b/poezio/mam.py @@ -18,6 +18,7 @@ from poezio import tabs from poezio import xhtml, colors from poezio.config import config from poezio.text_buffer import TextBuffer +from poezio.ui.types import Message class DiscoInfoException(Exception): pass @@ -63,16 +64,15 @@ def add_line( nick = nick.split('/')[0] color = get_theme().COLOR_OWN_NICK text_buffer.add_message( - txt=text, - time=time, - nickname=nick, - nick_color=color, - history=True, - user=None, - highlight=False, - top=top, - identifier=None, - jid=None, + Message( + txt=text, + time=time, + nickname=nick, + nick_color=color, + history=True, + user=None, + top=top, + ) ) diff --git a/poezio/tabs/basetabs.py b/poezio/tabs/basetabs.py index eadc9a3f..fbb0c4cf 100644 --- a/poezio/tabs/basetabs.py +++ b/poezio/tabs/basetabs.py @@ -47,6 +47,7 @@ from poezio.text_buffer import TextBuffer from poezio.theming import get_theme, dump_tuple from poezio.ui.funcs import truncate_nick from poezio.ui.consts import LONG_FORMAT_LENGTH +from poezio.ui.types import BaseMessage, InfoMessage from slixmpp import JID, InvalidJID, Message @@ -572,40 +573,19 @@ class ChatTab(Tab): def general_jid(self) -> JID: raise NotImplementedError - def log_message(self, - txt: str, - nickname: str, - time: Optional[datetime] = None, - typ=1): + def log_message(self, message: BaseMessage, typ=1): """ Log the messages in the archives. """ name = self.jid.bare - if not logger.log_message(name, nickname, txt, date=time, typ=typ): + if not isinstance(message, Message): + return + if not logger.log_message(name, message.nickname, message.txt, date=message.time, typ=typ): self.core.information('Unable to write in the log file', 'Error') - def add_message(self, - txt, - time=None, - nickname=None, - forced_user=None, - nick_color=None, - identifier=None, - jid=None, - history=None, - typ=1, - highlight=False): - self.log_message(txt, nickname, time=time, typ=typ) - self._text_buffer.add_message( - txt, - time=time, - nickname=nickname, - highlight=highlight, - nick_color=nick_color, - history=history, - user=forced_user, - identifier=identifier, - jid=jid) + def add_message(self, message: BaseMessage, typ=1): + self.log_message(message, typ=typ) + self._text_buffer.add_message(message) def modify_message(self, txt, @@ -614,10 +594,10 @@ class ChatTab(Tab): user=None, jid=None, nickname=None): - self.log_message(txt, nickname, typ=1) message = self._text_buffer.modify_message( - txt, old_id, new_id, time=time, user=user, jid=jid) + txt, old_id, new_id, user=user, jid=jid) if message: + self.log_message(message, typ=1) self.text_win.modify_message(message.identifier, message) self.core.refresh_window() return True @@ -1010,7 +990,10 @@ class OneToOneTab(ChatTab): msg += 'status: %s, ' % status.message if status.show in SHOW_NAME: msg += 'show: %s, ' % SHOW_NAME[status.show] - self.add_message(msg[:-2], typ=2) + self.add_message( + InfoMessage(txt=msg[:-2]), + typ=2, + ) def ack_message(self, msg_id: str, msg_jid: JID): """ @@ -1042,11 +1025,14 @@ class OneToOneTab(ChatTab): message.send() body = xhtml.xhtml_to_poezio_colors(xhtml_data, force=True) self._text_buffer.add_message( - body, - nickname=self.core.own_nick, - nick_color=get_theme().COLOR_OWN_NICK, - identifier=message['id'], - jid=self.core.xmpp.boundjid) + Message( + body, + nickname=self.core.own_nick, + nick_color=get_theme().COLOR_OWN_NICK, + identifier=message['id'], + jid=self.core.xmpp.boundjid, + ) + ) self.refresh() def check_features(self): diff --git a/poezio/tabs/conversationtab.py b/poezio/tabs/conversationtab.py index 410c5eda..70005f0f 100644 --- a/poezio/tabs/conversationtab.py +++ b/poezio/tabs/conversationtab.py @@ -28,6 +28,7 @@ from poezio.roster import roster from poezio.text_buffer import CorrectionError from poezio.theming import get_theme, dump_tuple from poezio.decorators import command_args_parser +from poezio.ui.types import InfoMessage log = logging.getLogger(__name__) @@ -179,7 +180,7 @@ class ConversationTab(OneToOneTab): (' and their last status was %s' % status) if status else '', ) - self.add_message(msg) + self.add_message(InfoMessage(msg), typ=0) self.core.refresh_window() self.core.xmpp.plugin['xep_0012'].get_last_activity( @@ -200,17 +201,21 @@ class ConversationTab(OneToOneTab): if resource: status = ( 'Status: %s' % resource.status) if resource.status else '' - self._text_buffer.add_message( - "\x19%(info_col)s}Show: %(show)s, %(status)s\x19o" % { - 'show': resource.presence or 'available', - 'status': status, - 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT) - }) + self.add_message( + InfoMessage( + "Show: %(show)s, %(status)s" % { + 'show': resource.presence or 'available', + 'status': status, + } + ), + typ=0, + ) return True else: - self._text_buffer.add_message( - "\x19%(info_col)s}No information available\x19o" % - {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}) + self.add_message( + InfoMessage("No information available"), + typ=0, + ) return True @command_args_parser.quoted(0, 1) diff --git a/poezio/tabs/muctab.py b/poezio/tabs/muctab.py index 73359610..ff8f6c07 100644 --- a/poezio/tabs/muctab.py +++ b/poezio/tabs/muctab.py @@ -40,6 +40,7 @@ from poezio.roster import roster from poezio.theming import get_theme, dump_tuple from poezio.user import User from poezio.core.structs import Completion, Status +from poezio.ui.types import BaseMessage, Message, InfoMessage, StatusMessage log = logging.getLogger(__name__) @@ -197,8 +198,7 @@ class MucTab(ChatTab): 'color_spec': spec_col, 'nick': self.own_nick, } - - self.add_message(msg, typ=2) + self.add_message(InfoMessage(msg), typ=2) self.disconnect() muc.leave_groupchat(self.core.xmpp, self.jid.bare, self.own_nick, message) @@ -301,7 +301,7 @@ class MucTab(ChatTab): 'role': user.role or 'None', 'status': '\n%s' % user.status if user.status else '' } - self.add_message(info, typ=0) + self.add_message(InfoMessage(info), typ=0) return True def change_topic(self, topic: str): @@ -327,9 +327,13 @@ class MucTab(ChatTab): else: user_string = '' - self._text_buffer.add_message( - "\x19%s}The subject of the room is: \x19%s}%s %s" % - (info_text, norm_text, self.topic, user_string)) + self.add_message( + InfoMessage( + "The subject of the room is: \x19%s}%s %s" % + (norm_text, self.topic, user_string), + ), + typ=0, + ) @refresh_wrapper.always def recolor(self, random_colors=False): @@ -558,28 +562,32 @@ class MucTab(ChatTab): 'nick_col': color, 'info_col': info_col, } - self.add_message(enable_message, typ=2) + self.add_message(InfoMessage(enable_message), typ=2) self.core.enable_private_tabs(self.jid.bare, enable_message) if '201' in status_codes: self.add_message( - '\x19%(info_col)s}Info: The room ' - 'has been created' % {'info_col': info_col}, - typ=0) + InfoMessage('Info: The room has been created'), + typ=0 + ) if '170' in status_codes: self.add_message( - '\x19%(warn_col)s}Warning:\x19%(info_col)s}' - ' This room is publicly logged' % { - 'info_col': info_col, - 'warn_col': warn_col - }, + InfoMessage( + '\x19%(warn_col)s}Warning:\x19%(info_col)s}' + ' This room is publicly logged' % { + 'info_col': info_col, + 'warn_col': warn_col + }, + ), typ=0) if '100' in status_codes: self.add_message( - '\x19%(warn_col)s}Warning:\x19%(info_col)s}' - ' This room is not anonymous.' % { - 'info_col': info_col, - 'warn_col': warn_col - }, + InfoMessage( + '\x19%(warn_col)s}Warning:\x19%(info_col)s}' + ' This room is not anonymous.' % { + 'info_col': info_col, + 'warn_col': warn_col + }, + ), typ=0) def handle_presence_joined(self, presence, status_codes): @@ -635,18 +643,20 @@ class MucTab(ChatTab): def on_non_member_kicked(self): """We have been kicked because the MUC is members-only""" self.add_message( - '\x19%(info_col)s}You have been kicked because you ' - 'are not a member and the room is now members-only.' % - {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, + InfoMessage( + 'You have been kicked because you ' + 'are not a member and the room is now members-only.' + ), typ=2) self.disconnect() def on_muc_shutdown(self): """We have been kicked because the MUC service is shutting down""" self.add_message( - '\x19%(info_col)s}You have been kicked because the' - ' MUC service is shutting down.' % - {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, + InfoMessage( + 'You have been kicked because the' + ' MUC service is shutting down.' + ), typ=2) self.disconnect() @@ -693,7 +703,7 @@ class MucTab(ChatTab): 'jid_color': dump_tuple(theme.COLOR_MUC_JID), 'color_spec': spec_col, } - self.add_message(msg, typ=2) + self.add_message(InfoMessage(msg), typ=2) self.core.on_user_rejoined_private_conversation(self.jid.bare, from_nick) def on_user_nick_change(self, presence, user, from_nick, from_room): @@ -723,14 +733,16 @@ class MucTab(ChatTab): old_color = color = 3 info_col = dump_tuple(get_theme().COLOR_INFORMATION_TEXT) self.add_message( - '\x19%(old_color)s}%(old)s\x19%(info_col)s} is' - ' now known as \x19%(color)s}%(new)s' % { - 'old': from_nick, - 'new': new_nick, - 'color': color, - 'old_color': old_color, - 'info_col': info_col - }, + InfoMessage( + '\x19%(old_color)s}%(old)s\x19%(info_col)s} is' + ' now known as \x19%(color)s}%(new)s' % { + 'old': from_nick, + 'new': new_nick, + 'color': color, + 'old_color': old_color, + 'info_col': info_col + }, + ), typ=2) # rename the private tabs if needed self.core.rename_private_tabs(self.jid.bare, from_nick, user) @@ -814,7 +826,7 @@ class MucTab(ChatTab): 'reason': reason.text, 'info_col': info_col } - self.add_message(kick_msg, typ=2) + self.add_message(InfoMessage(kick_msg), typ=2) def on_user_kicked(self, presence, user, from_nick): """ @@ -892,7 +904,7 @@ class MucTab(ChatTab): 'reason': reason.text, 'info_col': info_col } - self.add_message(kick_msg, typ=2) + self.add_message(InfoMessage(kick_msg), typ=2) def on_user_leave_groupchat(self, user: User, @@ -957,7 +969,7 @@ class MucTab(ChatTab): } if status: leave_msg += ' (\x19o%s\x19%s})' % (status, info_col) - self.add_message(leave_msg, typ=2) + self.add_message(InfoMessage(leave_msg), typ=2) self.core.on_user_left_private_conversation(from_room, user, status) def on_user_change_status(self, user, from_nick, from_room, affiliation, @@ -1016,7 +1028,7 @@ class MucTab(ChatTab): or show != user.show or status != user.status)) or ( affiliation != user.affiliation or role != user.role): # display the message in the room - self._text_buffer.add_message(msg) + self.add_message(InfoMessage(msg)) self.core.on_user_changed_status_in_private( '%s/%s' % (from_room, from_nick), Status(show, status)) self.users.remove(user) @@ -1118,7 +1130,6 @@ class MucTab(ChatTab): nickname=None, user=None, jid=None): - self.log_message(txt, nickname, time=time, typ=1) highlight = self.do_highlight(txt, time, nickname, corrected=True) message = self._text_buffer.modify_message( txt, @@ -1129,6 +1140,7 @@ class MucTab(ChatTab): user=user, jid=jid) if message: + self.log_message(message, typ=1) self.text_win.modify_message(message.identifier, message) return highlight return False @@ -1192,9 +1204,11 @@ class MucTab(ChatTab): def on_self_ping_failed(self, iq): if not self.lagged: self.lagged = True - info_text = dump_tuple(get_theme().COLOR_INFORMATION_TEXT) self._text_buffer.add_message( - "\x19%s}MUC service not responding." % info_text) + InfoMessage( + "MUC service not responding." + ), + ) self._state = 'disconnected' self.core.refresh_window() self.enable_self_ping_event() @@ -1202,9 +1216,9 @@ class MucTab(ChatTab): def reset_lag(self): if self.lagged: self.lagged = False - info_text = dump_tuple(get_theme().COLOR_INFORMATION_TEXT) - self._text_buffer.add_message( - "\x19%s}MUC service is responding again." % info_text) + self.add_message( + InfoMessage("MUC service is responding again.") + ) if self != self.core.tabs.current_tab: self._state = 'joined' else: @@ -1551,7 +1565,7 @@ class MucTab(ChatTab): buff.append('\n') message = ' '.join(buff) - self._text_buffer.add_message(message) + self.add_message(InfoMessage(message), typ=0) self.text_win.refresh() self.input.refresh() diff --git a/poezio/tabs/privatetab.py b/poezio/tabs/privatetab.py index 7206240f..b43294a1 100644 --- a/poezio/tabs/privatetab.py +++ b/poezio/tabs/privatetab.py @@ -27,6 +27,7 @@ from poezio.decorators import refresh_wrapper from poezio.logger import logger from poezio.theming import get_theme, dump_tuple from poezio.decorators import command_args_parser +from poezio.ui.types import BaseMessage, Message, InfoMessage log = logging.getLogger(__name__) @@ -106,12 +107,14 @@ class PrivateTab(OneToOneTab): def remove_information_element(plugin_name): del PrivateTab.additional_information[plugin_name] - def log_message(self, txt, nickname, time=None, typ=1): + def log_message(self, msg: BaseMessage, typ=1): """ Log the messages in the archives. """ + if not isinstance(msg, Message): + return if not logger.log_message( - self.jid.full, nickname, txt, date=time, typ=typ): + self.jid.full, msg.nickname, msg.txt, date=msg.time, typ=typ): self.core.information('Unable to write in the log file', 'Error') def on_close(self): @@ -163,8 +166,6 @@ class PrivateTab(OneToOneTab): self.core.events.trigger('private_say', msg, self) if not msg['body']: 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'] else: @@ -311,13 +312,15 @@ class PrivateTab(OneToOneTab): display a message. """ self.add_message( - '\x19%(nick_col)s}%(old)s\x19%(info_col)s} is now ' - 'known as \x19%(nick_col)s}%(new)s' % { - 'old': old_nick, - 'new': user.nick, - 'nick_col': dump_tuple(user.color), - 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT) - }, + InfoMessage( + '\x19%(nick_col)s}%(old)s\x19%(info_col)s} is now ' + 'known as \x19%(nick_col)s}%(new)s' % { + 'old': old_nick, + 'new': user.nick, + 'nick_col': dump_tuple(user.color), + 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT) + }, + ), typ=2) new_jid = self.jid.bare + '/' + user.nick self.name = new_jid @@ -338,27 +341,31 @@ class PrivateTab(OneToOneTab): if not status_message: self.add_message( - '\x19%(quit_col)s}%(spec)s \x19%(nick_col)s}' - '%(nick)s\x19%(info_col)s} has left the room' % { - 'nick': user.nick, - 'spec': theme.CHAR_QUIT, - 'nick_col': color, - 'quit_col': dump_tuple(theme.COLOR_QUIT_CHAR), - 'info_col': dump_tuple(theme.COLOR_INFORMATION_TEXT) - }, + InfoMessage( + '\x19%(quit_col)s}%(spec)s \x19%(nick_col)s}' + '%(nick)s\x19%(info_col)s} has left the room' % { + 'nick': user.nick, + 'spec': theme.CHAR_QUIT, + 'nick_col': color, + 'quit_col': dump_tuple(theme.COLOR_QUIT_CHAR), + 'info_col': dump_tuple(theme.COLOR_INFORMATION_TEXT) + }, + ), typ=2) else: self.add_message( - '\x19%(quit_col)s}%(spec)s \x19%(nick_col)s}' - '%(nick)s\x19%(info_col)s} has left the room' - ' (%(status)s)' % { - 'status': status_message, - 'nick': user.nick, - 'spec': theme.CHAR_QUIT, - 'nick_col': color, - 'quit_col': dump_tuple(theme.COLOR_QUIT_CHAR), - 'info_col': dump_tuple(theme.COLOR_INFORMATION_TEXT) - }, + InfoMessage( + '\x19%(quit_col)s}%(spec)s \x19%(nick_col)s}' + '%(nick)s\x19%(info_col)s} has left the room' + ' (%(status)s)' % { + 'status': status_message, + 'nick': user.nick, + 'spec': theme.CHAR_QUIT, + 'nick_col': color, + 'quit_col': dump_tuple(theme.COLOR_QUIT_CHAR), + 'info_col': dump_tuple(theme.COLOR_INFORMATION_TEXT) + }, + ), typ=2) return self.core.tabs.current_tab is self @@ -378,26 +385,28 @@ class PrivateTab(OneToOneTab): if user: color = dump_tuple(user.color) self.add_message( - '\x19%(join_col)s}%(spec)s \x19%(color)s}%(nick)s\x19' - '%(info_col)s} joined the room' % { - 'nick': nick, - 'color': color, - 'spec': theme.CHAR_JOIN, - 'join_col': dump_tuple(theme.COLOR_JOIN_CHAR), - 'info_col': dump_tuple(theme.COLOR_INFORMATION_TEXT) - }, + InfoMessage( + '\x19%(join_col)s}%(spec)s \x19%(color)s}%(nick)s\x19' + '%(info_col)s} joined the room' % { + 'nick': nick, + 'color': color, + 'spec': theme.CHAR_JOIN, + 'join_col': dump_tuple(theme.COLOR_JOIN_CHAR), + 'info_col': dump_tuple(theme.COLOR_INFORMATION_TEXT) + }, + ), typ=2) return self.core.tabs.current_tab is self def activate(self, reason=None): self.on = True if reason: - self.add_message(txt=reason, typ=2) + self.add_message(InfoMessage(reason), typ=2) def deactivate(self, reason=None): self.on = False if reason: - self.add_message(txt=reason, typ=2) + self.add_message(InfoMessage(reason), typ=2) def matching_names(self): return [(3, self.jid.resource), (4, self.name)] @@ -407,9 +416,12 @@ class PrivateTab(OneToOneTab): error = '\x19%s}%s\x19o' % (dump_tuple(theme.COLOR_CHAR_NACK), error_message) self.add_message( - error, - highlight=True, - nickname='Error', - nick_color=theme.COLOR_ERROR_MSG, - typ=2) + Message( + error, + highlight=True, + nickname='Error', + nick_color=theme.COLOR_ERROR_MSG, + ), + typ=2, + ) self.core.refresh_window() diff --git a/poezio/tabs/rostertab.py b/poezio/tabs/rostertab.py index 6f43cca1..50b8c0d5 100644 --- a/poezio/tabs/rostertab.py +++ b/poezio/tabs/rostertab.py @@ -27,6 +27,7 @@ from poezio.theming import get_theme, dump_tuple from poezio.decorators import command_args_parser from poezio.core.structs import Command, Completion from poezio.tabs import Tab +from poezio.ui.types import InfoMessage log = logging.getLogger(__name__) @@ -402,11 +403,11 @@ class RosterInfoTab(Tab): if not tab: log.debug('Received message from nonexistent tab: %s', message['from']) - message = '\x19%(info_col)s}Cannot send message to %(jid)s: contact blocked' % { + message = 'Cannot send message to %(jid)s: contact blocked' % { 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT), 'jid': message['from'], } - tab.add_message(message) + tab.add_message(InfoMessage(message), typ=0) @command_args_parser.ignored def command_list_blocks(self): diff --git a/poezio/tabs/xmltab.py b/poezio/tabs/xmltab.py index c73eddd0..85828602 100644 --- a/poezio/tabs/xmltab.py +++ b/poezio/tabs/xmltab.py @@ -65,7 +65,7 @@ class XMLTab(Tab): self.filtered_buffer = text_buffer.TextBuffer() self.info_header = windows.XMLInfoWin() - self.text_win = windows.XMLTextWin() + self.text_win = windows.TextWin() self.core_buffer.add_window(self.text_win) self.default_help_message = windows.HelpText("/ to enter a command") diff --git a/poezio/text_buffer.py b/poezio/text_buffer.py index c03f84f5..3c8d78ba 100644 --- a/poezio/text_buffer.py +++ b/poezio/text_buffer.py @@ -14,7 +14,7 @@ log = logging.getLogger(__name__) from typing import Dict, Union, Optional, List, Tuple from datetime import datetime from poezio.config import config -from poezio.ui.types import Message +from poezio.ui.types import Message, BaseMessage @@ -38,7 +38,7 @@ class TextBuffer: messages_nb_limit = config.get('max_messages_in_memory') self._messages_nb_limit = messages_nb_limit # type: int # Message objects - self.messages = [] # type: List[Message] + self.messages = [] # type: List[BaseMessage] # COMPAT: Correction id -> Original message id. self.correction_ids = {} # type: Dict[str, str] # we keep track of one or more windows @@ -53,33 +53,10 @@ class TextBuffer: def last_message(self) -> Optional[Message]: return self.messages[-1] if self.messages else None - def add_message(self, - txt: str, - time: Optional[datetime] = None, - nickname: Optional[str] = None, - nick_color: Optional[Tuple] = None, - history: bool = False, - user: Optional[str] = None, - highlight: bool = False, - top: Optional[bool] = False, - identifier: Optional[str] = None, - jid: Optional[str] = None, - ack: int = 0) -> int: + def add_message(self, msg: BaseMessage): """ Create a message and add it to the text buffer """ - msg = Message( - txt, - time=time, - nickname=nickname, - nick_color=nick_color, - history=history, - user=user, - identifier=identifier, - top=top, - highlight=highlight, - jid=jid, - ack=ack) self.messages.append(msg) while len(self.messages) > self._messages_nb_limit: @@ -92,13 +69,11 @@ class TextBuffer: # build the lines from the new message nb = window.build_new_message( msg, - history=history, - highlight=highlight, timestamp=show_timestamps, - top=top, nick_size=nick_size) if ret_val == 0: ret_val = nb + top = isinstance(msg, Message) and msg.top if window.pos != 0 and top is False: window.scroll_up(nb) @@ -156,7 +131,7 @@ class TextBuffer: highlight: bool = False, time: Optional[datetime] = None, user: Optional[str] = None, - jid: Optional[str] = None): + jid: Optional[str] = None) -> Message: """ Correct a message in a text buffer. diff --git a/poezio/ui/render.py b/poezio/ui/render.py index b695e8f3..3f94ecd8 100644 --- a/poezio/ui/render.py +++ b/poezio/ui/render.py @@ -3,10 +3,14 @@ import curses from datetime import datetime from functools import singledispatch -from typing import List, Optional, Tuple +from typing import List, Tuple from math import ceil, log10 from poezio import poopt +from poezio.theming import ( + get_theme, +) +from poezio.windows import Win from poezio.ui.consts import ( FORMAT_CHAR, LONG_FORMAT, @@ -16,12 +20,10 @@ from poezio.ui.funcs import ( truncate_nick, parse_attrs, ) -from poezio.theming import ( - get_theme, -) from poezio.ui.types import ( BaseMessage, Message, + StatusMessage, XMLLog, ) @@ -86,6 +88,17 @@ def build_message(msg: Message, width: int, timestamp: bool, nick_size: int = 10 return [] offset = msg.compute_offset(timestamp, nick_size) lines = poopt.cut_text(txt, width - offset - 1) + generated_lines = generate_lines(lines, msg, default_color='') + if msg.top: + generated_lines.reverse() + return generated_lines + + +@build_lines.register(StatusMessage) +def build_status(msg: StatusMessage, width: int, timestamp: bool, nick_size: int = 10) -> List[Line]: + msg.rebuild() + offset = msg.compute_offset(timestamp, nick_size) + lines = poopt.cut_text(msg.txt, width - offset - 1) return generate_lines(lines, msg, default_color='') @@ -97,7 +110,7 @@ def build_xmllog(msg: XMLLog, width: int, timestamp: bool, nick_size: int = 10) @singledispatch -def write_pre(msg: BaseMessage, win, with_timestamps: bool, nick_size: int) -> int: +def write_pre(msg: BaseMessage, win: Win, with_timestamps: bool, nick_size: int) -> int: """Write the part before text (only the timestamp)""" if with_timestamps: return PreMessageHelpers.write_time(win, False, msg.time) @@ -105,7 +118,7 @@ def write_pre(msg: BaseMessage, win, with_timestamps: bool, nick_size: int) -> i @write_pre.register(Message) -def write_pre_message(msg: Message, win, with_timestamps: bool, nick_size: int) -> int: +def write_pre_message(msg: Message, win: Win, with_timestamps: bool, nick_size: int) -> int: """Write the part before the body: - timestamp (short or long) - ack/nack @@ -149,7 +162,7 @@ def write_pre_message(msg: Message, win, with_timestamps: bool, nick_size: int) @write_pre.register(XMLLog) -def write_pre_xmllog(msg: XMLLog, win, with_timestamps: bool, nick_size: int) -> int: +def write_pre_xmllog(msg: XMLLog, win: Win, with_timestamps: bool, nick_size: int) -> int: """Write the part before the stanza (timestamp + IN/OUT)""" offset = 0 if with_timestamps: @@ -170,7 +183,7 @@ def write_pre_xmllog(msg: XMLLog, win, with_timestamps: bool, nick_size: int) -> class PreMessageHelpers: @staticmethod - def write_revisions(buffer, msg: Message) -> int: + def write_revisions(buffer: Win, msg: Message) -> int: if msg.revisions: color = get_theme().COLOR_REVISIONS_MESSAGE with buffer.colored_text(color=color): @@ -179,7 +192,7 @@ class PreMessageHelpers: return 0 @staticmethod - def write_ack(buffer) -> int: + def write_ack(buffer: Win) -> int: theme = get_theme() color = theme.COLOR_CHAR_ACK with buffer.colored_text(color=color): @@ -188,7 +201,7 @@ class PreMessageHelpers: return poopt.wcswidth(theme.CHAR_ACK_RECEIVED) + 1 @staticmethod - def write_nack(buffer) -> int: + def write_nack(buffer: Win) -> int: theme = get_theme() color = theme.COLOR_CHAR_NACK with buffer.colored_text(color=color): @@ -197,7 +210,7 @@ class PreMessageHelpers: return poopt.wcswidth(theme.CHAR_NACK) + 1 @staticmethod - def write_nickname(buffer, nickname: str, color, highlight=False) -> None: + def write_nickname(buffer: Win, nickname: str, color, highlight=False) -> None: """ Write the nickname, using the user's color and return the number of written characters @@ -215,7 +228,7 @@ class PreMessageHelpers: buffer.addstr(nickname) @staticmethod - def write_time(buffer, history: bool, time: datetime) -> int: + def write_time(buffer: Win, history: bool, time: datetime) -> int: """ Write the date on the yth line of the window """ diff --git a/poezio/ui/types.py b/poezio/ui/types.py index 6c744ac3..9ecdc185 100644 --- a/poezio/ui/types.py +++ b/poezio/ui/types.py @@ -27,6 +27,12 @@ class BaseMessage: return SHORT_FORMAT_LENGTH + 1 +class InfoMessage(BaseMessage): + def __init__(self, txt: str, identifier: str = '', time: Optional[datetime] = None): + txt = ('\x19%s}' % dump_tuple(get_theme().COLOR_INFORMATION_TEXT)) + txt + super().__init__(txt=txt, identifier=identifier, time=time) + + class XMLLog(BaseMessage): """XML Log message""" __slots__ = ('txt', 'time', 'identifier', 'incoming') @@ -59,6 +65,25 @@ class XMLLog(BaseMessage): return offset +class StatusMessage(BaseMessage): + __slots__ = ('txt', 'time', 'identifier', 'format_string', 'format_args') + + def __init__(self, format_string: str, format_args: dict): + BaseMessage.__init__( + self, + txt='', + ) + self.format_string = format_string + self.format_args = format_args + self.rebuild() + + def rebuild(self): + real_args = {} + for key, func in self.format_args.items(): + real_args[key] = func() + self.txt = self.format_string.format(**real_args) + + class Message(BaseMessage): __slots__ = ('txt', 'nick_color', 'time', 'nickname', 'user', 'history', 'identifier', 'top', 'highlight', 'me', 'old_message', 'revisions', diff --git a/poezio/user.py b/poezio/user.py index 146a70da..bead0a93 100644 --- a/poezio/user.py +++ b/poezio/user.py @@ -97,7 +97,8 @@ class User: """ time: datetime object """ - self.last_talked = time + if time > self.last_talked: + self.last_talked = time def has_talked_since(self, t: int) -> bool: """ diff --git a/test/test_ui/test_render.py b/test/test_ui/test_render.py index d13751d1..57bd05c9 100644 --- a/test/test_ui/test_render.py +++ b/test/test_ui/test_render.py @@ -4,7 +4,7 @@ from datetime import datetime from poezio.theming import get_theme from poezio.ui.render import build_lines, Line, write_pre from poezio.ui.consts import SHORT_FORMAT -from poezio.ui.types import BaseMessage, Message, XMLLog +from poezio.ui.types import BaseMessage, Message, StatusMessage, XMLLog def test_simple_build_basemsg(): msg = BaseMessage(txt='coucou') @@ -28,6 +28,17 @@ def test_simple_render_separator(): line = build_lines(None, 100, True, 10)[0] assert line is None + +def test_simple_render_status(): + class Obj: + name = 'toto' + msg = StatusMessage("Coucou {name}", {'name': lambda: Obj.name}) + assert msg.txt == "Coucou toto" + Obj.name = 'titi' + build_lines(msg, 100, True, 10)[0] + assert msg.txt == "Coucou titi" + + class FakeBuffer: def __init__(self): self.text = '' |