summaryrefslogtreecommitdiff
path: root/alot/widgets
diff options
context:
space:
mode:
authorAnton Khirnov <anton@khirnov.net>2020-04-26 18:07:13 +0200
committerAnton Khirnov <anton@khirnov.net>2020-05-05 11:24:28 +0200
commita6327a79a867c72f4ba764e7e5bf34440e8917a0 (patch)
treefea384f4ae18e9913a2f3b540171565ecaa6e1e3 /alot/widgets
parenta9e8f3271055740c9b96bb83483a09381f15312b (diff)
widgets/thread: turn the body into a tree of _MIMEPartWidget subclasses
Rather than an ad-hoc collection of widgets. This will allow folding body parts (such as quotes).
Diffstat (limited to 'alot/widgets')
-rw-r--r--alot/widgets/thread.py195
1 files changed, 108 insertions, 87 deletions
diff --git a/alot/widgets/thread.py b/alot/widgets/thread.py
index 09dd15fe..b410ff14 100644
--- a/alot/widgets/thread.py
+++ b/alot/widgets/thread.py
@@ -70,70 +70,19 @@ class MessageSummaryWidget(urwid.WidgetWrap):
def keypress(self, size, key):
return key
-class _MessageBodyWidget(urwid.WidgetWrap):
- _QUOTE_CHARS = '>|:}#'
- _QUOTE_REGEX = '(([ \t]*[{quote_chars}])+)'.format(quote_chars = _QUOTE_CHARS)
+class _MIMEPartWidget(urwid.WidgetWrap):
- _prefer_plaintext = None
-
- def __init__(self, mime_tree):
- self._prefer_plaintext = settings.get('prefer_plaintext')
- body_wgt = self._build_body(mime_tree);
- if body_wgt is None:
- body_wgt = urwid.Text('<<Message body is empty>>>', alignt = 'center')
+ # when not None, a list of child _MIMEPartWidget subclasses
+ _children = None
+ def __init__(self, body_wgt, children = None):
+ self._children = children
super().__init__(body_wgt)
- def _build_body(self, mime_tree):
- # handle encrypted/signed parts
- if mime_tree.is_signed or mime_tree.is_encrypted:
- return self._handle_crypt(mime_tree)
-
- if mime_tree.children is not None:
- # multipart MIME parts
- if mime_tree.is_alternative:
- return self._handle_alternative(mime_tree)
-
- return self._handle_mixed(mime_tree)
-
- # no children - this is a leaf node
- # skip attachment parts
- if mime_tree.attachment:
- return None
-
- # try rendering the message
- text = mime_tree.render_str()
- if text is not None:
- return self._render_plain_text(text)
-
- return urwid.Text('Undisplayable "%s" MIME part.' % mime_tree.content_type,
- align = 'center')
+class _CryptPartWidget(_MIMEPartWidget):
+ def __init__(self, mime_tree, alternative_pref):
+ children = None
- def _handle_mixed(self, mime_tree):
- children = []
- for child in mime_tree.children:
- ch = self._build_body(child)
- if ch is not None:
- children.append(ch)
-
- if len(children) > 0:
- return urwid.Pile(children)
-
- def _handle_alternative(self, mime_tree):
- # TODO: switching between alternatives
- preferred = 'plain' if self._prefer_plaintext else 'html'
-
- child = None
- for ch in mime_tree.children:
- if ch.content_subtype == preferred:
- child = ch
- break
- if child is None:
- child = mime_tree.children[0]
-
- return self._build_body(child)
-
- def _handle_crypt(self, mime_tree):
# handle broken crypto parts where we could not get the payload
if (mime_tree.children is None or
len(mime_tree.children) != 1):
@@ -142,41 +91,52 @@ class _MessageBodyWidget(urwid.WidgetWrap):
if mime_tree.crypt_error:
text += ': ' + mime_tree.crypt_error
- return urwid.Text(text, align = 'center')
+ body_wgt = urwid.Text(text, align = 'center')
+ else:
+ text_parts = []
- text_parts = []
+ if mime_tree.is_encrypted and mime_tree.is_signed:
+ desc = 'encrypted+signed'
+ if mime_tree.is_encrypted:
+ desc = 'encrypted'
+ elif mime_tree.is_signed:
+ desc = 'signed'
- if mime_tree.is_encrypted and mime_tree.is_signed:
- desc = 'encrypted+signed'
- if mime_tree.is_encrypted:
- desc = 'encrypted'
- elif mime_tree.is_signed:
- desc = 'signed'
+ text_parts.append(desc + ' MIME part')
- text_parts.append(desc + ' MIME part')
+ if mime_tree.is_signed and mime_tree.sig_valid:
+ if mime_tree.sig_trusted:
+ trust = 'trusted'
+ attr_name = 'crypt_trusted'
+ else:
+ trust = 'untrusted'
+ attr_name = 'crypt_untrusted'
- if mime_tree.is_signed and mime_tree.sig_valid:
- if mime_tree.sig_trusted:
- trust = 'trusted'
- attr_name = 'crypt_trusted'
+ t = 'valid %s signature from "%s"' % (trust, mime_tree.signer_id)
+ text_parts.append(t)
else:
- trust = 'untrusted'
- attr_name = 'crypt_untrusted'
+ attr_name = 'crypt_unsigned'
- t = 'valid %s signature from "%s"' % (trust, mime_tree.signer_id)
- text_parts.append(t)
- else:
- attr_name = 'crypt_unsigned'
+ if mime_tree.crypt_error:
+ text_parts.append('crypto processing error: ' + mime_tree.crypt_error)
+ attr_name = 'crypt_invalid'
+
+ child = _render_mime_tree(mime_tree.children[0], alternative_pref)
+ attr = settings.get_theming_attribute('thread', attr_name)
+ body_wgt = urwid.AttrMap(urwid.LineBox(child, title = ':'.join(text_parts)), attr)
+ children = [child]
+
+ super().__init__(body_wgt, children)
- if mime_tree.crypt_error:
- text_parts.append('crypto processing error: ' + mime_tree.crypt_error)
- attr_name = 'crypt_invalid'
+class _AltMixedPart(_MIMEPartWidget):
+ def __init__(self, children):
+ super().__init__(urwid.Pile(children), children)
- body = self._build_body(mime_tree.children[0])
- attr = settings.get_theming_attribute('thread', attr_name)
- return urwid.AttrMap(urwid.LineBox(body, title = ':'.join(text_parts)), attr)
+class _TextPart(_MIMEPartWidget):
+ _QUOTE_CHARS = '>|:}#'
+ _QUOTE_REGEX = '(([ \t]*[{quote_chars}])+)'.format(quote_chars = _QUOTE_CHARS)
- def _render_plain_text(self, text):
+ def __init__(self, text):
attr_text = settings.get_theming_attribute('thread', 'body')
attrs_quote = settings.get_quote_theming()
@@ -202,7 +162,60 @@ class _MessageBodyWidget(urwid.WidgetWrap):
attr = attrs_quote[(level - 1) % len(attrs_quote)]
line_widgets.append(urwid.Text((attr, line)))
- return urwid.Pile(line_widgets)
+ super().__init__(urwid.Pile(line_widgets))
+
+class _EmptyMessageWidget(_MIMEPartWidget):
+ def __init__(self):
+ body_wgt = urwid.Text('<<Message body is empty>>>', align = 'center')
+ super().__init__(body_wgt)
+
+def _handle_mixed(mime_tree, alternative_pref):
+ children = []
+ for child in mime_tree.children:
+ ch = _render_mime_tree(child, alternative_pref)
+ if ch is not None:
+ children.append(ch)
+
+ if len(children) > 0:
+ return _AltMixedPart(children)
+
+def _handle_alternative(mime_tree, alternative_pref):
+ # TODO: switching between alternatives
+ child = None
+ for ch in mime_tree.children:
+ if ch.content_type == alternative_pref:
+ child = ch
+ break
+ if child is None:
+ child = mime_tree.children[0]
+
+ return _render_mime_tree(child, alternative_pref)
+
+def _render_mime_tree(mime_tree, alternative_pref):
+ # handle encrypted/signed parts
+ if mime_tree.is_signed or mime_tree.is_encrypted:
+ return _CryptPartWidget(mime_tree, alternative_pref)
+
+ if mime_tree.children is not None:
+ # multipart MIME parts
+ if mime_tree.is_alternative:
+ return _handle_alternative(mime_tree, alternative_pref)
+
+ return _AltMixedPart(mime_tree, alternative_pref)
+
+ # no children - this is a leaf node
+ # skip attachment parts
+ if mime_tree.attachment:
+ return None
+
+ # try rendering the message
+ text = mime_tree.render_str()
+ if text is not None:
+ return _TextPart(text)
+
+ body_wgt = urwid.Text('Undisplayable "%s" MIME part.' % mime_tree.content_type,
+ align = 'center')
+ return _MIMEPartWidget(body_wgt)
class HeadersWidget(urwid.WidgetWrap):
"""
@@ -308,11 +321,19 @@ class MessageWidget(urwid.WidgetWrap):
"""
self._message = message
+ if settings.get('prefer_plaintext'):
+ alternative_pref = 'text/plain'
+ else:
+ alternative_pref = 'text/html'
+
self._headers_wgt = self._get_headers()
self._source_wgt = _RawMessageWidget(message.as_bytes())
- self._body_wgt = _MessageBodyWidget(message.body)
self._attach_wgt = self._get_attachments()
+ self._body_wgt = _render_mime_tree(message.body, alternative_pref)
+ if self._body_wgt is None:
+ self._body_wgt = _EmptyMessageWidget()
+
super().__init__(urwid.ListBox(urwid.SimpleListWalker([])))
self.display_all_headers = False