summaryrefslogtreecommitdiff
path: root/alot
diff options
context:
space:
mode:
authorpazz <patricktotzke@gmail.com>2011-05-23 21:47:44 +0100
committerpazz <patricktotzke@gmail.com>2011-05-23 21:47:44 +0100
commit30788c15e76e2d58840c0e4b89116572854586da (patch)
treeb8c486b22f89e9f15e6dc3c7652f39428a7c70ba /alot
parent25a42f2cf157632740e6940b544fca7d5bc98c5f (diff)
rename to "alot"
read and laugh, alot: http://hyperboleandahalf.blogspot.com/2010/04/alot-is-better-than-you-at-everything.html
Diffstat (limited to 'alot')
-rw-r--r--alot/__init__.py0
-rw-r--r--alot/buffer.py139
-rw-r--r--alot/command.py237
-rw-r--r--alot/db.py29
-rw-r--r--alot/helper.py6
-rw-r--r--alot/hooks.py14
-rw-r--r--alot/ui.py169
-rw-r--r--alot/walker.py66
-rw-r--r--alot/widgets.py190
9 files changed, 850 insertions, 0 deletions
diff --git a/alot/__init__.py b/alot/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/alot/__init__.py
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