summaryrefslogtreecommitdiff
path: root/alot
diff options
context:
space:
mode:
authorAnton Khirnov <anton@khirnov.net>2020-02-08 20:54:38 +0100
committerAnton Khirnov <anton@khirnov.net>2020-02-19 16:00:44 +0100
commit279b8316341841dd32bdda5a21bb61fed8b9d1ef (patch)
treed35fd36b6a887351c4c9dd198e337b8148c34423 /alot
parentd25d788bdcf91f4066ae8e80ef7aebe85213d4d3 (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.py38
-rw-r--r--alot/db/message.py16
-rw-r--r--alot/db/thread.py7
-rw-r--r--alot/widgets/thread.py53
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))