""" This file is part of alot. Alot is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Notmuch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with notmuch. If not, see . Copyright (C) 2011 Patrick Totzke """ import email import urwid import tempfile import os from datetime import datetime from settings import config from settings import get_mime_handler from helper import shorten from helper import pretty_datetime from helper import cmd_output class ThreadlineWidget(urwid.AttrMap): def __init__(self, tid, dbman): self.dbman = dbman self.thread = dbman.get_thread(tid) self.rebuild() urwid.AttrMap.__init__(self, self.columns, 'threadline', 'threadline_focus') def rebuild(self): cols = [] datestring = pretty_datetime(self.thread.get_newest_date()).rjust(10) self.date_w = urwid.AttrMap(urwid.Text(datestring), 'threadline_date') cols.append(('fixed', len(datestring), self.date_w)) mailcountstring = "(%d)" % self.thread.get_total_messages() self.mailcount_w = urwid.AttrMap(urwid.Text(mailcountstring), 'threadline_mailcount') cols.append(('fixed', len(mailcountstring), self.mailcount_w)) tagsstring = " ".join(self.thread.get_tags()) self.tags_w = urwid.AttrMap(urwid.Text(tagsstring), 'threadline_tags') if tagsstring: cols.append(('fixed', len(tagsstring), self.tags_w)) authors = self.thread.get_authors() or '(None)' maxlength = config.getint('general', 'authors_maxlength') authorsstring = shorten(authors, maxlength) self.authors_w = urwid.AttrMap(urwid.Text(authorsstring), 'threadline_authors') cols.append(('fixed', len(authorsstring), self.authors_w)) subjectstring = self.thread.get_subject() self.subject_w = urwid.AttrMap(urwid.Text(subjectstring, wrap='clip'), 'threadline_subject') if subjectstring: cols.append(self.subject_w) self.columns = urwid.Columns(cols, dividechars=1) self.original_widget = self.columns def render(self, size, focus=False): if focus: self.date_w.set_attr_map({None: 'threadline_date_focus'}) self.mailcount_w.set_attr_map({None: 'threadline_mailcount_focus'}) self.tags_w.set_attr_map({None: 'threadline_tags_focus'}) self.authors_w.set_attr_map({None: 'threadline_authors_focus'}) self.subject_w.set_attr_map({None: 'threadline_subject_focus'}) else: self.date_w.set_attr_map({None: 'threadline_date'}) self.mailcount_w.set_attr_map({None: 'threadline_mailcount'}) self.tags_w.set_attr_map({None: 'threadline_tags'}) self.authors_w.set_attr_map({None: 'threadline_authors'}) self.subject_w.set_attr_map({None: 'threadline_subject'}) 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 class BufferlineWidget(urwid.Text): def __init__(self, buffer): self.buffer = buffer line = '[' + buffer.typename + '] ' + str(buffer) 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.Text): def __init__(self, tag): self.tag = tag urwid.Text.__init__(self, tag, wrap='clip') def selectable(self): return True def keypress(self, size, key): return key def get_tag(self): return self.tag class PromptWidget(urwid.AttrMap): def __init__(self, prefix, text='', completer=None): self.completer = completer leftpart = urwid.Text(prefix, align='left') self.editpart = urwid.Edit(edit_text=text) self.start_completion_pos = len(text) self.completion_results = None both = urwid.Columns( [ ('fixed', len(prefix) + 1, leftpart), ('weight', 1, self.editpart), ]) urwid.AttrMap.__init__(self, both, 'prompt', 'prompt') def set_input(self, txt): return self.editpart.set_edit_text(txt) def get_input(self): return self.editpart.get_edit_text() def keypress(self, size, key): if key in ['tab', 'shift tab']: if self.completer: pos = self.start_completion_pos original = self.editpart.edit_text[:pos] if not self.completion_results: # not in completion mode self.completion_results = [''] + \ self.completer.complete(original) self.focus_in_clist = 1 else: if key == 'tab': self.focus_in_clist += 1 else: self.focus_in_clist -= 1 if len(self.completion_results) > 1: suffix = self.completion_results[self.focus_in_clist % len(self.completion_results)] self.editpart.set_edit_text(original + suffix) self.editpart.edit_pos += len(suffix) else: self.editpart.set_edit_text(original + ' ') self.editpart.edit_pos += 1 self.start_completion_pos = self.editpart.edit_pos self.completion_results = None else: result = self.editpart.keypress(size, key) self.start_completion_pos = self.editpart.edit_pos self.completion_results = None return result class MessageWidget(urwid.WidgetWrap): def __init__(self, message, even=False, unfold_body=False, unfold_header=False, depth=0, bars_at=[]): self.message = message self.depth = depth self.bars_at = bars_at self.even = even # build the summary line, header and body will be created on demand self.sumline = self._build_sum_line() self.headerw = None self.bodyw = None self.displayed_list = [self.sumline] if unfold_header: self.displayed_list.append(self.get_header_widget()) if unfold_body: self.displayed_list.append(self.get_body_widget()) #build pile and call super constructor self.pile = urwid.Pile(self.displayed_list) urwid.WidgetWrap.__init__(self, self.pile) # in case the message is yet unread, remove this tag if 'unread' in message.get_tags(): message.remove_tags(['unread']) def rebuild(self): 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) if self.even: attr = 'messagesummary_even' else: attr = 'messagesummary_odd' cols = [] bc = list() # box_columns if self.depth > 1: bc.append(0) cols.append(self._get_spacer(self.bars_at[1:-1])) if self.depth > 0: if self.bars_at[-1]: arrowhead = u'\u251c\u25b6' else: arrowhead = u'\u2514\u25b6' cols.append(('fixed', 2, urwid.Text(arrowhead))) cols.append(self.sumw) line = urwid.AttrMap(urwid.Columns(cols, box_columns=bc), attr, 'messagesummary_focus') return line def _get_header_widget(self): """creates/returns the widget that displays the mail header""" if not self.headerw: displayed = config.getstringlist('general', 'displayed_headers') cols = [MessageHeaderWidget(self.message.get_email(), displayed)] bc = list() if self.depth: cols.insert(0, self._get_spacer(self.bars_at[1:])) bc.append(0) self.headerw = urwid.Columns(cols, box_columns=bc) return self.headerw 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) self.bodyw = urwid.Columns(cols, box_columns=bc) return self.bodyw 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)) return ('fixed', length, spacer) def toggle_header(self): hw = self._get_header_widget() if hw in self.displayed_list: self.displayed_list.remove(hw) else: self.displayed_list.insert(1, hw) self.rebuild() def toggle_body(self): bw = self._get_body_widget() if bw in self.displayed_list: self.displayed_list.remove(bw) else: self.displayed_list.append(bw) self.sumw.toggle_folded() self.rebuild() def selectable(self): return True def keypress(self, size, key): if key == 'h': self.toggle_header() elif key == 'enter': self.toggle_body() else: return self.pile.keypress(size, key) def get_message(self): return self.message def get_email(self): return self.message.get_email() class MessageSummaryWidget(urwid.WidgetWrap): """a one line summary of a message, top of the message widget pile.""" def __init__(self, message, folded=True): self.message = message self.folded = folded urwid.WidgetWrap.__init__(self, urwid.Text(str(self))) def __str__(self): prefix = "- " if self.folded: prefix = '+ ' return "%s%s (%s)" % (prefix, self.message.sender, pretty_datetime(self.message.datetime)) def toggle_folded(self): self.folded = not self.folded self._w = urwid.Text(str(self)) def selectable(self): return True def keypress(self, size, key): return key class MessageHeaderWidget(urwid.AttrMap): def __init__(self, eml, displayed_headers=None): self.eml = eml headerlines = [] if not displayed_headers: displayed_headers = eml.keys() for line in displayed_headers: if line in eml: headerlines.append('%s:%s' % (line, eml.get(line))) headertxt = '\n'.join(headerlines) urwid.AttrMap.__init__(self, urwid.Text(headertxt), 'message_header') def selectable(self): return True def keypress(self, size, key): return key class MessageBodyWidget(urwid.AttrMap): def __init__(self, eml): self.eml = eml bodytxt = '' for part in self.eml.walk(): ctype = part.get_content_type() if ctype == 'text/plain': bodytxt += part.get_payload(None, True) elif ctype == 'text/html': #get mime handler handler = get_mime_handler(ctype, key='view', interactive=False) #open tempfile: tmpfile = tempfile.NamedTemporaryFile(delete=False, suffix='.html') #write payload to tmpfile tmpfile.write(part.get_payload(None, True)) #create and call external command cmd = handler % tmpfile.name rendered = cmd_output(cmd) #remove tempfile tmpfile.close() os.unlink(tmpfile.name) bodytxt += rendered urwid.AttrMap.__init__(self, urwid.Text(bodytxt), 'message_body') def selectable(self): return True def keypress(self, size, key): return key