diff options
author | Anton Khirnov <anton@khirnov.net> | 2020-04-26 18:07:13 +0200 |
---|---|---|
committer | Anton Khirnov <anton@khirnov.net> | 2020-05-05 11:24:28 +0200 |
commit | a6327a79a867c72f4ba764e7e5bf34440e8917a0 (patch) | |
tree | fea384f4ae18e9913a2f3b540171565ecaa6e1e3 /alot/widgets | |
parent | a9e8f3271055740c9b96bb83483a09381f15312b (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.py | 195 |
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 |