summaryrefslogtreecommitdiff
path: root/alot/buffers
diff options
context:
space:
mode:
authorAnton Khirnov <anton@khirnov.net>2020-02-08 13:56:56 +0100
committerAnton Khirnov <anton@khirnov.net>2020-02-19 16:00:44 +0100
commitd25d788bdcf91f4066ae8e80ef7aebe85213d4d3 (patch)
treef0b35cfdd0fbeb36af971a6ea640f145771d16aa /alot/buffers
parent48dac1d9089ce2a36c55dc4768b24293d1257a37 (diff)
thread: drop the use of urwidtrees
Their API is misdesigned - forces the use of trees for nontree objects and mixes data relationships with display properties. The result is a mess that is hard to understand/maintain/extend. Replace the use of urwidtrees with urwid Pile and ListBox. This temporarily removes tree-style indentation and decorations for thread buffers. That will be reimplemented in following commits.
Diffstat (limited to 'alot/buffers')
-rw-r--r--alot/buffers/thread.py243
1 files changed, 87 insertions, 156 deletions
diff --git a/alot/buffers/thread.py b/alot/buffers/thread.py
index 6434bb04..bc467ded 100644
--- a/alot/buffers/thread.py
+++ b/alot/buffers/thread.py
@@ -5,11 +5,10 @@
import asyncio
import urwid
import logging
-from urwidtrees import ArrowTree, TreeBox, NestedTree
from .buffer import Buffer
from ..settings.const import settings
-from ..widgets.thread import ThreadTree
+from ..widgets.thread import MessageWidget
from .. import commands
from ..db.errors import NonexistantObjectError
@@ -27,16 +26,15 @@ class ThreadBuffer(Buffer):
:type thread: :class:`~alot.db.Thread`
"""
self.thread = thread
- self.message_count = thread.total_messages
self._indent_width = settings.get('thread_indent_replies')
self.rebuild()
- Buffer.__init__(self, ui, self.body)
+ super().__init__(ui, self.body)
def __str__(self):
return '[thread] %s (%d message%s)' % (self.thread.subject,
- self.message_count,
- 's' * (self.message_count > 1))
+ self.thread.total_messages,
+ 's' * (self.thread.total_messages > 1))
def translated_tags_str(self, intersection=False):
tags = self.thread.get_tags(intersection=intersection)
@@ -49,7 +47,7 @@ class ThreadBuffer(Buffer):
info['subject'] = self.thread.subject
info['authors'] = self.thread.get_authors_string()
info['tid'] = self.thread.id
- info['message_count'] = self.message_count
+ info['message_count'] = self.thread.total_messages
info['thread_tags'] = self.translated_tags_str()
info['intersection_tags'] = self.translated_tags_str(intersection=True)
return info
@@ -59,78 +57,37 @@ class ThreadBuffer(Buffer):
self.thread.refresh()
except NonexistantObjectError:
self.body = urwid.SolidFill()
- self.message_count = 0
return
- self._tree = ThreadTree(self.thread)
+ self._msg_walker = urwid.SimpleFocusListWalker(
+ [MessageWidget(msg, idx & 1) for (idx, msg) in
+ enumerate(self.thread.message_list)])
- # define A to be the tree to be wrapped by a NestedTree and displayed.
- # We wrap the thread tree into an ArrowTree for decoration if
- # indentation was requested and otherwise use it as is.
- if self._indent_width == 0:
- A = self._tree
- else:
- # we want decoration.
- bars_att = settings.get_theming_attribute('thread', 'arrow_bars')
- # only add arrow heads if there is space (indent > 1).
- heads_char = None
- heads_att = None
- if self._indent_width > 1:
- heads_char = '➤'
- heads_att = settings.get_theming_attribute('thread',
- 'arrow_heads')
- A = ArrowTree(
- self._tree,
- indent=self._indent_width,
- childbar_offset=0,
- arrow_tip_att=heads_att,
- arrow_tip_char=heads_char,
- arrow_att=bars_att)
-
- self._nested_tree = NestedTree(A, interpret_covered=True)
- self.body = TreeBox(self._nested_tree)
-
- self.message_count = self.thread.total_messages
-
- def get_selected_mid(self):
- """Return Message ID of focussed message."""
- return self.body.get_focus()[1][0]
+ self.body = urwid.ListBox(self._msg_walker)
def get_selected_message_position(self):
"""Return position of focussed message in the thread tree."""
- return self._sanitize_position((self.get_selected_mid(),))
+ return self.body.focus_position
- def get_selected_messagetree(self):
- """Return currently focussed :class:`MessageTree`."""
- return self._nested_tree[self.body.get_focus()[1][:1]]
+ def get_selected_message_widget(self):
+ """Return currently focused :class:`MessageWidget`."""
+ return self.body.focus
def get_selected_message(self):
"""Return focussed :class:`~alot.db.message.Message`."""
- return self.get_selected_messagetree()._message
-
- def get_messagetree_positions(self):
- """
- Return a Generator to walk through all positions of
- :class:`MessageTree` in the :class:`ThreadTree` of this buffer.
- """
- return [(pos,) for pos in self._tree.positions()]
+ return self.get_selected_message_widget().get_message()
- def messagetrees(self):
+ def message_widgets(self):
"""
- returns a Generator of all :class:`MessageTree` in the
- :class:`ThreadTree` of this buffer.
+ Iterate over all the message widgets in this buffer
"""
- for pos in self._tree.positions():
- yield self._tree[pos]
-
- def refresh(self):
- """Refresh and flush caches of Thread tree."""
- self.body.refresh()
+ for w in self._msg_walker:
+ yield w
# needed for ui.get_deep_focus..
def get_focus(self):
"Get the focus from the underlying body widget."
- return self.body.get_focus()
+ return self.body.focus
def set_focus(self, pos):
"Set the focus in the underlying body widget."
@@ -139,145 +96,119 @@ class ThreadBuffer(Buffer):
def focus_first(self):
"""set focus to first message of thread"""
- self.body.set_focus(self._nested_tree.root)
+ self.set_focus(0)
def focus_last(self):
- self.body.set_focus(next(self._nested_tree.positions(reverse=True)))
-
- def _sanitize_position(self, pos):
- return self._nested_tree._sanitize_position(pos,
- self._nested_tree._tree)
+ self.set_focus(max(self.thread.total_messages - 1), 0)
def focus_selected_message(self):
- """focus the summary line of currently focussed message"""
- # move focus to summary (root of current MessageTree)
+ """focus the summary line of currently focused message"""
self.set_focus(self.get_selected_message_position())
def focus_parent(self):
- """move focus to parent of currently focussed message"""
- mid = self.get_selected_mid()
- newpos = self._tree.parent_position(mid)
- if newpos is not None:
- newpos = self._sanitize_position((newpos,))
- self.body.set_focus(newpos)
+ """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
def focus_first_reply(self):
- """move focus to first reply to currently focussed message"""
- mid = self.get_selected_mid()
- newpos = self._tree.first_child_position(mid)
- if newpos is not None:
- newpos = self._sanitize_position((newpos,))
- self.body.set_focus(newpos)
+ """move focus to first reply to currently focused message"""
+ msg = self.get_selected_message()
+ if len(msg.replies) > 0:
+ new_focus = self.thread.message_list.index(msg.replies[0])
+ self.set_focus(new_focus)
def focus_last_reply(self):
- """move focus to last reply to currently focussed message"""
- mid = self.get_selected_mid()
- newpos = self._tree.last_child_position(mid)
- if newpos is not None:
- newpos = self._sanitize_position((newpos,))
- self.body.set_focus(newpos)
+ """move focus to last reply to currently focused message"""
+ msg = self.get_selected_message()
+ if len(msg.replies) > 0:
+ new_focus = self.thread.message_list.index(msg.replies[-1])
+ self.set_focus(new_focus)
def focus_next_sibling(self):
"""focus next sibling of currently focussed message in thread tree"""
- mid = self.get_selected_mid()
- newpos = self._tree.next_sibling_position(mid)
- if newpos is not None:
- newpos = self._sanitize_position((newpos,))
- self.body.set_focus(newpos)
+ pos_next = self.get_selected_message_position() + 1
+ depth = self.get_selected_message().depth
+ if (pos_next < self.thread.total_messages and
+ self.thread.message_list[pos_next].depth == depth):
+ self.set_focus(pos_next)
def focus_prev_sibling(self):
"""
focus previous sibling of currently focussed message in thread tree
"""
- mid = self.get_selected_mid()
- localroot = self._sanitize_position((mid,))
- if localroot == self.get_focus()[1]:
- newpos = self._tree.prev_sibling_position(mid)
- if newpos is not None:
- newpos = self._sanitize_position((newpos,))
- else:
- newpos = localroot
- if newpos is not None:
- self.body.set_focus(newpos)
+ pos_next = self.get_selected_message_position() - 1
+ depth = self.get_selected_message().depth
+ if (pos_next < self.thread.total_messages and
+ self.thread.message_list[pos_next].depth == depth):
+ self.set_focus(pos_next)
def focus_next(self):
"""focus next message in depth first order"""
- mid = self.get_selected_mid()
- newpos = self._tree.next_position(mid)
- if newpos is not None:
- newpos = self._sanitize_position((newpos,))
- self.body.set_focus(newpos)
+ next_focus = self.get_selected_message_position() + 1
+ if next_focus >= 0 and next_focus < self.thread.total_messages:
+ self.set_focus(next_focus)
def focus_prev(self):
"""focus previous message in depth first order"""
- mid = self.get_selected_mid()
- localroot = self._sanitize_position((mid,))
- if localroot == self.get_focus()[1]:
- newpos = self._tree.prev_position(mid)
- if newpos is not None:
- newpos = self._sanitize_position((newpos,))
- else:
- newpos = localroot
- if newpos is not None:
- self.body.set_focus(newpos)
+ next_focus = self.get_selected_message_position() - 1
+ if next_focus >= 0 and next_focus < self.thread.total_messages:
+ self.set_focus(next_focus)
- def focus_property(self, prop, direction):
+ def _focus_property(self, prop, direction):
"""does a walk in the given direction and focuses the
- first message tree that matches the given property"""
- newpos = self.get_selected_mid()
- newpos = direction(newpos)
- while newpos is not None:
- MT = self._tree[newpos]
- if prop(MT):
- newpos = self._sanitize_position((newpos,))
- self.body.set_focus(newpos)
+ first message that matches the given property"""
+ cur_pos = self.get_selected_message_position()
+
+ if direction > 0:
+ walk = range(cur_pos + 1, self.thread.total_messages)
+ else:
+ walk = reversed(range(0, cur_pos))
+
+ for pos in walk:
+ if prop(self._msg_walker[pos]):
+ self.set_focus(pos)
break
- newpos = direction(newpos)
def focus_next_matching(self, querystring):
"""focus next matching message in depth first order"""
- self.focus_property(lambda x: x._message.matches(querystring),
- self._tree.next_position)
+ self._focus_property(lambda x: x.get_message().matches(querystring), 1)
def focus_prev_matching(self, querystring):
"""focus previous matching message in depth first order"""
- self.focus_property(lambda x: x._message.matches(querystring),
- self._tree.prev_position)
+ self._focus_property(lambda x: x.get_message().matches(querystring), -1)
def focus_next_unfolded(self):
"""focus next unfolded message in depth first order"""
- self.focus_property(lambda x: not x.is_collapsed(x.root),
- self._tree.next_position)
+ self._focus_property(lambda x: x.display_content, 1)
def focus_prev_unfolded(self):
"""focus previous unfolded message in depth first order"""
- self.focus_property(lambda x: not x.is_collapsed(x.root),
- self._tree.prev_position)
+ self._focus_property(lambda x: x.display_content, -1)
def expand(self, msgpos):
"""expand message at given position"""
- MT = self._tree[msgpos]
- MT.expand(MT.root)
-
- def messagetree_at_position(self, pos):
- """get :class:`MessageTree` for given position"""
- return self._tree[pos[0]]
+ self.body.body[msgpos].expand()
def expand_all(self):
"""expand all messages in thread"""
- for MT in self.messagetrees():
- MT.expand(MT.root)
+ for msg in self.message_widgets():
+ msg.expand()
def collapse(self, msgpos):
"""collapse message at given position"""
- MT = self._tree[msgpos]
- MT.collapse(MT.root)
+ self.body.body[msgpos].collapse()
self.focus_selected_message()
def collapse_all(self):
"""collapse all messages in thread"""
- for MT in self.messagetrees():
- MT.collapse(MT.root)
+ for msg in self.message_widgets():
+ msg.collapse()
self.focus_selected_message()
def unfold_matching(self, querystring, focus_first=True):
@@ -289,14 +220,14 @@ class ThreadBuffer(Buffer):
:param focus_first: set the focus to the first matching message
:type focus_first: bool
"""
+ focus_set = False
first = None
- for MT in self.messagetrees():
- msg = MT._message
+ for pos, msg_wgt in enumerate(self.message_widgets()):
+ msg = msg_wgt.get_message()
if msg.matches(querystring):
- MT.expand(MT.root)
- if first is None:
- first = (self._tree.position_of_messagetree(MT), MT.root)
- self.body.set_focus(first)
+ msg_wgt.expand()
+ if focus_first and not focus_set:
+ self.set_focus(pos)
+ focus_set = True
else:
- MT.collapse(MT.root)
- self.body.refresh()
+ msg_wgt.collapse()