diff options
author | Anton Khirnov <anton@khirnov.net> | 2020-02-08 20:54:38 +0100 |
---|---|---|
committer | Anton Khirnov <anton@khirnov.net> | 2020-02-19 16:00:44 +0100 |
commit | 279b8316341841dd32bdda5a21bb61fed8b9d1ef (patch) | |
tree | d35fd36b6a887351c4c9dd198e337b8148c34423 /alot | |
parent | d25d788bdcf91f4066ae8e80ef7aebe85213d4d3 (diff) |
thread: implement tree decorations
They were temporarily removed in the previous commit.
Still not working:
- theming for the decorations
- drawing the connector line properly for expanded messages
- configurable indentation
Diffstat (limited to 'alot')
-rw-r--r-- | alot/buffers/thread.py | 38 | ||||
-rw-r--r-- | alot/db/message.py | 16 | ||||
-rw-r--r-- | alot/db/thread.py | 7 | ||||
-rw-r--r-- | alot/widgets/thread.py | 53 |
4 files changed, 94 insertions, 20 deletions
diff --git a/alot/buffers/thread.py b/alot/buffers/thread.py index bc467ded..6c92fe64 100644 --- a/alot/buffers/thread.py +++ b/alot/buffers/thread.py @@ -8,7 +8,7 @@ import logging from .buffer import Buffer from ..settings.const import settings -from ..widgets.thread import MessageWidget +from ..widgets.thread import MessageWidget, ThreadNode from .. import commands from ..db.errors import NonexistantObjectError @@ -18,6 +18,8 @@ class ThreadBuffer(Buffer): modename = 'thread' + _msg_widgets = None + def __init__(self, ui, thread): """ :param ui: main UI @@ -59,11 +61,17 @@ class ThreadBuffer(Buffer): self.body = urwid.SolidFill() return - self._msg_walker = urwid.SimpleFocusListWalker( - [MessageWidget(msg, idx & 1) for (idx, msg) in - enumerate(self.thread.message_list)]) + self._msg_widgets = [] + + body_walker = urwid.SimpleFocusListWalker([]) + for pos, msg in enumerate(self.thread.message_list): + msg_wgt = MessageWidget(msg, pos & 1) + wgt = ThreadNode(msg_wgt, self.thread, pos, self._indent_width) + + self._msg_widgets.append(msg_wgt) + body_walker.append(wgt) - self.body = urwid.ListBox(self._msg_walker) + self.body = urwid.ListBox(body_walker) def get_selected_message_position(self): """Return position of focussed message in the thread tree.""" @@ -71,7 +79,8 @@ class ThreadBuffer(Buffer): def get_selected_message_widget(self): """Return currently focused :class:`MessageWidget`.""" - return self.body.focus + pos = self.get_selected_message_position() + return self._msg_widgets[pos] def get_selected_message(self): """Return focussed :class:`~alot.db.message.Message`.""" @@ -81,7 +90,7 @@ class ThreadBuffer(Buffer): """ Iterate over all the message widgets in this buffer """ - for w in self._msg_walker: + for w in self._msg_widgets: yield w # needed for ui.get_deep_focus.. @@ -99,7 +108,7 @@ class ThreadBuffer(Buffer): self.set_focus(0) def focus_last(self): - self.set_focus(max(self.thread.total_messages - 1), 0) + self.set_focus(max(self.thread.total_messages - 1, 0)) def focus_selected_message(self): """focus the summary line of currently focused message""" @@ -107,14 +116,9 @@ class ThreadBuffer(Buffer): def focus_parent(self): """move focus to parent of currently focused message""" - pos = self.get_selected_message_position() - cur_depth = self.get_selected_message().depth - - for idx in reversed(range(0, pos)): - msg = self.thread.message_list[idx] - if msg.depth < cur_depth: - self.set_focus(idx) - break + msg = self.get_selected_message() + if msg.parent: + self.set_focus(self.thread.message_list.index(msg.parent)) def focus_first_reply(self): """move focus to first reply to currently focused message""" @@ -171,7 +175,7 @@ class ThreadBuffer(Buffer): walk = reversed(range(0, cur_pos)) for pos in walk: - if prop(self._msg_walker[pos]): + if prop(self._msg_widgets[pos]): self.set_focus(pos) break diff --git a/alot/db/message.py b/alot/db/message.py index 62b85788..4adb0816 100644 --- a/alot/db/message.py +++ b/alot/db/message.py @@ -41,6 +41,12 @@ class Message: """A list of replies to this message""" replies = None + """ + This message parent in the list (i.e. the message this message is a reply + to). None when this message is top-level. + """ + parent = None + def __init__(self, dbman, thread, msg, depth): """ :param dbman: db manager that is used for further lookups @@ -248,3 +254,13 @@ class Message: """tests if this messages is in the resultset for `querystring`""" searchfor = '( {} ) AND id:{}'.format(querystring, self.id) return self._dbman.count_messages(searchfor) > 0 + + def parents(self): + """ + A generator iterating over this message's parents up to the topmost + level. + """ + m = self.parent + while m: + yield m + m = m.parent diff --git a/alot/db/thread.py b/alot/db/thread.py index a76997f8..3dc90325 100644 --- a/alot/db/thread.py +++ b/alot/db/thread.py @@ -107,22 +107,23 @@ class Thread: msg_tree = [] msg_list = [] - def thread_tree_walk(nm_msg, depth): + def thread_tree_walk(nm_msg, depth, parent): msg = Message(self._dbman, self, nm_msg, depth) msg_list.append(msg) replies = [] for m in nm_msg.get_replies(): - replies.append(thread_tree_walk(m, depth + 1)) + replies.append(thread_tree_walk(m, depth + 1, msg)) msg.replies = replies + msg.parent = parent msgs[msg.id] = msg return msg for m in nm_thread.get_toplevel_messages(): - msg_tree.append(thread_tree_walk(m, 0)) + msg_tree.append(thread_tree_walk(m, 0, None)) return msgs, msg_tree, msg_list diff --git a/alot/widgets/thread.py b/alot/widgets/thread.py index 0b4e2404..ebbc4704 100644 --- a/alot/widgets/thread.py +++ b/alot/widgets/thread.py @@ -290,3 +290,56 @@ class MessageWidget(urwid.WidgetWrap): matches given `querystring` """ self.display_content = not self._message.matches(querystring) + +class ThreadNode(urwid.WidgetWrap): + _ARROW = '➤' + _HBAR = '─' + _VBAR = '│' + _TNODE = '├' + _CORNER = '└' + + _decor_text = None + _have_next_sibling = None + + def __init__(self, msg_wgt, thread, pos, indent): + msg = msg_wgt.get_message() + + if msg.depth == 0: + wgt = msg_wgt + else: + self._decor_text = urwid.Text('') + wgt = urwid.Columns([('pack', self._decor_text), msg_wgt]) + + ancestor_chain = [msg] + for p in msg.parents(): + ancestor_chain.insert(0, p) + + have_sibling = [] + for d, m in enumerate(ancestor_chain): + if d == 0: + have_sibling.append(m != thread.toplevel_messages[-1]) + else: + have_sibling.append(m != ancestor_chain[d - 1].replies[-1]) + + self._have_next_sibling = have_sibling + + super().__init__(wgt) + + self.set_indent(indent) + + def set_indent(self, indent): + if self._decor_text is None: + return + + seg_text = [] + if indent > 0: + depth = len(self._have_next_sibling) - 1 + for d in range(depth): + if d == depth - 1: + corner = self._TNODE if self._have_next_sibling[d + 1] else self._CORNER + seg_text.append(corner + self._ARROW) + else: + bar = self._VBAR if self._have_next_sibling[d + 1] else ' ' + seg_text.append(bar + ' ') + + self._decor_text.set_text(''.join(seg_text)) |