summaryrefslogtreecommitdiff
path: root/alot/widgets
diff options
context:
space:
mode:
Diffstat (limited to 'alot/widgets')
-rw-r--r--alot/widgets/__init__.py0
-rw-r--r--alot/widgets/bufferlist.py29
-rw-r--r--alot/widgets/globals.py203
-rw-r--r--alot/widgets/search.py186
-rw-r--r--alot/widgets/thread.py301
-rw-r--r--alot/widgets/utils.py64
6 files changed, 783 insertions, 0 deletions
diff --git a/alot/widgets/__init__.py b/alot/widgets/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/alot/widgets/__init__.py
diff --git a/alot/widgets/bufferlist.py b/alot/widgets/bufferlist.py
new file mode 100644
index 00000000..0ab315c5
--- /dev/null
+++ b/alot/widgets/bufferlist.py
@@ -0,0 +1,29 @@
+# Copyright (C) 2011-2012 Patrick Totzke <patricktotzke@gmail.com>
+# This file is released under the GNU GPL, version 3 or a later revision.
+# For further details see the COPYING file
+
+"""
+Widgets specific to Bufferlist mode
+"""
+import urwid
+
+
+class BufferlineWidget(urwid.Text):
+ """
+ selectable text widget that represents a :class:`~alot.buffers.Buffer`
+ in the :class:`~alot.buffers.BufferlistBuffer`.
+ """
+
+ def __init__(self, buffer):
+ self.buffer = buffer
+ line = buffer.__str__()
+ urwid.Text.__init__(self, line, wrap='clip')
+
+ def selectable(self):
+ return True
+
+ def keypress(self, size, key):
+ return key
+
+ def get_buffer(self):
+ return self.buffer
diff --git a/alot/widgets/globals.py b/alot/widgets/globals.py
new file mode 100644
index 00000000..96fec312
--- /dev/null
+++ b/alot/widgets/globals.py
@@ -0,0 +1,203 @@
+# Copyright (C) 2011-2012 Patrick Totzke <patricktotzke@gmail.com>
+# This file is released under the GNU GPL, version 3 or a later revision.
+# For further details see the COPYING file
+
+"""
+This contains alot-specific :ref:`urwid.Widgets` used in more than one mode.
+"""
+import urwid
+
+from alot.helper import string_decode
+from alot.settings import settings
+from alot.db.attachment import Attachment
+
+
+class AttachmentWidget(urwid.WidgetWrap):
+ """
+ one-line summary of an :class:`~alot.db.attachment.Attachment`.
+ """
+ def __init__(self, attachment, selectable=True):
+ self._selectable = selectable
+ self.attachment = attachment
+ if not isinstance(attachment, Attachment):
+ self.attachment = Attachment(self.attachment)
+ att = settings.get_theming_attribute('thread', 'attachment')
+ focus_att = settings.get_theming_attribute('thread',
+ 'attachment_focus')
+ widget = urwid.AttrMap(urwid.Text(self.attachment.__str__()),
+ att, focus_att)
+ urwid.WidgetWrap.__init__(self, widget)
+
+ def get_attachment(self):
+ return self.attachment
+
+ def selectable(self):
+ return self._selectable
+
+ def keypress(self, size, key):
+ return key
+
+
+class ChoiceWidget(urwid.Text):
+ def __init__(self, choices, callback, cancel=None, select=None):
+ self.choices = choices
+ self.callback = callback
+ self.cancel = cancel
+ self.select = select
+
+ items = []
+ for k, v in choices.items():
+ if v == select and select is not None:
+ items.append('[%s]:%s' % (k, v))
+ else:
+ items.append('(%s):%s' % (k, v))
+ urwid.Text.__init__(self, ' '.join(items))
+
+ def selectable(self):
+ return True
+
+ def keypress(self, size, key):
+ if key == 'select' and self.select is not None:
+ self.callback(self.select)
+ elif key == 'cancel' and self.cancel is not None:
+ self.callback(self.cancel)
+ elif key in self.choices:
+ self.callback(self.choices[key])
+ else:
+ return key
+
+
+class CompleteEdit(urwid.Edit):
+ def __init__(self, completer, on_exit, edit_text=u'',
+ history=None, **kwargs):
+ self.completer = completer
+ self.on_exit = on_exit
+ self.history = list(history) # we temporarily add stuff here
+ self.historypos = None
+
+ if not isinstance(edit_text, unicode):
+ edit_text = string_decode(edit_text)
+ self.start_completion_pos = len(edit_text)
+ self.completions = None
+ urwid.Edit.__init__(self, edit_text=edit_text, **kwargs)
+
+ def keypress(self, size, key):
+ # if we tabcomplete
+ if key in ['tab', 'shift tab'] and self.completer:
+ # if not already in completion mode
+ if not self.completions:
+ self.completions = [(self.edit_text, self.edit_pos)] + \
+ self.completer.complete(self.edit_text, self.edit_pos)
+ self.focus_in_clist = 1
+ else: # otherwise tab through results
+ if key == 'tab':
+ self.focus_in_clist += 1
+ else:
+ self.focus_in_clist -= 1
+ if len(self.completions) > 1:
+ ctext, cpos = self.completions[self.focus_in_clist %
+ len(self.completions)]
+ self.set_edit_text(ctext)
+ self.set_edit_pos(cpos)
+ else:
+ self.edit_pos += 1
+ if self.edit_pos >= len(self.edit_text):
+ self.edit_text += ' '
+ self.completions = None
+ elif key in ['up', 'down']:
+ if self.history:
+ if self.historypos is None:
+ self.history.append(self.edit_text)
+ self.historypos = len(self.history) - 1
+ if key == 'cursor up':
+ self.historypos = (self.historypos + 1) % len(self.history)
+ else:
+ self.historypos = (self.historypos - 1) % len(self.history)
+ self.set_edit_text(self.history[self.historypos])
+ elif key == 'select':
+ self.on_exit(self.edit_text)
+ elif key == 'cancel':
+ self.on_exit(None)
+ elif key == 'ctrl a':
+ self.set_edit_pos(0)
+ elif key == 'ctrl e':
+ self.set_edit_pos(len(self.edit_text))
+ else:
+ result = urwid.Edit.keypress(self, size, key)
+ self.completions = None
+ return result
+
+
+class HeadersList(urwid.WidgetWrap):
+ """ renders a pile of header values as key/value list """
+ def __init__(self, headerslist, key_attr, value_attr):
+ self.headers = headerslist
+ self.key_attr = key_attr
+ self.value_attr = value_attr
+ pile = urwid.Pile(self._build_lines(headerslist))
+ att = settings.get_theming_attribute('thread', 'header')
+ pile = urwid.AttrMap(pile, att)
+ urwid.WidgetWrap.__init__(self, pile)
+
+ def __str__(self):
+ return str(self.headers)
+
+ def _build_lines(self, lines):
+ max_key_len = 1
+ headerlines = []
+ #calc max length of key-string
+ for key, value in lines:
+ if len(key) > max_key_len:
+ max_key_len = len(key)
+ for key, value in lines:
+ ##todo : even/odd
+ keyw = ('fixed', max_key_len + 1,
+ urwid.Text((self.key_attr, key)))
+ valuew = urwid.Text((self.value_attr, value))
+ line = urwid.Columns([keyw, valuew])
+ headerlines.append(line)
+ return headerlines
+
+
+class TagWidget(urwid.AttrMap):
+ """
+ text widget that renders a tagstring.
+
+ It looks up the string it displays in the `tags` section
+ of the config as well as custom theme settings for its tag.
+ """
+ def __init__(self, tag, fallback_normal=None, fallback_focus=None):
+ self.tag = tag
+ representation = settings.get_tagstring_representation(tag,
+ fallback_normal,
+ fallback_focus)
+ self.translated = representation['translated']
+ self.hidden = self.translated == ''
+ self.txt = urwid.Text(self.translated, wrap='clip')
+ normal_att = representation['normal']
+ focus_att = representation['focussed']
+ self.attmaps = {'normal': normal_att, 'focus': focus_att}
+ urwid.AttrMap.__init__(self, self.txt, normal_att, focus_att)
+
+ def set_map(self, attrstring):
+ self.set_attr_map({None: self.attmaps[attrstring]})
+
+ def width(self):
+ # evil voodoo hotfix for double width chars that may
+ # lead e.g. to strings with length 1 that need width 2
+ return self.txt.pack()[0]
+
+ def selectable(self):
+ return True
+
+ def keypress(self, size, key):
+ return key
+
+ def get_tag(self):
+ return self.tag
+
+ def set_focussed(self):
+ self.set_attr_map(self.attmap['focus'])
+
+ def set_unfocussed(self):
+ self.set_attr_map(self.attmap['normal'])
diff --git a/alot/widgets/search.py b/alot/widgets/search.py
new file mode 100644
index 00000000..6f8ed126
--- /dev/null
+++ b/alot/widgets/search.py
@@ -0,0 +1,186 @@
+# Copyright (C) 2011-2012 Patrick Totzke <patricktotzke@gmail.com>
+# This file is released under the GNU GPL, version 3 or a later revision.
+# For further details see the COPYING file
+"""
+Widgets specific to search mode
+"""
+import urwid
+
+from alot.settings import settings
+from alot.helper import shorten_author_string
+from alot.helper import tag_cmp
+from alot.widgets.utils import AttrFlipWidget
+from alot.widgets.globals import TagWidget
+
+
+class ThreadlineWidget(urwid.AttrMap):
+ """
+ selectable line widget that represents a :class:`~alot.db.Thread`
+ in the :class:`~alot.buffers.SearchBuffer`.
+ """
+ def __init__(self, tid, dbman):
+ self.dbman = dbman
+ self.thread = dbman.get_thread(tid)
+ self.tag_widgets = []
+ self.display_content = settings.get('display_content_in_threadline')
+ self.structure = None
+ self.rebuild()
+ normal = self.structure['normal']
+ focussed = self.structure['focus']
+ urwid.AttrMap.__init__(self, self.columns, normal, focussed)
+
+ def _build_part(self, name, struct, minw, maxw, align):
+ def pad(string, shorten=None):
+ if maxw:
+ if len(string) > maxw:
+ if shorten:
+ string = shorten(string, maxw)
+ else:
+ string = string[:maxw]
+ if minw:
+ if len(string) < minw:
+ if align == 'left':
+ string = string.ljust(minw)
+ elif align == 'center':
+ string = string.center(minw)
+ else:
+ string = string.rjust(minw)
+ return string
+
+ part = None
+ width = None
+ if name == 'date':
+ newest = None
+ datestring = ''
+ if self.thread:
+ newest = self.thread.get_newest_date()
+ datestring = settings.represent_datetime(newest)
+ datestring = pad(datestring)
+ width = len(datestring)
+ part = AttrFlipWidget(urwid.Text(datestring), struct['date'])
+
+ elif name == 'mailcount':
+ if self.thread:
+ mailcountstring = "(%d)" % self.thread.get_total_messages()
+ else:
+ mailcountstring = "(?)"
+ datestring = pad(mailcountstring)
+ width = len(mailcountstring)
+ mailcount_w = AttrFlipWidget(urwid.Text(mailcountstring),
+ struct['mailcount'])
+ part = mailcount_w
+ elif name == 'authors':
+ if self.thread:
+ authors = self.thread.get_authors_string() or '(None)'
+ else:
+ authors = '(None)'
+ authorsstring = pad(authors, shorten_author_string)
+ authors_w = AttrFlipWidget(urwid.Text(authorsstring),
+ struct['authors'])
+ width = len(authorsstring)
+ part = authors_w
+
+ elif name == 'subject':
+ if self.thread:
+ subjectstring = self.thread.get_subject() or ' '
+ else:
+ subjectstring = ' '
+ # sanitize subject string:
+ subjectstring = subjectstring.replace('\n', ' ')
+ subjectstring = subjectstring.replace('\r', '')
+ subjectstring = pad(subjectstring)
+
+ subject_w = AttrFlipWidget(urwid.Text(subjectstring, wrap='clip'),
+ struct['subject'])
+ if subjectstring:
+ width = len(subjectstring)
+ part = subject_w
+
+ elif name == 'content':
+ if self.thread:
+ msgs = self.thread.get_messages().keys()
+ else:
+ msgs = []
+ # sort the most recent messages first
+ msgs.sort(key=lambda msg: msg.get_date(), reverse=True)
+ lastcontent = ' '.join([m.get_text_content() for m in msgs])
+ contentstring = pad(lastcontent.replace('\n', ' ').strip())
+ content_w = AttrFlipWidget(urwid.Text(
+ contentstring,
+ wrap='clip'),
+ struct['content'])
+ width = len(contentstring)
+ part = content_w
+ elif name == 'tags':
+ if self.thread:
+ fallback_normal = struct[name]['normal']
+ fallback_focus = struct[name]['focus']
+ tag_widgets = [TagWidget(t, fallback_normal, fallback_focus)
+ for t in self.thread.get_tags()]
+ tag_widgets.sort(tag_cmp,
+ lambda tag_widget: tag_widget.translated)
+ else:
+ tag_widgets = []
+ cols = []
+ length = -1
+ for tag_widget in tag_widgets:
+ if not tag_widget.hidden:
+ wrapped_tagwidget = tag_widget
+ tag_width = tag_widget.width()
+ cols.append(('fixed', tag_width, wrapped_tagwidget))
+ length += tag_width + 1
+ if cols:
+ part = urwid.Columns(cols, dividechars=1)
+ width = length
+ return width, part
+
+ def rebuild(self):
+ self.widgets = []
+ columns = []
+ self.structure = settings.get_threadline_theming(self.thread)
+ for partname in self.structure['parts']:
+ minw = maxw = None
+ width_tuple = self.structure[partname]['width']
+ if width_tuple is not None:
+ if width_tuple[0] == 'fit':
+ minw, maxw = width_tuple[1:]
+ align_mode = self.structure[partname]['alignment']
+ width, part = self._build_part(partname, self.structure,
+ minw, maxw, align_mode)
+ if part is not None:
+ if isinstance(part, urwid.Columns):
+ for w in part.widget_list:
+ self.widgets.append(w)
+ else:
+ self.widgets.append(part)
+
+ # compute width and align
+ if width_tuple[0] == 'weight':
+ columnentry = width_tuple + (part,)
+ else:
+ columnentry = ('fixed', width, part)
+ columns.append(columnentry)
+ self.columns = urwid.Columns(columns, dividechars=1)
+ self.original_widget = self.columns
+
+ def render(self, size, focus=False):
+ for w in self.widgets:
+ w.set_map('focus' if focus else 'normal')
+ return urwid.AttrMap.render(self, size, focus)
+
+ def selectable(self):
+ return True
+
+ def keypress(self, size, key):
+ return key
+
+ def get_thread(self):
+ return self.thread
+
+ def _get_theme(self, component, focus=False):
+ path = ['search', 'threadline', component]
+ if focus:
+ path.append('focus')
+ else:
+ path.append('normal')
+ return settings.get_theming_attribute(path)
diff --git a/alot/widgets/thread.py b/alot/widgets/thread.py
new file mode 100644
index 00000000..c6d399e8
--- /dev/null
+++ b/alot/widgets/thread.py
@@ -0,0 +1,301 @@
+# Copyright (C) 2011-2012 Patrick Totzke <patricktotzke@gmail.com>
+# This file is released under the GNU GPL, version 3 or a later revision.
+# For further details see the COPYING file
+"""
+Widgets specific to thread mode
+"""
+import urwid
+import logging
+
+from alot.settings import settings
+from alot.db.utils import decode_header
+import alot.db.message as message
+from alot.helper import tag_cmp
+from alot.widgets.globals import HeadersList
+from alot.widgets.globals import TagWidget
+from alot.widgets.globals import AttachmentWidget
+
+
+class MessageWidget(urwid.WidgetWrap):
+ """
+ Flow widget that renders a :class:`~alot.db.message.Message`.
+ """
+ #TODO: atm this is heavily bent to work nicely with ThreadBuffer to display
+ #a tree structure. A better way would be to keep this widget simple
+ #(subclass urwid.Pile) and use urwids new Tree widgets
+ def __init__(self, message, even=False, folded=True, raw=False,
+ all_headers=False, depth=0, bars_at=[]):
+ """
+ :param message: the message to display
+ :type message: alot.db.Message
+ :param even: use messagesummary_even theme for summary
+ :type even: bool
+ :param folded: fold message initially
+ :type folded: bool
+ :param raw: show message source initially
+ :type raw: bool
+ :param all_headers: show all headers initially
+ :type all_headers: bool
+ :param depth: number of characters to shift content to the right
+ :type depth: int
+ :param bars_at: defines for each column of the indentation whether to
+ use a vertical bar instead of a space.
+ :type bars_at: list(bool)
+ """
+ self.message = message
+ self.mail = self.message.get_email()
+
+ self.depth = depth
+ self.bars_at = bars_at
+ self.even = even
+ self.folded = folded
+ self.show_raw = raw
+ self.show_all_headers = all_headers
+
+ # define subwidgets that will be created on demand
+ self.sumline = None
+ self.headerw = None
+ self.attachmentw = None
+ self.bodyw = None
+ self.sourcew = None
+
+ # set available and to be displayed headers
+ self._all_headers = list(set(self.mail.keys()))
+ displayed = settings.get('displayed_headers')
+ self._filtered_headers = [k for k in displayed if k in self.mail]
+ self._displayed_headers = None
+
+ bars = settings.get_theming_attribute('thread', 'arrow_bars')
+ self.arrow_bars_att = bars
+ heads = settings.get_theming_attribute('thread', 'arrow_heads')
+ self.arrow_heads_att = heads
+ logging.debug(self.arrow_heads_att)
+
+ self.rebuild() # this will build self.pile
+ urwid.WidgetWrap.__init__(self, self.pile)
+
+ def get_focus(self):
+ return self.pile.get_focus()
+
+ def rebuild(self):
+ self.sumline = self._build_sum_line()
+ if not self.folded: # only if already unfolded
+ self.displayed_list = [self.sumline]
+ if self.show_raw:
+ srcw = self._get_source_widget()
+ self.displayed_list.append(srcw)
+ else:
+ hw = self._get_header_widget()
+ aw = self._get_attachment_widget()
+ bw = self._get_body_widget()
+ if hw:
+ self.displayed_list.append(hw)
+ if aw:
+ self.displayed_list.append(aw)
+ self.displayed_list.append(bw)
+ else:
+ self.displayed_list = [self.sumline]
+ self.pile = urwid.Pile(self.displayed_list)
+ self._w = self.pile
+
+ def _build_sum_line(self):
+ """creates/returns the widget that displays the summary line."""
+ self.sumw = MessageSummaryWidget(self.message, even=self.even)
+ cols = []
+ bc = list() # box_columns
+ if self.depth > 1:
+ bc.append(0)
+ spacer = self._get_spacer(self.bars_at[1:-1])
+ cols.append(spacer)
+ if self.depth > 0:
+ if self.bars_at[-1]:
+ arrowhead = [(self.arrow_bars_att, u'\u251c'),
+ (self.arrow_heads_att, u'\u25b6')]
+ else:
+ arrowhead = [(self.arrow_bars_att, u'\u2514'),
+ (self.arrow_heads_att, u'\u25b6')]
+ cols.append(('fixed', 2, urwid.Text(arrowhead)))
+ cols.append(self.sumw)
+ line = urwid.Columns(cols, box_columns=bc)
+ return line
+
+ def _get_header_widget(self):
+ """creates/returns the widget that displays the mail header"""
+ all_shown = (self._all_headers == self._displayed_headers)
+
+ if self.headerw and (self.show_all_headers == all_shown):
+ return self.headerw
+
+ if self.show_all_headers:
+ self._displayed_headers = self._all_headers
+ else:
+ self._displayed_headers = self._filtered_headers
+
+ mail = self.message.get_email()
+ # normalize values if only filtered list is shown
+ norm = not (self._displayed_headers == self._all_headers)
+
+ #build lines
+ lines = []
+ for key in self._displayed_headers:
+ if key in mail:
+ if key.lower() in ['cc', 'bcc', 'to']:
+ values = mail.get_all(key)
+ values = [decode_header(v, normalize=norm) for v in values]
+ lines.append((key, ', '.join(values)))
+ else:
+ for value in mail.get_all(key):
+ dvalue = decode_header(value, normalize=norm)
+ lines.append((key, dvalue))
+
+ key_att = settings.get_theming_attribute('thread', 'header_key')
+ value_att = settings.get_theming_attribute('thread', 'header_value')
+ cols = [HeadersList(lines, key_att, value_att)]
+ bc = list()
+ if self.depth:
+ cols.insert(0, self._get_spacer(self.bars_at[1:]))
+ bc.append(0)
+ cols.insert(1, self._get_arrowhead_aligner())
+ bc.append(1)
+ self.headerw = urwid.Columns(cols, box_columns=bc)
+ return self.headerw
+
+ def _get_attachment_widget(self):
+ if self.message.get_attachments() and not self.attachmentw:
+ lines = []
+ for a in self.message.get_attachments():
+ cols = [AttachmentWidget(a)]
+ bc = list()
+ if self.depth:
+ cols.insert(0, self._get_spacer(self.bars_at[1:]))
+ bc.append(0)
+ cols.insert(1, self._get_arrowhead_aligner())
+ bc.append(1)
+ lines.append(urwid.Columns(cols, box_columns=bc))
+ self.attachmentw = urwid.Pile(lines)
+ return self.attachmentw
+
+ def _get_body_widget(self):
+ """creates/returns the widget that displays the mail body"""
+ if not self.bodyw:
+ cols = [MessageBodyWidget(self.message.get_email())]
+ bc = list()
+ if self.depth:
+ cols.insert(0, self._get_spacer(self.bars_at[1:]))
+ bc.append(0)
+ cols.insert(1, self._get_arrowhead_aligner())
+ bc.append(1)
+ self.bodyw = urwid.Columns(cols, box_columns=bc)
+ return self.bodyw
+
+ def _get_source_widget(self):
+ """creates/returns the widget that displays the mail body"""
+ if not self.sourcew:
+ cols = [urwid.Text(self.message.get_email().as_string())]
+ bc = list()
+ if self.depth:
+ cols.insert(0, self._get_spacer(self.bars_at[1:]))
+ bc.append(0)
+ cols.insert(1, self._get_arrowhead_aligner())
+ bc.append(1)
+ self.sourcew = urwid.Columns(cols, box_columns=bc)
+ return self.sourcew
+
+ def _get_spacer(self, bars_at):
+ prefixchars = []
+ length = len(bars_at)
+ for b in bars_at:
+ if b:
+ c = u'\u2502'
+ else:
+ c = ' '
+ prefixchars.append(('fixed', 1, urwid.SolidFill(c)))
+
+ spacer = urwid.Columns(prefixchars, box_columns=range(length))
+ spacer = urwid.AttrMap(spacer, self.arrow_bars_att)
+ return ('fixed', length, spacer)
+
+ def _get_arrowhead_aligner(self):
+ if self.message.has_replies():
+ aligner = u'\u2502'
+ else:
+ aligner = ' '
+ aligner = urwid.SolidFill(aligner)
+ return ('fixed', 1, urwid.AttrMap(aligner, self.arrow_bars_att))
+
+ def selectable(self):
+ return True
+
+ def keypress(self, size, key):
+ return self.pile.keypress(size, key)
+
+ def get_message(self):
+ """get contained :class`~alot.db.message.Message`"""
+ return self.message
+
+ def get_email(self):
+ """get contained :class:`email <email.Message>`"""
+ return self.message.get_email()
+
+
+class MessageSummaryWidget(urwid.WidgetWrap):
+ """
+ one line summary of a :class:`~alot.db.message.Message`.
+ """
+
+ def __init__(self, message, even=True):
+ """
+ :param message: a message
+ :type message: alot.db.Message
+ :param even: even entry in a pile of messages? Used for theming.
+ :type even: bool
+ """
+ self.message = message
+ self.even = even
+ if even:
+ attr = settings.get_theming_attribute('thread', 'summary', 'even')
+ else:
+ attr = settings.get_theming_attribute('thread', 'summary', 'odd')
+ focus_att = settings.get_theming_attribute('thread', 'summary',
+ 'focus')
+ cols = []
+
+ sumstr = self.__str__()
+ txt = urwid.Text(sumstr)
+ cols.append(txt)
+
+ thread_tags = message.get_thread().get_tags(intersection=True)
+ outstanding_tags = set(message.get_tags()).difference(thread_tags)
+ tag_widgets = [TagWidget(t, attr, focus_att) for t in outstanding_tags]
+ tag_widgets.sort(tag_cmp, lambda tag_widget: tag_widget.translated)
+ for tag_widget in tag_widgets:
+ if not tag_widget.hidden:
+ cols.append(('fixed', tag_widget.width(), tag_widget))
+ line = urwid.AttrMap(urwid.Columns(cols, dividechars=1), attr,
+ focus_att)
+ urwid.WidgetWrap.__init__(self, line)
+
+ def __str__(self):
+ author, address = self.message.get_author()
+ date = self.message.get_datestring()
+ rep = author if author != '' else address
+ if date is not None:
+ rep += " (%s)" % date
+ return rep
+
+ def selectable(self):
+ return True
+
+ def keypress(self, size, key):
+ return key
+
+
+class MessageBodyWidget(urwid.AttrMap):
+ """
+ displays printable parts of an email
+ """
+
+ def __init__(self, msg):
+ bodytxt = message.extract_body(msg)
+ att = settings.get_theming_attribute('thread', 'body')
+ urwid.AttrMap.__init__(self, urwid.Text(bodytxt), att)
diff --git a/alot/widgets/utils.py b/alot/widgets/utils.py
new file mode 100644
index 00000000..b50b2db9
--- /dev/null
+++ b/alot/widgets/utils.py
@@ -0,0 +1,64 @@
+# Copyright (C) 2011-2012 Patrick Totzke <patricktotzke@gmail.com>
+# This file is released under the GNU GPL, version 3 or a later revision.
+# For further details see the COPYING file
+
+"""
+Utility Widgets not specific to alot
+"""
+import urwid
+import logging
+
+
+class AttrFlipWidget(urwid.AttrMap):
+ """
+ An AttrMap that can remember attributes to set
+ """
+ def __init__(self, w, maps, init_map='normal'):
+ self.maps = maps
+ urwid.AttrMap.__init__(self, w, maps[init_map])
+
+ def set_map(self, attrstring):
+ self.set_attr_map({None: self.maps[attrstring]})
+
+
+class DialogBox(urwid.WidgetWrap):
+ def __init__(self, body, title, bodyattr=None, titleattr=None):
+ self.body = urwid.LineBox(body)
+ self.title = urwid.Text(title)
+ if titleattr is not None:
+ self.title = urwid.AttrMap(self.title, titleattr)
+ if bodyattr is not None:
+ self.body = urwid.AttrMap(self.body, bodyattr)
+
+ box = urwid.Overlay(self.title, self.body,
+ align='center',
+ valign='top',
+ width=len(title),
+ height=None,
+ )
+ urwid.WidgetWrap.__init__(self, box)
+
+ def selectable(self):
+ return self.body.selectable()
+
+ def keypress(self, size, key):
+ return self.body.keypress(size, key)
+
+
+class CatchKeyWidgetWrap(urwid.WidgetWrap):
+ def __init__(self, widget, key, on_catch, relay_rest=True):
+ urwid.WidgetWrap.__init__(self, widget)
+ self.key = key
+ self.relay = relay_rest
+ self.on_catch = on_catch
+
+ def selectable(self):
+ return True
+
+ def keypress(self, size, key):
+ logging.debug('CATCH KEY: %s' % key)
+ logging.debug('relay: %s' % self.relay)
+ if key == self.key:
+ self.on_catch()
+ elif self._w.selectable() and self.relay:
+ return self._w.keypress(size, key)