From 30788c15e76e2d58840c0e4b89116572854586da Mon Sep 17 00:00:00 2001 From: pazz Date: Mon, 23 May 2011 21:47:44 +0100 Subject: rename to "alot" read and laugh, alot: http://hyperboleandahalf.blogspot.com/2010/04/alot-is-better-than-you-at-everything.html --- alot/__init__.py | 0 alot/buffer.py | 139 ++++++++++++++++++++++++++++++++ alot/command.py | 237 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ alot/db.py | 29 +++++++ alot/helper.py | 6 ++ alot/hooks.py | 14 ++++ alot/ui.py | 169 +++++++++++++++++++++++++++++++++++++++ alot/walker.py | 66 ++++++++++++++++ alot/widgets.py | 190 ++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 850 insertions(+) create mode 100644 alot/__init__.py create mode 100644 alot/buffer.py create mode 100644 alot/command.py create mode 100644 alot/db.py create mode 100644 alot/helper.py create mode 100644 alot/hooks.py create mode 100644 alot/ui.py create mode 100644 alot/walker.py create mode 100644 alot/widgets.py (limited to 'alot') diff --git a/alot/__init__.py b/alot/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/alot/buffer.py b/alot/buffer.py new file mode 100644 index 00000000..ff37a69c --- /dev/null +++ b/alot/buffer.py @@ -0,0 +1,139 @@ +import urwid + +import widgets +import command +from walker import IteratorWalker + + +class Buffer(urwid.AttrMap): + def __init__(self, ui, widget, name): + self.ui = ui + self.typename = name + self.bindings = {} + urwid.AttrMap.__init__(self, widget, {}) + + def refresh(self): + pass + + def __str__(self): + return "[%s]" % (self.typename) + + def apply_command(self, cmd): + #call and store it directly for a local cmd history + self.ui.apply_command(cmd) + #self.refresh() + + def keypress(self, size, key): + if key in self.bindings: + self.ui.logger.debug("%s: handeles key: %s" % (self.typename, key)) + (cmdname, parms) = self.bindings[key] + cmd = command.factory(cmdname, **parms) + self.apply_command(cmd) + else: + if key == 'j': key = 'down' + elif key == 'k': key = 'up' + elif key == 'h': key = 'left' + elif key == 'l': key = 'right' + elif key == ' ': key = 'page down' + return self.original_widget.keypress(size, key) + + +class BufferListBuffer(Buffer): + def __init__(self, ui, filtfun=None): + self.filtfun = filtfun + self.ui = ui + #better create a walker obj that has a pointer to ui.bufferlist + #self.widget=createWalker(... + self.refresh() + Buffer.__init__(self, ui, self.original_widget, 'bufferlist') + self.bindings = { + 'd': ('buffer_close', + {'buffer': self.get_selected_buffer}), + 'enter': ('buffer_focus', + {'buffer': self.get_selected_buffer}), + } + + def index_of(self, b): + return self.ui.buffers.index(b) + + def refresh(self): + lines = list() + for (num, b) in enumerate(filter(self.filtfun, self.ui.buffers)): + line = widgets.BufferlineWidget(b) + if (num % 2) == 0: attr = 'bufferlist_results_even' + else: attr = 'bufferlist_results_odd' + buf = urwid.AttrMap(line, attr, 'bufferlist_focus') + num = urwid.Text('%3d:' % self.index_of(b)) + lines.append(urwid.Columns([('fixed', 4, num), buf])) + self.bufferlist = urwid.ListBox(urwid.SimpleListWalker(lines)) + self.original_widget = self.bufferlist + + def get_selected_buffer(self): + (linewidget, size) = self.bufferlist.get_focus() + bufferlinewidget = linewidget.get_focus().original_widget + return bufferlinewidget.get_buffer() + + +class SearchBuffer(Buffer): + threads = [] + + def __init__(self, ui, initialquery=''): + self.dbman = ui.dbman + self.querystring = initialquery + self.result_count = 0 + #self.widget=createWalker(... + self.refresh() + Buffer.__init__(self, ui, self.original_widget, 'search') + self.bindings = { + 'enter': ('open_thread', {'thread': self.get_selected_thread}), + } + + def refresh(self): + self.result_count = self.dbman.count_messages(self.querystring) + threads = self.dbman.search_threads(self.querystring) + iterator = IteratorWalker(threads, widgets.ThreadlineWidget) + self.threadlist = urwid.ListBox(iterator) + self.original_widget = self.threadlist + + def __str__(self): + string = "[%s] for %s, (%d)" + return string % (self.typename, self.querystring, self.result_count) + + def get_selected_thread(self): + (threadlinewidget, size) = self.threadlist.get_focus() + return threadlinewidget.get_thread() + + +class SingleThreadBuffer(Buffer): + def __init__(self, ui, thread): + self.read_thread(thread) + self.refresh() + Buffer.__init__(self, ui, self.original_widget, 'search') + self.bindings = { + 'enter': ('call_pager', + {'path': self.get_selected_message_file}), + } + + def read_thread(self, thread): + self.message_count = thread.get_total_messages() + self.subject = thread.get_subject() + # list() throws an error + self.messages = [m for m in thread.get_toplevel_messages()] + + def refresh(self): + msgs = list() + for (num, m) in enumerate(self.messages, 1): + msgs.append(widgets.MessageWidget(m, even=(num % 2 == 0))) + self.messagelist = urwid.ListBox(msgs) + self.original_widget = self.messagelist + + def __str__(self): + string = "[%s] %s, (%d)" + return string % (self.typename, self.subject, self.message_count) + + def get_selected_message(self): + (messagewidget, size) = self.messagelist.get_focus() + return messagewidget.get_message() + + def get_selected_message_file(self): + return self.get_selected_message().get_filename() diff --git a/alot/command.py b/alot/command.py new file mode 100644 index 00000000..735f7cd0 --- /dev/null +++ b/alot/command.py @@ -0,0 +1,237 @@ +import os +import code +import logging +import threading +import subprocess + +import buffer +import hooks +import settings + + +class Command: + """base class for commands""" + def __init__(self, prehook=None, posthook=None): + self.prehook = prehook + self.posthook = posthook + self.undoable = False + self.help = self.__doc__ + + def apply(self, caller): + pass + + +class ShutdownCommand(Command): + """shuts the MUA down cleanly""" + def apply(self, ui): + ui.shutdown() + + +class OpenThreadCommand(Command): + """open a new thread-view buffer""" + def __init__(self, thread, **kwargs): + self.thread = thread + Command.__init__(self, **kwargs) + + def apply(self, ui): + ui.logger.info('open thread view for %s' % self.thread) + sb = buffer.SingleThreadBuffer(ui, self.thread) + ui.buffer_open(sb) + + +class SearchCommand(Command): + """open a new search buffer""" + def __init__(self, query, **kwargs): + """ + @param query initial querystring + """ + self.query = query + Command.__init__(self, **kwargs) + + def apply(self, ui): + sb = buffer.SearchBuffer(ui, self.query) + ui.buffer_open(sb) + + +class SearchPromptCommand(Command): + """prompt the user for a querystring, then start a search""" + def apply(self, ui): + querystring = ui.prompt('search threads:') + ui.logger.info("got %s" % querystring) + if querystring: + cmd = factory('search', query=querystring) + ui.apply_command(cmd) + + +class EditCommand(Command): + """ + opens editor + TODO tempfile handling etc + """ + def __init__(self, path, spawn=False, **kwargs): + self.path = path + self.spawn = settings.spawn_editor or spawn + Command.__init__(self, **kwargs) + + def apply(self, ui): + def afterwards(): + ui.logger.info('Editor was closed') + cmd = ExternalCommand(settings.editor_cmd % self.path, + spawn=self.spawn, + onExit=afterwards) + ui.apply_command(cmd) + + +class PagerCommand(Command): + """opens pager""" + + def __init__(self, path, spawn=False, **kwargs): + self.path = path + self.spawn = settings.spawn_pager or spawn + Command.__init__(self, **kwargs) + + def apply(self, ui): + def afterwards(): + ui.logger.info('pager was closed') + cmd = ExternalCommand(settings.pager_cmd %self.path, + spawn=self.spawn, + onExit=afterwards) + ui.apply_command(cmd) + + +class ExternalCommand(Command): + """calls external command""" + def __init__(self, commandstring, spawn=False, refocus=True, onExit=None, **kwargs): + self.commandstring = commandstring + self.spawn = spawn + self.refocus = refocus + self.onExit = onExit + Command.__init__(self, **kwargs) + + def apply(self, ui): + def call(onExit, popenArgs): + callerbuffer = ui.current_buffer + ui.logger.info('CALLERBUFFER: %s'%callerbuffer) + proc = subprocess.Popen(*popenArgs,shell=True) + proc.wait() + if callable(onExit): + onExit() + if self.refocus and callerbuffer in ui.buffers: + ui.logger.info('TRY TO REFOCUS: %s'%callerbuffer) + ui.buffer_focus(callerbuffer) + return + + if self.spawn: + cmd = settings.terminal_cmd % self.commandstring + thread = threading.Thread(target=call, args=(self.onExit, (cmd,))) + thread.start() + else: + ui.mainloop.screen.stop() + cmd = self.commandstring + logging.debug(cmd) + call(self.onExit,(cmd,)) + ui.mainloop.screen.start() + +class OpenPythonShellCommand(Command): + """ + opens an interactive shell for introspection + """ + def apply(self, ui): + ui.mainloop.screen.stop() + code.interact(local=locals()) + ui.mainloop.screen.start() + + +class BufferCloseCommand(Command): + """ + close a buffer + @param buffer the selected buffer + """ + def __init__(self, buffer=None, **kwargs): + self.buffer = buffer + Command.__init__(self, **kwargs) + + def apply(self, ui): + if not self.buffer: + self.buffer = ui.current_buffer + ui.buffer_close(self.buffer) + ui.buffer_focus(ui.current_buffer) + + +class BufferFocusCommand(Command): + """ + focus a buffer + @param buffer the selected buffer + """ + def __init__(self, buffer=None, offset=0, **kwargs): + self.buffer = buffer + self.offset = offset + Command.__init__(self, **kwargs) + + def apply(self, ui): + if not self.buffer: + self.buffer = ui.current_buffer + i = ui.buffers.index(self.buffer) + l = len(ui.buffers) + ui.buffer_focus(ui.buffers[(i + self.offset) % l]) + + +class BufferListCommand(Command): + """ + open a bufferlist + """ + def __init__(self, filtfun=None, **kwargs): + self.filtfun = filtfun + Command.__init__(self, **kwargs) + + def apply(self, ui): + b = buffer.BufferListBuffer(ui, self.filtfun) + ui.buffers.append(b) + b.refresh() + ui.buffer_focus(b) + + +commands = { + 'buffer_close': (BufferCloseCommand, {}), + 'buffer_list': (BufferListCommand, {}), + 'buffer_focus': (BufferFocusCommand, {}), + 'buffer_next': (BufferFocusCommand, {'offset': 1}), + 'buffer_prev': (BufferFocusCommand, {'offset': -1}), + 'open_inbox': (SearchCommand, {'query': 'tag:inbox'}), + 'open_unread': (SearchCommand, {'query': 'tag:unread'}), + 'open_search': (SearchPromptCommand, {}), + 'open_thread': (OpenThreadCommand, {}), + 'search': (SearchCommand, {}), + 'shutdown': (ShutdownCommand, {}), + 'shell': (OpenPythonShellCommand, {}), + 'view_log': (PagerCommand, {'path': 'debug.log'}), + 'call_editor': (EditCommand, {}), + 'call_pager': (PagerCommand, {}), + } + + +def factory(cmdname, **kwargs): + if cmdname in commands: + (cmdclass, parms) = commands[cmdname] + parms = parms.copy() + parms.update(kwargs) + for (key, value) in kwargs.items(): + if callable(value): + try: + parms[key] = value() + except: + parms[key] = None + else: + parms[key] = value + prehook = hooks.get_hook('pre-' + cmdname) + if prehook: + parms['prehook'] = prehook + + posthook = hooks.get_hook('post-' + cmdname) + if posthook: + parms['posthook'] = hooks.get_hook('post-' + cmdname) + + logging.debug('cmd parms %s' % parms) + return cmdclass(**parms) + else: + logging.error('there is no command %s' % cmdname) diff --git a/alot/db.py b/alot/db.py new file mode 100644 index 00000000..41aa95d9 --- /dev/null +++ b/alot/db.py @@ -0,0 +1,29 @@ +from notmuch import Database + + +class DBManager(): + def __init__(self, path=None, ro=False): + self.ro = ro + self.path = path + + def count_messages(self, querystring): + q = self.query(querystring) + return q.count_messages() + + def search_threads(self, querystring): + q = self.query(querystring) + return q.search_threads() + + def query(self, querystring): + mode = Database.MODE.READ_ONLY + db = Database(path=self.path, mode=mode) + return db.create_query(querystring) + + def update(self, updatestring): + if self.ro: + self.logger.error('I\'m in RO mode') + else: + self.logger.error('DB updates not implemented yet') + mode = Database.MODE.READ_WRITE + db = Database(path=self.path, mode=mode) + return None # do stuff diff --git a/alot/helper.py b/alot/helper.py new file mode 100644 index 00000000..a65cc007 --- /dev/null +++ b/alot/helper.py @@ -0,0 +1,6 @@ + + +def shorten(string, maxlen): + if len(string) > maxlen - 3: + string = string[:maxlen - 3] + '...' + return string diff --git a/alot/hooks.py b/alot/hooks.py new file mode 100644 index 00000000..6f2ed760 --- /dev/null +++ b/alot/hooks.py @@ -0,0 +1,14 @@ +import logging + +import settings + +defaulthooks = {} + + +def get_hook(name): + logging.debug('looking for hook %s' % name) + if name in settings.hooks: + return settings.hooks[name] + else: + # TODO: parse hookdir for binaries? + return None diff --git a/alot/ui.py b/alot/ui.py new file mode 100644 index 00000000..3f33d0a8 --- /dev/null +++ b/alot/ui.py @@ -0,0 +1,169 @@ +import urwid + +import settings +from alot import command +from alot.widgets import PromptWidget + + +class UI: + buffers = [] + current_buffer = None + + def __init__(self, db, log, **args): + self.logger = log + self.dbman = db + + self.logger.error(args) + self.logger.debug('setup gui') + self.mainframe = urwid.Frame(urwid.SolidFill(' ')) + self.mainloop = urwid.MainLoop(self.mainframe, + settings.palette, + handle_mouse=args['handle_mouse'], + unhandled_input=self.keypress) + #self.mainloop.screen.set_terminal_properties(colors=256) + self.mainloop.screen.set_terminal_properties(colors=16) + + self.logger.debug('setup bindings') + self.bindings = { + 'i': ('open_inbox', {}), + 'u': ('open_unread', {}), + 'x': ('buffer_close', {}), + 'tab': ('buffer_next', {}), + 'shift tab': ('buffer_prev', {}), + '\\': ('open_search', {}), + 'q': ('shutdown', {}), + ';': ('buffer_list', {}), + 's': ('shell', {}), + 'v': ('view_log', {}), + } + + cmd = command.factory('search', query=args['search']) + self.apply_command(cmd) + self.mainloop.run() + + def shutdown(self): + """ + close the ui. this is _not_ the main shutdown procedure: + there is a shutdown command that will eventually call this. + """ + raise urwid.ExitMainLoop() + + def prompt(self, prefix): + self.logger.info('open prompt') + + p = PromptWidget(prefix) + footer = self.mainframe.get_footer() + self.mainframe.set_footer(p) + self.mainframe.set_focus('footer') + self.mainloop.draw_screen() + while True: + keys = None + while not keys: + keys = self.mainloop.screen.get_input() + for k in keys: + if k == 'enter': + self.mainframe.set_footer(footer) + self.mainframe.set_focus('body') + return p.get_input() + if k in ('escape', 'tab'): + self.mainframe.set_footer(footer) + self.mainframe.set_focus('body') + return None + else: + size = (20,) # don't know why they want a size here + p.editpart.keypress(size, k) + self.mainloop.draw_screen() + + def buffer_open(self, b): + """ + register and focus new buffer + """ + self.buffers.append(b) + self.buffer_focus(b) + + def buffer_close(self, b): + buffers = self.buffers + if b not in buffers: + string = 'tried to close unknown buffer: %s. \n\ni have:%s' + self.logger.error(string % (b, self.buffers)) + elif len(buffers) == 1: + self.logger.info('closing the last buffer, exiting') + cmd = command.factory('shutdown') + self.apply_command(cmd) + else: + if self.current_buffer == b: + self.logger.debug('UI: closing current buffer %s' % b) + index = buffers.index(b) + buffers.remove(b) + self.current_buffer = buffers[index % len(buffers)] + else: + string = 'closing buffer %d:%s' + self.logger.debug(string % (buffers.index(b), b)) + index = buffers.index(b) + buffers.remove(b) + + def buffer_focus(self, b): + """ + focus given buffer. must be contained in self.buffers + """ + if b not in self.buffers: + self.logger.error('tried to focus unknown buffer') + else: + self.current_buffer = b + self.current_buffer.refresh() + self.update() + if self.mainloop.screen._started: + self.mainloop.draw_screen() + + def update(self): + """ + redraw interface + """ + #who needs a header? + #head = urwid.Text('notmuch gui') + #h=urwid.AttrMap(head, 'header') + #self.mainframe.set_header(h) + + #body + self.mainframe.set_body(self.current_buffer) + + #footer + self.update_footer() + + def update_footer(self): + i = self.buffers.index(self.current_buffer) + lefttxt = '%d: %s' % (i, self.current_buffer) + footerleft = urwid.Text(lefttxt, align='left') + righttxt = 'total messages: %d' % self.dbman.count_messages('*') + footerright = urwid.Text(righttxt, align='right') + columns = urwid.Columns([ + footerleft, + ('fixed', len(righttxt), footerright)]) + footer = urwid.AttrMap(columns, 'footer') + self.mainframe.set_footer(footer) + + def keypress(self, key): + if key in self.bindings: + self.logger.debug("got globally bounded key: %s" % key) + (cmdname, parms) = self.bindings[key] + cmd = command.factory(cmdname, **parms) + self.apply_command(cmd) + else: + self.logger.debug('unhandeled input: %s' % input) + + def apply_command(self, cmd): + if cmd: + if cmd.prehook: + self.logger.debug('calling pre-hook') + try: + cmd.prehook(self) + except: + self.logger.exception('prehook failed') + self.logger.debug('apply command: %s' % cmd) + cmd.apply(self) + if cmd.posthook: + self.logger.debug('calling post-hook') + try: + cmd.posthook(self) + except: + self.logger.exception('posthook failed') diff --git a/alot/walker.py b/alot/walker.py new file mode 100644 index 00000000..417a9e21 --- /dev/null +++ b/alot/walker.py @@ -0,0 +1,66 @@ +import urwid +import logging + +from notmuch import NotmuchError + + +class IteratorWalker(urwid.ListWalker): + def __init__(self, it, containerclass): + self.it = it + self.containerclass = containerclass + self.lines = [] + self.focus = 0 + self.empty = False + + def get_focus(self): + return self._get_at_pos(self.focus) + + def set_focus(self, focus): + self.focus = focus + self._modified() + + def get_next(self, start_from): + return self._get_at_pos(start_from + 1) + + def get_prev(self, start_from): + return self._get_at_pos(start_from - 1) + + def _get_at_pos(self, pos): + if pos < 0: # pos too low + return (None, None) + elif pos > len(self.lines): # pos too high + return (None, None) + elif len(self.lines) > pos: # pos already cached + return (self.lines[pos], pos) + else: # pos not cached yet, look at next item from iterator + if self.empty: # iterator is empty + return (None, None) + else: + widget = self._get_next_item() + if widget: + return (widget, pos) + else: + return (None, None) + + def _get_next_item(self): + try: + next_obj = self.it.next() + next_widget = self.containerclass(next_obj) + self.lines.append(next_widget) + except StopIteration: + next_widget = None + self.empty = True + return next_widget + + +class NotmuchIteratorWalker(IteratorWalker): + def _get_next_item(self): + logging.error("it still there") + try: + next_obj = self.it.next() + logging.error("next obj: %s" % next_obj) + next_widget = self.containerclass(next_obj) + self.lines.append(next_widget) + except NotmuchError: + next_widget = None + return next_widget diff --git a/alot/widgets.py b/alot/widgets.py new file mode 100644 index 00000000..7c761c12 --- /dev/null +++ b/alot/widgets.py @@ -0,0 +1,190 @@ +from urwid import Text +from urwid import Edit +from urwid import Pile +from urwid import Columns +from urwid import AttrMap +from urwid import WidgetWrap + +import email +from datetime import datetime + +import settings +from helper import shorten + + +class ThreadlineWidget(AttrMap): + def __init__(self, thread): + self.thread = thread + + self.datetime = datetime.fromtimestamp(thread.get_newest_date()) + datestring = self.datetime.strftime('%B %d,%I:%M') + datestring += self.datetime.strftime('%p').lower() + self.date_w = AttrMap(Text(datestring), 'threadline_date') + + mailcountstring = "(%d)" % self.thread.get_total_messages() + self.mailcount_w = AttrMap(Text(mailcountstring), + 'threadline_mailcount') + + tagsstring = " ".join(self.thread.get_tags()) + self.tags_w = AttrMap(Text(tagsstring), + 'threadline_tags') + + authorsstring = shorten(thread.get_authors(), + settings.authors_maxlength) + self.authors_w = AttrMap(Text(authorsstring), + 'threadline_authors') + + self.subject_w = AttrMap(Text(thread.get_subject(), wrap='clip'), + 'threadline_subject') + + self.columns = Columns([ + ('fixed', len(datestring), self.date_w), + ('fixed', len(mailcountstring), self.mailcount_w), + ('fixed', len(tagsstring), self.tags_w), + ('fixed', len(authorsstring), self.authors_w), + self.subject_w, + ], + dividechars=1) + AttrMap.__init__(self, self.columns, 'threadline', 'threadline_focus') + + def render(self, size, focus=False): + if focus: + self.date_w.set_attr_map({None: 'threadline_date_linefocus'}) + self.mailcount_w.set_attr_map({None: 'threadline_mailcount_linefocus'}) + self.tags_w.set_attr_map({None: 'threadline_tags_linefocus'}) + self.authors_w.set_attr_map({None: 'threadline_authors_linefocus'}) + self.subject_w.set_attr_map({None: 'threadline_subject_linefocus'}) + 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 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(Text): + def __init__(self, buffer): + self.buffer = buffer + Text.__init__(self, buffer.__str__(), wrap='clip') + + def selectable(self): + return True + + def keypress(self, size, key): + return key + + def get_buffer(self): + return self.buffer + + +class PromptWidget(AttrMap): + def __init__(self, prefix): + leftpart = Text(prefix, align='left') + self.editpart = Edit() + both = Columns( + [ + ('fixed', len(prefix) + 1, leftpart), + ('weight', 1, self.editpart), + ]) + 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() + + +class MessageWidget(WidgetWrap): + def __init__(self, message, even=False): + self.message = message + self.email = self.read_mail(message) + if even: + lineattr = 'messageline_even' + else: + lineattr = 'messageline_odd' + + self.bodyw = MessageBodyWidget(self.email) + self.headerw = MessageHeaderWidget(self.email) + self.linew = MessageLineWidget(self.message) + pile = Pile([ + AttrMap(self.linew, lineattr), + AttrMap(self.headerw, 'message_header'), + AttrMap(self.bodyw, 'message_body') + ]) + WidgetWrap.__init__(self, pile) + + def selectable(self): + return True + + def keypress(self, size, key): + return key + + def get_message(self): + return self.message + + def get_email(self): + return self.eml + + def read_mail(self, message): + #what about crypto? + try: + f_mail = open(message.get_filename()) + except EnvironmentError: + eml = email.message_from_string('Unable to open the file') + else: + eml = email.message_from_file(f_mail) + f_mail.close() + return eml + + +class MessageLineWidget(WidgetWrap): + def __init__(self, message): + self.message = message + WidgetWrap.__init__(self, Text(str(message))) + + def selectable(self): + return True + + def keypress(self, size, key): + return key + + +class MessageHeaderWidget(WidgetWrap): + def __init__(self, eml): + self.eml = eml + headerlines = [] + for l in settings.displayed_headers: + if l in eml: + headerlines.append('%s:%s' % (l, eml.get(l))) + headertxt = '\n'.join(headerlines) + WidgetWrap.__init__(self, Text(headertxt)) + + def selectable(self): + return True + + def keypress(self, size, key): + return key + + +class MessageBodyWidget(WidgetWrap): + def __init__(self, eml): + self.eml = eml + bodytxt = ''.join(email.iterators.body_line_iterator(self.eml)) + WidgetWrap.__init__(self, Text(bodytxt)) + + def selectable(self): + return True + + def keypress(self, size, key): + return key -- cgit v1.2.3