Rewrite part of the message handling/rendering

This commit is contained in:
mathieui 2019-09-28 18:35:23 +02:00
parent a5ef6ec910
commit 80ce8453f5
10 changed files with 374 additions and 310 deletions

View file

@ -1349,7 +1349,10 @@ class Core:
colors = get_theme().INFO_COLORS colors = get_theme().INFO_COLORS
color = colors.get(typ.lower(), colors.get('default', None)) color = colors.get(typ.lower(), colors.get('default', None))
nb_lines = self.information_buffer.add_message( nb_lines = self.information_buffer.add_message(
msg, nickname=typ, nick_color=color) txt=msg,
nickname=typ,
nick_color=color
)
popup_on = config.get('information_buffer_popup_on').split() popup_on = config.get('information_buffer_popup_on').split()
if isinstance(self.tabs.current_tab, tabs.RosterInfoTab): if isinstance(self.tabs.current_tab, tabs.RosterInfoTab):
self.refresh_window() self.refresh_window()

View file

@ -72,7 +72,6 @@ def add_line(
highlight=False, highlight=False,
top=top, top=top,
identifier=None, identifier=None,
str_time=None,
jid=None, jid=None,
) )

View file

@ -45,7 +45,8 @@ from poezio.decorators import command_args_parser, refresh_wrapper
from poezio.logger import logger from poezio.logger import logger
from poezio.text_buffer import TextBuffer from poezio.text_buffer import TextBuffer
from poezio.theming import get_theme, dump_tuple from poezio.theming import get_theme, dump_tuple
from poezio.windows.funcs import truncate_nick from poezio.ui.funcs import truncate_nick
from poezio.ui.consts import LONG_FORMAT_LENGTH
from slixmpp import JID, InvalidJID, Message from slixmpp import JID, InvalidJID, Message
@ -839,12 +840,8 @@ class ChatTab(Tab):
if message.me: if message.me:
offset += 1 offset += 1
if timestamp: if timestamp:
if message.str_time: if message.history:
offset += 1 + len(message.str_time) offset += 1 + LONG_FORMAT_LENGTH
if theme.CHAR_TIME_LEFT and message.str_time:
offset += 1
if theme.CHAR_TIME_RIGHT and message.str_time:
offset += 1
lines = poopt.cut_text(txt, self.text_win.width - offset - 1) lines = poopt.cut_text(txt, self.text_win.width - offset - 1)
for line in lines: for line in lines:
built_lines.append(line) built_lines.append(line)

View file

@ -262,7 +262,10 @@ class XMLTab(Tab):
else: else:
xml = self.core_buffer.messages[:] xml = self.core_buffer.messages[:]
text = '\n'.join( text = '\n'.join(
('%s %s %s' % (msg.str_time, msg.nickname, clean_text(msg.txt)) ('%s %s %s' % (
msg.time.strftime('%H:%M:%S'),
msg.nickname,
clean_text(msg.txt))
for msg in xml)) for msg in xml))
filename = os.path.expandvars(os.path.expanduser(args[0])) filename = os.path.expandvars(os.path.expanduser(args[0]))
try: try:

View file

@ -63,7 +63,6 @@ class TextBuffer:
highlight: bool = False, highlight: bool = False,
top: Optional[bool] = False, top: Optional[bool] = False,
identifier: Optional[str] = None, identifier: Optional[str] = None,
str_time: Optional[str] = None,
jid: Optional[str] = None, jid: Optional[str] = None,
ack: int = 0) -> int: ack: int = 0) -> int:
""" """
@ -71,14 +70,13 @@ class TextBuffer:
""" """
msg = Message( msg = Message(
txt, txt,
time, time=time,
nickname, nickname=nickname,
nick_color, nick_color=nick_color,
history, history=history,
user, user=user,
identifier, identifier=identifier,
top, top=top,
str_time=str_time,
highlight=highlight, highlight=highlight,
jid=jid, jid=jid,
ack=ack) ack=ack)
@ -180,7 +178,7 @@ class TextBuffer:
if msg.user and msg.user is not user: if msg.user and msg.user is not user:
raise CorrectionError("Different users") raise CorrectionError("Different users")
elif len(msg.str_time) > 8: # ugly elif msg.history:
raise CorrectionError("Delayed message") raise CorrectionError("Delayed message")
elif not msg.user and (msg.jid is None or jid is None): elif not msg.user and (msg.jid is None or jid is None):
raise CorrectionError('Could not check the ' raise CorrectionError('Could not check the '
@ -195,13 +193,13 @@ class TextBuffer:
self.correction_ids[new_id] = orig_id self.correction_ids[new_id] = orig_id
message = Message( message = Message(
txt, txt=txt,
time, time=time,
msg.nickname, nickname=msg.nickname,
msg.nick_color, nick_color=msg.nick_color,
False, history=False,
msg.user, user=msg.user,
orig_id, identifier=orig_id,
highlight=highlight, highlight=highlight,
old_message=msg, old_message=msg,
revisions=msg.revisions + 1, revisions=msg.revisions + 1,

View file

@ -1,4 +1,14 @@
from datetime import datetime
FORMAT_CHAR = '\x19' FORMAT_CHAR = '\x19'
# These are non-printable chars, so they should never appear in the input, # These are non-printable chars, so they should never appear in the input,
# I guess. But maybe we can find better chars that are even less risky. # I guess. But maybe we can find better chars that are even less risky.
FORMAT_CHARS = '\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x1A' FORMAT_CHARS = '\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x1A'
# Short date format (only show time)
SHORT_FORMAT = '%H:%M:%S'
SHORT_FORMAT_LENGTH = len(datetime.now().strftime(SHORT_FORMAT))
# Long date format (show date and time)
LONG_FORMAT = '%Y-%m-%d %H:%M:%S'
LONG_FORMAT_LENGTH = len(datetime.now().strftime(LONG_FORMAT))

View file

@ -22,12 +22,14 @@ def find_first_format_char(text: str,
return pos return pos
def truncate_nick(nick: Optional[str], size=10) -> Optional[str]: def truncate_nick(nick: Optional[str], size=10) -> str:
if size < 1: if size < 1:
size = 1 size = 1
if nick and len(nick) > size: if nick:
return nick[:size] + '' if len(nick) > size:
return nick return nick[:size] + ''
return nick
return ''
def parse_attrs(text: str, previous: Optional[List[str]] = None) -> List[str]: def parse_attrs(text: str, previous: Optional[List[str]] = None) -> List[str]:

234
poezio/ui/render.py Normal file
View file

@ -0,0 +1,234 @@
import logging
import curses
from datetime import datetime
from functools import singledispatch
from typing import List, Optional, Tuple
from math import ceil, log10
from poezio import poopt
from poezio.ui.consts import (
FORMAT_CHAR,
LONG_FORMAT,
SHORT_FORMAT,
)
from poezio.ui.funcs import (
truncate_nick,
parse_attrs,
)
from poezio.theming import (
get_theme,
)
from poezio.ui.types import (
BaseMessage,
Message,
XMLLog,
)
# msg is a reference to the corresponding Message object. text_start and
# text_end are the position delimiting the text in this line.
class Line:
__slots__ = ('msg', 'start_pos', 'end_pos', 'prepend')
def __init__(self, msg: BaseMessage, start_pos: int, end_pos: int, prepend: str) -> None:
self.msg = msg
self.start_pos = start_pos
self.end_pos = end_pos
self.prepend = prepend
def __repr__(self):
return '(%s, %s)' % (self.start_pos, self.end_pos)
LinePos = Tuple[int, int]
def generate_lines(lines: List[LinePos], msg: BaseMessage, default_color: str = '') -> List[Line]:
line_objects = []
attrs = [] # type: List[str]
prepend = default_color if default_color else ''
for line in lines:
saved = Line(
msg=msg,
start_pos=line[0],
end_pos=line[1],
prepend=prepend)
attrs = parse_attrs(msg.txt[line[0]:line[1]], attrs)
if attrs:
prepend = FORMAT_CHAR + FORMAT_CHAR.join(attrs)
else:
if default_color:
prepend = default_color
else:
prepend = ''
line_objects.append(saved)
return line_objects
@singledispatch
def build_lines(msg: BaseMessage, width: int, timestamp: bool, nick_size: int = 10) -> List[Line]:
offset = msg.compute_offset(timestamp, nick_size)
lines = poopt.cut_text(msg.txt, width - offset - 1)
return generate_lines(lines, msg, default_color='')
@build_lines.register(type(None))
def build_separator(*args, **kwargs):
return [None]
@build_lines.register(Message)
def build_message(msg: Message, width: int, timestamp: bool, nick_size: int = 10) -> List[Line]:
"""
Build a list of lines from this message.
"""
txt = msg.txt
if not txt:
return []
offset = msg.compute_offset(timestamp, nick_size)
lines = poopt.cut_text(txt, width - offset - 1)
return generate_lines(lines, msg, default_color='')
@build_lines.register(XMLLog)
def build_xmllog(msg: XMLLog, width: int, timestamp: bool, nick_size: int = 10) -> List[Line]:
offset = msg.compute_offset(timestamp, nick_size)
lines = poopt.cut_text(msg.txt, width - offset - 1)
return generate_lines(lines, msg, default_color='')
@singledispatch
def write_pre(msg: BaseMessage, win, with_timestamps: bool, nick_size: int) -> int:
"""Write the part before text (only the timestamp)"""
if with_timestamps:
return 1 + PreMessageHelpers.write_time(win, False, msg.time)
return 0
@write_pre.register(Message)
def write_pre_message(msg: Message, win, with_timestamps: bool, nick_size: int) -> int:
"""Write the part before the body:
- timestamp (short or long)
- ack/nack
- nick (with a "* " for /me)
- LMC number if present
"""
offset = 0
if with_timestamps:
logging.debug(msg)
offset += PreMessageHelpers.write_time(win, msg.history, msg.time)
if not msg.nickname: # not a message, nothing to do afterwards
return offset
nick = truncate_nick(msg.nickname, nick_size)
offset += poopt.wcswidth(nick)
if msg.nick_color:
color = msg.nick_color
elif msg.user:
color = msg.user.color
else:
color = None
if msg.ack:
if msg.ack > 0:
offset += PreMessageHelpers.write_ack(win)
else:
offset += PreMessageHelpers.write_nack(win)
if msg.me:
with win.colored_text(color=get_theme().COLOR_ME_MESSAGE):
win.addstr('* ')
PreMessageHelpers.write_nickname(win, nick, color, msg.highlight)
offset += PreMessageHelpers.write_revisions(win, msg)
win.addstr(' ')
offset += 3
else:
PreMessageHelpers.write_nickname(win, nick, color, msg.highlight)
offset += PreMessageHelpers.write_revisions(win, msg)
win.addstr('> ')
offset += 2
return offset
@write_pre.register(XMLLog)
def write_pre_xmllog(msg: XMLLog, win, with_timestamps: bool, nick_size: int) -> int:
"""Write the part before the stanza (timestamp + IN/OUT)"""
offset = 0
if with_timestamps:
offset += PreMessageHelpers.write_time(win, False, msg.time)
theme = get_theme()
if msg.incoming:
char = theme.CHAR_XML_IN
color = theme.COLOR_XML_IN
else:
char = theme.CHAR_XML_OUT
color = theme.COLOR_XML_OUT
nick = truncate_nick(char, nick_size)
offset += poopt.wcswidth(nick)
PreMessageHelpers.write_nickname(win, char, color)
win.addstr(' ')
return offset
class PreMessageHelpers:
@staticmethod
def write_revisions(buffer, msg: Message) -> int:
if msg.revisions:
color = get_theme().COLOR_REVISIONS_MESSAGE
with buffer.colored_text(color=color):
buffer.addstr('%d' % msg.revisions)
return ceil(log10(msg.revisions + 1))
return 0
@staticmethod
def write_ack(buffer) -> int:
theme = get_theme()
color = theme.COLOR_CHAR_ACK
with buffer.colored_text(color=color):
buffer.addstr(theme.CHAR_ACK_RECEIVED)
buffer.addstr(' ')
return poopt.wcswidth(theme.CHAR_ACK_RECEIVED) + 1
@staticmethod
def write_nack(buffer) -> int:
theme = get_theme()
color = theme.COLOR_CHAR_NACK
with buffer.colored_text(color=color):
buffer.addstr(theme.CHAR_NACK)
buffer.addstr(' ')
return poopt.wcswidth(theme.CHAR_NACK) + 1
@staticmethod
def write_nickname(buffer, nickname: str, color, highlight=False) -> None:
"""
Write the nickname, using the user's color
and return the number of written characters
"""
if not nickname:
return
attr = None
if highlight:
hl_color = get_theme().COLOR_HIGHLIGHT_NICK
if hl_color == "reverse":
attr = curses.A_REVERSE
else:
color = hl_color
with buffer.colored_text(color=color, attr=attr):
buffer.addstr(nickname)
@staticmethod
def write_time(buffer, history: bool, time: datetime) -> int:
"""
Write the date on the yth line of the window
"""
if time:
if history:
format = LONG_FORMAT
else:
format = SHORT_FORMAT
logging.debug(time)
time_str = time.strftime(format)
color = get_theme().COLOR_TIME_STRING
with buffer.colored_text(color=color):
buffer.addstr(time_str)
buffer.addstr(' ')
return poopt.wcswidth(time_str) + 1
return 0

View file

@ -2,28 +2,78 @@
from datetime import datetime from datetime import datetime
from math import ceil, log10 from math import ceil, log10
from typing import Union, Optional, List, Tuple from typing import Union, Optional, List, Tuple
from poezio.ui.funcs import truncate_nick
from poezio.theming import get_theme, dump_tuple
from poezio.ui.funcs import truncate_nick, parse_attrs
from poezio import poopt from poezio import poopt
from poezio.ui.consts import FORMAT_CHAR from poezio.user import User
from poezio.theming import dump_tuple, get_theme
from poezio.ui.consts import (
SHORT_FORMAT_LENGTH,
LONG_FORMAT_LENGTH,
)
class Message: class BaseMessage:
__slots__ = ('txt', 'nick_color', 'time', 'str_time', 'nickname', 'user', __slots__ = ('txt', 'time', 'identifier')
def __init__(self, txt: str, identifier: str = '', time: Optional[datetime] = None):
self.txt = txt
self.identifier = identifier
if time is not None:
self.time = time
else:
self.time = datetime.now()
def compute_offset(self, with_timestamps: bool, nick_size: int) -> int:
return SHORT_FORMAT_LENGTH + 1
class XMLLog(BaseMessage):
"""XML Log message"""
__slots__ = ('txt', 'time', 'identifier', 'incoming')
def __init__(
self,
txt: str,
incoming: bool,
):
BaseMessage.__init__(
self,
txt=txt,
identifier='',
)
self.txt = txt
self.identifier = ''
self.incoming = incoming
def compute_offset(self, with_timestamps: bool, nick_size: int) -> int:
offset = 0
theme = get_theme()
IN, OUT = theme.CHAR_XML_IN, theme.CHAR_XML_OUT
if with_timestamps:
offset += 1 + SHORT_FORMAT_LENGTH
if self.incoming:
nick = IN
else:
nick = OUT
nick = truncate_nick(nick, nick_size) or ''
offset += 1 + len(nick)
return offset
class Message(BaseMessage):
__slots__ = ('txt', 'nick_color', 'time', 'nickname', 'user', 'history',
'identifier', 'top', 'highlight', 'me', 'old_message', 'revisions', 'identifier', 'top', 'highlight', 'me', 'old_message', 'revisions',
'jid', 'ack') 'jid', 'ack')
def __init__(self, def __init__(self,
txt: str, txt: str,
time: Optional[datetime],
nickname: Optional[str], nickname: Optional[str],
nick_color: Optional[Tuple], time: Optional[datetime] = None,
history: bool, nick_color: Optional[Tuple] = None,
user: Optional[str], history: bool = False,
identifier: Optional[str], user: Optional[User] = None,
identifier: Optional[str] = '',
top: Optional[bool] = False, top: Optional[bool] = False,
str_time: Optional[str] = None,
highlight: bool = False, highlight: bool = False,
old_message: Optional['Message'] = None, old_message: Optional['Message'] = None,
revisions: int = 0, revisions: int = 0,
@ -33,27 +83,22 @@ class Message:
Create a new Message object with parameters, check for /me messages, Create a new Message object with parameters, check for /me messages,
and delayed messages and delayed messages
""" """
time = time if time is not None else datetime.now() BaseMessage.__init__(
self,
txt=txt.replace('\t', ' ') + '\x19o',
identifier=identifier or '',
time=time,
)
if txt.startswith('/me '): if txt.startswith('/me '):
me = True me = True
txt = '\x19%s}%s\x19o' % (dump_tuple(get_theme().COLOR_ME_MESSAGE), txt = '\x19%s}%s\x19o' % (dump_tuple(get_theme().COLOR_ME_MESSAGE),
txt[4:]) txt[4:])
else: else:
me = False me = False
str_time = time.strftime("%H:%M:%S") self.history = history
if history:
txt = txt.replace(
'\x19o',
'\x19o\x19%s}' % dump_tuple(get_theme().COLOR_LOG_MSG))
str_time = time.strftime("%Y-%m-%d %H:%M:%S")
self.txt = txt.replace('\t', ' ') + '\x19o'
self.nick_color = nick_color
self.time = time
self.str_time = str_time
self.nickname = nickname self.nickname = nickname
self.nick_color = nick_color
self.user = user self.user = user
self.identifier = identifier
self.top = top self.top = top
self.highlight = highlight self.highlight = highlight
self.me = me self.me = me
@ -91,68 +136,29 @@ class Message:
rev -= 1 rev -= 1
return ''.join(acc) return ''.join(acc)
def render(self, width: int, timestamp: bool = False, nick_size: int = 10) -> List["Line"]: def compute_offset(self, with_timestamps: bool, nick_size: int) -> int:
"""
Build a list of lines from this message.
"""
txt = self.txt
if not txt:
return []
theme = get_theme()
if len(self.str_time) > 8:
default_color = (
FORMAT_CHAR + dump_tuple(theme.COLOR_LOG_MSG) + '}') # type: Optional[str]
else:
default_color = None
ret = [] # type: List[Union[None, Line]]
nick = truncate_nick(self.nickname, nick_size)
offset = 0 offset = 0
if with_timestamps:
if self.history:
offset += 1 + LONG_FORMAT_LENGTH
else:
offset += 1 + SHORT_FORMAT_LENGTH
if not self.nickname: # not a message, nothing to do afterwards
return offset
nick = truncate_nick(self.nickname, nick_size) or ''
offset += poopt.wcswidth(nick)
if self.ack: if self.ack:
theme = get_theme()
if self.ack > 0: if self.ack > 0:
offset += poopt.wcswidth(theme.CHAR_ACK_RECEIVED) + 1 offset += poopt.wcswidth(theme.CHAR_ACK_RECEIVED) + 1
else: else:
offset += poopt.wcswidth(theme.CHAR_NACK) + 1 offset += poopt.wcswidth(theme.CHAR_NACK) + 1
if nick:
offset += poopt.wcswidth(nick) + 2 # + nick + '> ' length
if self.revisions > 0:
offset += ceil(log10(self.revisions + 1))
if self.me: if self.me:
offset += 1 # '* ' before and ' ' after offset += 3
if timestamp: else:
if self.str_time: offset += 2
offset += 1 + len(self.str_time) if self.revisions:
if theme.CHAR_TIME_LEFT and self.str_time: offset += ceil(log10(self.revisions + 1))
offset += 1 return offset
if theme.CHAR_TIME_RIGHT and self.str_time:
offset += 1
lines = poopt.cut_text(txt, width - offset - 1)
prepend = default_color if default_color else ''
attrs = [] # type: List[str]
for line in lines:
saved = Line(
msg=self,
start_pos=line[0],
end_pos=line[1],
prepend=prepend)
attrs = parse_attrs(self.txt[line[0]:line[1]], attrs)
if attrs:
prepend = FORMAT_CHAR + FORMAT_CHAR.join(attrs)
else:
if default_color:
prepend = default_color
else:
prepend = ''
ret.append(saved)
return ret
# msg is a reference to the corresponding Message object. text_start and
# text_end are the position delimiting the text in this line.
class Line:
__slots__ = ('msg', 'start_pos', 'end_pos', 'prepend')
def __init__(self, msg: Message, start_pos: int, end_pos: int, prepend: str) -> None:
self.msg = msg
self.start_pos = start_pos
self.end_pos = end_pos
self.prepend = prepend

View file

@ -14,7 +14,8 @@ from poezio.ui.funcs import truncate_nick, parse_attrs
from poezio import poopt from poezio import poopt
from poezio.config import config from poezio.config import config
from poezio.theming import to_curses_attr, get_theme, dump_tuple from poezio.theming import to_curses_attr, get_theme, dump_tuple
from poezio.ui.types import Line, Message from poezio.ui.types import Message
from poezio.ui.render import Line, build_lines, write_pre
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -106,7 +107,7 @@ class BaseTextWin(Win):
Build a list of lines from a message, without adding it Build a list of lines from a message, without adding it
to a list to a list
""" """
return [] return build_lines(message, self.width, timestamp, nick_size)
def refresh(self) -> None: def refresh(self) -> None:
pass pass
@ -117,20 +118,6 @@ class BaseTextWin(Win):
""" """
self.addstr_colored(txt, y, x) self.addstr_colored(txt, y, x)
def write_time(self, time: str) -> int:
"""
Write the date on the yth line of the window
"""
if time:
color = get_theme().COLOR_TIME_STRING
curses_color = to_curses_attr(color)
self._win.attron(curses_color)
self.addstr(time)
self._win.attroff(curses_color)
self.addstr(' ')
return poopt.wcswidth(time) + 1
return 0
# TODO: figure out the type of room. # TODO: figure out the type of room.
def resize(self, height: int, width: int, y: int, x: int, room=None) -> None: def resize(self, height: int, width: int, y: int, x: int, room=None) -> None:
if hasattr(self, 'width'): if hasattr(self, 'width'):
@ -341,15 +328,6 @@ class TextWin(BaseTextWin):
self.built_lines.pop(0) self.built_lines.pop(0)
return len(lines) return len(lines)
def build_message(self, message: Optional[Message], timestamp: bool = False, nick_size: int = 10) -> List[Union[None, Line]]:
"""
Build a list of lines from a message, without adding it
to a list
"""
if message is None: # line separator
return [None]
return message.render(self.width, timestamp, nick_size)
def refresh(self) -> None: def refresh(self) -> None:
log.debug('Refresh: %s', self.__class__.__name__) log.debug('Refresh: %s', self.__class__.__name__)
if self.height <= 0: if self.height <= 0:
@ -367,11 +345,10 @@ class TextWin(BaseTextWin):
if line: if line:
msg = line.msg msg = line.msg
if line.start_pos == 0: if line.start_pos == 0:
offset = self.write_pre_msg(msg, with_timestamps, offset = write_pre(msg, self, with_timestamps, nick_size)
nick_size)
elif y == 0: elif y == 0:
offset = self.compute_offset(msg, with_timestamps, offset = msg.compute_offset(with_timestamps,
nick_size) nick_size)
self.write_text( self.write_text(
y, offset, y, offset,
line.prepend + line.msg.txt[line.start_pos:line.end_pos]) line.prepend + line.msg.txt[line.start_pos:line.end_pos])
@ -382,120 +359,11 @@ class TextWin(BaseTextWin):
self._win.attrset(0) self._win.attrset(0)
self._refresh() self._refresh()
def compute_offset(self, msg, with_timestamps, nick_size) -> int:
offset = 0
if with_timestamps and msg.str_time:
offset += poopt.wcswidth(msg.str_time) + 1
if not msg.nickname: # not a message, nothing to do afterwards
return offset
nick = truncate_nick(msg.nickname, nick_size)
offset += poopt.wcswidth(nick)
if msg.ack:
theme = get_theme()
if msg.ack > 0:
offset += poopt.wcswidth(theme.CHAR_ACK_RECEIVED) + 1
else:
offset += poopt.wcswidth(theme.CHAR_NACK) + 1
if msg.me:
offset += 3
else:
offset += 2
if msg.revisions:
offset += ceil(log10(msg.revisions + 1))
offset += self.write_revisions(msg)
return offset
def write_pre_msg(self, msg, with_timestamps, nick_size) -> int:
offset = 0
if with_timestamps:
offset += self.write_time(msg.str_time)
if not msg.nickname: # not a message, nothing to do afterwards
return offset
nick = truncate_nick(msg.nickname, nick_size)
offset += poopt.wcswidth(nick)
if msg.nick_color:
color = msg.nick_color
elif msg.user:
color = msg.user.color
else:
color = None
if msg.ack:
if msg.ack > 0:
offset += self.write_ack()
else:
offset += self.write_nack()
if msg.me:
self._win.attron(to_curses_attr(get_theme().COLOR_ME_MESSAGE))
self.addstr('* ')
self.write_nickname(nick, color, msg.highlight)
offset += self.write_revisions(msg)
self.addstr(' ')
offset += 3
else:
self.write_nickname(nick, color, msg.highlight)
offset += self.write_revisions(msg)
self.addstr('> ')
offset += 2
return offset
def write_revisions(self, msg) -> int:
if msg.revisions:
self._win.attron(
to_curses_attr(get_theme().COLOR_REVISIONS_MESSAGE))
self.addstr('%d' % msg.revisions)
self._win.attrset(0)
return ceil(log10(msg.revisions + 1))
return 0
def write_line_separator(self, y) -> None: def write_line_separator(self, y) -> None:
theme = get_theme() theme = get_theme()
char = theme.CHAR_NEW_TEXT_SEPARATOR char = theme.CHAR_NEW_TEXT_SEPARATOR
self.addnstr(y, 0, char * (self.width // len(char) - 1), self.width, self.addnstr(y, 0, char * (self.width // len(char) - 1), self.width,
to_curses_attr(theme.COLOR_NEW_TEXT_SEPARATOR)) to_curses_attr(theme.COLOR_NEW_TEXT_SEPARATOR))
def write_ack(self) -> int:
theme = get_theme()
color = theme.COLOR_CHAR_ACK
self._win.attron(to_curses_attr(color))
self.addstr(theme.CHAR_ACK_RECEIVED)
self._win.attroff(to_curses_attr(color))
self.addstr(' ')
return poopt.wcswidth(theme.CHAR_ACK_RECEIVED) + 1
def write_nack(self) -> int:
theme = get_theme()
color = theme.COLOR_CHAR_NACK
self._win.attron(to_curses_attr(color))
self.addstr(theme.CHAR_NACK)
self._win.attroff(to_curses_attr(color))
self.addstr(' ')
return poopt.wcswidth(theme.CHAR_NACK) + 1
def write_nickname(self, nickname, color, highlight=False) -> None:
"""
Write the nickname, using the user's color
and return the number of written characters
"""
if not nickname:
return
if highlight:
hl_color = get_theme().COLOR_HIGHLIGHT_NICK
if hl_color == "reverse":
self._win.attron(curses.A_REVERSE)
else:
color = hl_color
if color:
self._win.attron(to_curses_attr(color))
self.addstr(nickname)
if color:
self._win.attroff(to_curses_attr(color))
if highlight and hl_color == "reverse":
self._win.attroff(curses.A_REVERSE)
def modify_message(self, old_id, message) -> None: def modify_message(self, old_id, message) -> None:
""" """
Find a message, and replace it with a new one Find a message, and replace it with a new one
@ -544,28 +412,12 @@ class XMLTextWin(BaseTextWin):
if line: if line:
msg = line.msg msg = line.msg
if line.start_pos == 0: if line.start_pos == 0:
if msg.nickname == theme.CHAR_XML_OUT: offset = write_pre(msg, self, True, 10)
color = theme.COLOR_XML_OUT
elif msg.nickname == theme.CHAR_XML_IN:
color = theme.COLOR_XML_IN
self.write_time(msg.str_time)
self.write_prefix(msg.nickname, color)
self.addstr(' ')
if y != self.height - 1: if y != self.height - 1:
self.addstr('\n') self.addstr('\n')
self._win.attrset(0) self._win.attrset(0)
for y, line in enumerate(lines): for y, line in enumerate(lines):
offset = 0 offset = msg.compute_offset(True, 10)
# Offset for the timestamp (if any) plus a space after it
offset += len(line.msg.str_time)
# space
offset += 1
# Offset for the prefix
offset += poopt.wcswidth(truncate_nick(line.msg.nickname))
# space
offset += 1
self.write_text( self.write_text(
y, offset, y, offset,
line.prepend + line.msg.txt[line.start_pos:line.end_pos]) line.prepend + line.msg.txt[line.start_pos:line.end_pos])
@ -573,43 +425,3 @@ class XMLTextWin(BaseTextWin):
self.addstr('\n') self.addstr('\n')
self._win.attrset(0) self._win.attrset(0)
self._refresh() self._refresh()
def build_message(self, message: Message, timestamp: bool = False, nick_size: int = 10) -> List[Line]:
txt = message.txt
ret = []
default_color = None
nick = truncate_nick(message.nickname, nick_size)
offset = 0
if nick:
offset += poopt.wcswidth(nick) + 1 # + nick + ' ' length
if message.str_time:
offset += 1 + len(message.str_time)
theme = get_theme()
if theme.CHAR_TIME_LEFT and message.str_time:
offset += 1
if theme.CHAR_TIME_RIGHT and message.str_time:
offset += 1
lines = poopt.cut_text(txt, self.width - offset - 1)
prepend = default_color if default_color else ''
attrs = [] # type: List[str]
for line in lines:
saved = Line(
msg=message,
start_pos=line[0],
end_pos=line[1],
prepend=prepend)
attrs = parse_attrs(message.txt[line[0]:line[1]], attrs)
if attrs:
prepend = FORMAT_CHAR + FORMAT_CHAR.join(attrs)
else:
if default_color:
prepend = default_color
else:
prepend = ''
ret.append(saved)
return ret
def write_prefix(self, nickname, color) -> None:
self._win.attron(to_curses_attr(color))
self.addstr(truncate_nick(nickname))
self._win.attroff(to_curses_attr(color))