summaryrefslogtreecommitdiff
path: root/alot/widgets.py
diff options
context:
space:
mode:
Diffstat (limited to 'alot/widgets.py')
-rw-r--r--alot/widgets.py738
1 files changed, 0 insertions, 738 deletions
diff --git a/alot/widgets.py b/alot/widgets.py
deleted file mode 100644
index 16d4b53b..00000000
--- a/alot/widgets.py
+++ /dev/null
@@ -1,738 +0,0 @@
-# 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
-import urwid
-import logging
-
-from settings import settings
-from alot.helper import shorten_author_string
-from alot.helper import tag_cmp
-from alot.helper import string_decode
-import alot.db.message as message
-from alot.db.attachment import Attachment
-from alot.db.utils import decode_header
-
-
-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)
-
-
-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)
-
-
-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
-
-
-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'])
-
-
-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 != 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 != None:
- self.callback(self.select)
- elif key == 'cancel' and self.cancel != 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 == 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 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 != None:
- rep += " (%s)" % date
- return rep
-
- def selectable(self):
- return True
-
- def keypress(self, size, key):
- return key
-
-
-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 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)
-
-
-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