diff options
author | Patrick Totzke <patricktotzke@gmail.com> | 2012-07-22 21:55:00 +0100 |
---|---|---|
committer | Patrick Totzke <patricktotzke@gmail.com> | 2012-07-22 21:55:00 +0100 |
commit | dd087f6c0ea083a5b245bf11e2e2b08ee9ede0e0 (patch) | |
tree | 0c7728a6dfa348895bcc8660159cef4d333b8702 | |
parent | 0fc049f04bb9b5a44a379206e37dd32e65feae99 (diff) | |
parent | 44a7d6f262bd6eb133202d48e9fd176c81d285be (diff) |
Merge branch '0.3.1-theming'
27 files changed, 1555 insertions, 1003 deletions
diff --git a/alot/addressbooks.py b/alot/addressbooks.py index 00fe9fe7..a8453b09 100644 --- a/alot/addressbooks.py +++ b/alot/addressbooks.py @@ -50,7 +50,8 @@ class AbookAddressBook(AddressBook): res = [] for id in c.sections: for email in c[id]['email']: - if email: res.append((c[id]['name'], email)) + if email: + res.append((c[id]['name'], email)) return res diff --git a/alot/buffers.py b/alot/buffers.py index 07a7589d..866e1491 100644 --- a/alot/buffers.py +++ b/alot/buffers.py @@ -78,11 +78,11 @@ class BufferlistBuffer(Buffer): line = widgets.BufferlineWidget(b) if (num % 2) == 0: attr = settings.get_theming_attribute('bufferlist', - 'results_even') + 'line_even') else: - attr = settings.get_theming_attribute('bufferlist', - 'results_odd') - focus_att = settings.get_theming_attribute('bufferlist', 'focus') + attr = settings.get_theming_attribute('bufferlist', 'line_odd') + focus_att = settings.get_theming_attribute('bufferlist', + 'line_focus') buf = urwid.AttrMap(line, attr, focus_att) num = urwid.Text('%3d:' % self.index_of(b)) lines.append(urwid.Columns([('fixed', 4, num), buf])) @@ -385,13 +385,23 @@ class TagListBuffer(Buffer): displayedtags = sorted(filter(self.filtfun, self.tags), key=unicode.lower) for (num, b) in enumerate(displayedtags): - tw = widgets.TagWidget(b) + line = widgets.BufferlineWidget(b) + if (num % 2) == 0: + attr = settings.get_theming_attribute('taglist', 'line_even') + else: + attr = settings.get_theming_attribute('taglist', 'line_odd') + focus_att = settings.get_theming_attribute('taglist', 'line_focus') + + tw = widgets.TagWidget(b, attr, focus_att) rows = [('fixed', tw.width(), tw)] if tw.hidden: - rows.append(urwid.Text('[hidden]')) + rows.append(urwid.Text(b + ' [hidden]')) elif tw.translated is not b: rows.append(urwid.Text('(%s)' % b)) - lines.append(urwid.Columns(rows, dividechars=1)) + line = urwid.Columns(rows, dividechars=1) + line = urwid.AttrMap(line, attr, focus_att) + lines.append(line) + self.taglist = urwid.ListBox(urwid.SimpleListWalker(lines)) self.body = self.taglist @@ -400,5 +410,5 @@ class TagListBuffer(Buffer): def get_selected_tag(self): """returns selected tagstring""" (cols, pos) = self.taglist.get_focus() - tagwidget = cols.get_focus() + tagwidget = cols.original_widget.get_focus() return tagwidget.get_tag() diff --git a/alot/defaults/alot.rc.spec b/alot/defaults/alot.rc.spec index cd5dd6c3..174413fd 100644 --- a/alot/defaults/alot.rc.spec +++ b/alot/defaults/alot.rc.spec @@ -128,9 +128,6 @@ envelope_statusbar = mixed_list(string, string, default=list('[{buffer_no}: enve # timestamp format in `strftime format syntax <http://docs.python.org/library/datetime.html#strftime-strptime-behavior>`_ timestamp_format = string(default=None) -# maximal length of authors string in search mode before it gets truncated -authors_maxlength = integer(default=30) - # how to print messages: # this specifies a shell command used for printing. # threads/messages are piped to this command as plain text. @@ -179,14 +176,10 @@ forward_subject_prefix = string(default='Fwd: ') [tags] # for each tag [[__many__]] - # foreground - fg = string(default=None) - # background - bg = string(default=None) - # foreground if focused - focus_fg = string(default=None) - # background if focused - focus_bg = string(default=None) + # unfocussed + normal = attrtriple(default=None) + # focussed + focus = attrtriple(default=None) # don't display at all? hidden = boolean(default=False) # alternative string representation diff --git a/alot/defaults/default.theme b/alot/defaults/default.theme index e5781912..e88f4a97 100644 --- a/alot/defaults/default.theme +++ b/alot/defaults/default.theme @@ -1,354 +1,87 @@ -[1] -[[global]] - [[[footer]]] - fg = 'standout' - [[[body]]] - fg = 'default' - [[[notify_error]]] - fg = 'standout' - [[[notify_normal]]] - fg = 'default' - [[[prompt]]] - fg = 'default' - [[[tag]]] - fg = 'default' - [[[tag_focus]]] - fg = 'standout, bold' -[[help]] - [[[text]]] - fg = 'default' - [[[section]]] - fg = 'underline' - [[[title]]] - fg = 'standout' -[[bufferlist]] - [[[focus]]] - fg = 'standout' - [[[results_even]]] - fg = 'default' - [[[results_odd]]] - fg = 'default' -[[search]] - [[[thread]]] - fg = 'default' - [[[thread_authors]]] - fg = 'default,underline' - [[[thread_authors_focus]]] - fg = 'standout' - [[[thread_content]]] - fg = 'default' - [[[thread_content_focus]]] - fg = 'standout' - [[[thread_date]]] - fg = 'default' - [[[thread_date_focus]]] - fg = 'standout' - [[[thread_focus]]] - fg = 'standout' - [[[thread_mailcount]]] - fg = 'default' - [[[thread_mailcount_focus]]] - fg = 'standout' - [[[thread_subject]]] - fg = 'default' - [[[thread_subject_isunread]]] - fg = 'bold' - [[[thread_subject_isflagged]]] - fg = 'underline' - [[[thread_subject_focus]]] - fg = 'standout' - [[[thread_subject_isunread_focus]]] - fg = 'standout,bold' - [[[thread_subject_isflagged_focus]]] - fg = 'standout,underline' - [[[thread_tags]]] - fg = 'bold' - [[[thread_tags_focus]]] - fg = 'standout' -[[thread]] - [[[attachment]]] - fg = 'default' - [[[attachment_focus]]] - fg = 'underline' - [[[body]]] - fg = 'default' - [[[header]]] - fg = 'default' - [[[header_key]]] - fg = 'default' - [[[header_value]]] - fg = 'default' - [[[summary_even]]] - fg = 'default' - [[[summary_focus]]] - fg = 'standout' - [[[summary_odd]]] - fg = 'default' -[[envelope]] - [[[body]]] - fg = 'default' - [[[header]]] - fg = 'default' - [[[header_key]]] - fg = 'default' - [[[header_value]]] - fg = 'default' -[16] -[[global]] - [[[footer]]] - bg = 'dark blue' - fg = 'light green' - [[[body]]] - bg = 'default' - fg = 'default' - [[[notify_error]]] - bg = 'dark red' - fg = 'white' - [[[notify_normal]]] - bg = 'dark gray' - fg = 'light gray' - [[[prompt]]] - bg = 'black' - fg = 'light gray' - [[[tag]]] - bg = 'black' - fg = 'brown' - [[[tag_focus]]] - bg = 'dark gray' - fg = 'white' -[[help]] - [[[text]]] - bg = 'dark gray' - fg = 'default' - [[[section]]] - bg = 'dark gray' - fg = 'bold,underline' - [[[title]]] - bg = 'dark blue' - fg = 'white' -[[bufferlist]] - [[[focus]]] - bg = 'dark gray' - fg = 'white' - [[[results_even]]] - bg = 'black' - fg = 'light gray' - [[[results_odd]]] - bg = 'black' - fg = 'light gray' -[[thread]] - [[[attachment]]] - bg = 'dark gray' - fg = 'light gray' - [[[attachment_focus]]] - bg = 'light green' - fg = 'light gray' - [[[body]]] - bg = 'default' - fg = 'light gray' - [[[header]]] - bg = 'dark gray' - fg = 'white' - [[[header_key]]] - bg = 'dark gray' - fg = 'white' - [[[header_value]]] - bg = 'dark gray' - fg = 'light gray' - [[[summary_even]]] - bg = 'light blue' - fg = 'white' - [[[summary_focus]]] - bg = 'dark cyan' - fg = 'white' - [[[summary_odd]]] - bg = 'dark blue' - fg = 'white' -[[envelope]] - [[[body]]] - bg = 'default' - fg = 'light gray' - [[[header]]] - bg = 'dark gray' - fg = 'white' - [[[header_key]]] - bg = 'dark gray' - fg = 'white' - [[[header_value]]] - bg = 'dark gray' - fg = 'light gray' -[[search]] - [[[thread]]] - bg = 'default' - fg = 'default' - [[[thread_authors]]] - bg = 'default' - fg = 'dark green' - [[[thread_authors_focus]]] - bg = 'dark gray' - fg = 'dark green,bold' - [[[thread_content]]] - bg = 'default' - fg = 'dark gray' - [[[thread_content_focus]]] - bg = 'dark gray' - fg = 'black' - [[[thread_date]]] - bg = 'default' - fg = 'light gray' - [[[thread_date_focus]]] - bg = 'dark gray' - fg = 'light gray' - [[[thread_focus]]] - bg = 'dark gray' - fg = 'light gray' - [[[thread_mailcount]]] - bg = 'default' - fg = 'light gray' - [[[thread_mailcount_focus]]] - bg = 'dark gray' - fg = 'light gray' - [[[thread_subject]]] - bg = 'default' - fg = 'light gray' - [[[thread_subject_focus]]] - bg = 'dark gray' - fg = 'light gray' - [[[thread_tags]]] - bg = 'default' - fg = 'brown' - [[[thread_tags_focus]]] - bg = 'dark gray' - fg = 'yellow,bold' +[global] + footer = 'standout','','white,bold','dark blue','white,bold','#006' + body = 'default','','dark gray','default','g58','default' + notify_error = 'standout','','white','dark red','white','dark red' + notify_normal = 'default','','light gray','dark gray','light gray','#68a' + prompt = 'default','','light gray','black','light gray','g11' + tag = 'default','','light gray','black','light gray','default' + tag_focus = 'standout, bold','','white','dark gray','#ffa','#68a' +[help] + text = 'default','','default','dark gray','default','g35' + section = 'underline','','bold,underline','dark gray','bold,underline','g35' + title = 'standout','','white','dark blue','white,bold,underline','g35' +[bufferlist] + line_focus = 'standout','','yellow','light gray','#ff8','g58' + line_even = 'default','','light gray','black','default','g3' + line_odd = 'default','','light gray','black','default','default' +[taglist] + line_focus = 'standout','','yellow','light gray','#ff8','g58' + line_even = 'default','','light gray','black','default','g3' + line_odd = 'default','','light gray','black','default','default' +[thread] + arrow_heads = '','','dark red','','#a00','' + arrow_bars = '','','dark red','','#800','' + attachment = 'default','','light gray','dark gray','light gray','dark gray' + attachment_focus = 'underline','','light gray','light green','light gray','light green' + body = 'default','','light gray','default','light gray','default' + header = 'default','','white','dark gray','white','dark gray' + header_key = 'default','','white','dark gray','white','dark gray' + header_value = 'default','','light gray','dark gray','light gray','dark gray' -[256] -[[global]] - # attributes used in all modi - [[[footer]]] - bg = '#006' - fg = 'white' - [[[body]]] - bg = 'default' - fg = 'default' - [[[notify_error]]] - bg = 'dark red' - fg = 'white' - [[[notify_normal]]] - bg = '#68a' - fg = 'light gray' - [[[prompt]]] - bg = 'g10' - fg = 'light gray' - [[[tag]]] - bg = 'default' - fg = 'brown' - [[[tag_focus]]] - bg = '#68a' - fg = '#ffa' -[[help]] - # formating of the `help bindings` overlay - [[[text]]] - bg = 'g35' - fg = 'default' - [[[section]]] - bg = 'g35' - fg = 'bold,underline' - [[[title]]] - bg = 'g35' - fg = 'white,bold,underline' -# mode specific attributes -[[bufferlist]] - [[[focus]]] - bg = '#68a' - fg = '#ffa' - [[[results_even]]] - bg = 'g3' - fg = 'default' - [[[results_odd]]] - bg = 'default' - fg = 'default' -[[search]] - [[[thread]]] - bg = 'default' - fg = '#6d6' - - [[[thread_authors]]] - bg = 'default' - fg = '#6d6' - [[[thread_authors_focus]]] - bg = '#68a' - fg = '#8f6' - [[[thread_content]]] - bg = 'default' - fg = '#866' - [[[thread_content_focus]]] - bg = '#68a' - fg = '#866' - [[[thread_date]]] - bg = 'default' - fg = 'g58' - [[[thread_date_focus]]] - bg = '#68a' - fg = 'g89' - [[[thread_focus]]] - bg = '#68a' - fg = 'white' - [[[thread_mailcount]]] - bg = 'default' - fg = 'light gray' - [[[thread_mailcount_focus]]] - bg = '#68a' - fg = 'g89' - [[[thread_subject]]] - bg = 'default' - fg = 'g58' - [[[thread_subject_focus]]] - bg = '#68a' - fg = 'g89' - [[[thread_tags]]] - bg = 'default' - fg = '#a86' - [[[thread_tags_focus]]] - bg = '#68a' - fg = '#ff8' -[[thread]] - [[[attachment]]] - bg = 'dark gray' - fg = 'light gray' - [[[attachment_focus]]] - bg = 'light green' - fg = 'light gray' - [[[body]]] - bg = 'default' - fg = 'light gray' - [[[header]]] - bg = 'dark gray' - fg = 'white' - [[[header_key]]] - bg = 'dark gray' - fg = 'white' - [[[header_value]]] - bg = 'dark gray' - fg = 'light gray' - [[[summary_even]]] - bg = '#068' - fg = 'white' - [[[summary_focus]]] - bg = 'g58' - fg = '#ff8' - [[[summary_odd]]] - bg = '#006' - fg = 'white' -[[envelope]] - [[[body]]] - bg = 'default' - fg = 'light gray' - [[[header]]] - bg = 'dark gray' - fg = 'white' - [[[header_key]]] - bg = 'dark gray' - fg = 'white' - [[[header_value]]] - bg = 'dark gray' - fg = 'light gray' + [[summary]] + even = 'default','','white','light blue','white','#006' + odd = 'default','','white','dark blue','white','#068' + focus = 'standout','','white','light gray','#ff8','g58' + +[envelope] + body = 'default','','light gray','default','light gray','default' + header = 'default','','white','dark gray','white','dark gray' + header_key = 'default','','white','dark gray','white','dark gray' + header_value = 'default','','light gray','dark gray','light gray','dark gray' +[search] + [[threadline]] + normal = 'default','','default','default','#6d6','default' + focus = 'standout','','light gray','light gray','g85','g58' + parts = date,mailcount,tags,authors,subject + [[[date]]] + normal = 'default','','light gray','default','g74','default' + focus = 'standout','','yellow','light gray','yellow','g58' + width = 'fit',10,10 + alignment = right + [[[mailcount]]] + normal = 'default','','light gray','default','g66','default' + focus = 'standout','','yellow','light gray','yellow','g58' + width = 'fit', 5,5 + [[[tags]]] + normal = 'bold','','dark cyan','','dark cyan','' + focus = 'standout','','yellow','light gray','yellow','g58' + [[[authors]]] + normal = 'default,underline','','light blue','default','#068','default' + focus = 'standout','','yellow','light gray','yellow','g58' + width = 'fit',0,30 + [[[subject]]] + normal = 'default','','light gray','default','g66','default' + focus = 'standout','','yellow','light gray','yellow','g58' + #width = 'weight', 1 + [[[content]]] + normal = 'default','','light gray','default','dark gray','default' + focus = 'standout','','yellow','light gray','yellow','g58' + width = 'weight', 1 + + # highlight threads containing unread messages + [[threadline-unread]] + tagged_with = 'unread' + normal = 'default','','default,bold','default','#6d6,bold','default' + parts = date,mailcount,tags,authors,subject + [[[date]]] + normal = 'default','','light gray,bold','default','white','default' + [[[mailcount]]] + normal = 'default','','light gray,bold','default','g93','default' + [[[tags]]] + normal = 'bold','','dark cyan,bold','','#6dd','' + [[[authors]]] + normal = 'default,underline','','light blue,bold','default','#68f','default' + [[[subject]]] + normal = 'default','','light gray,bold','default','g93','default' + [[[content]]] + normal = 'default','','light gray,bold','default','dark gray,bold','default' diff --git a/alot/defaults/theme.spec b/alot/defaults/theme.spec index f23d91da..05d59c16 100644 --- a/alot/defaults/theme.spec +++ b/alot/defaults/theme.spec @@ -1,353 +1,65 @@ -[1] -[[global]] - [[[footer]]] - fg = string(default='default') - [[[body]]] - fg = string(default='default') - [[[notify_error]]] - fg = string(default='default') - [[[notify_normal]]] - fg = string(default='default') - [[[prompt]]] - fg = string(default='default') - [[[tag]]] - fg = string(default='default') - [[[tag_focus]]] - fg = string(default='default') -[[help]] - [[[text]]] - fg = string(default='default') - [[[section]]] - fg = string(default='default') - [[[title]]] - fg = string(default='default') -[[bufferlist]] - [[[focus]]] - fg = string(default='default') - [[[results_even]]] - fg = string(default='default') - [[[results_odd]]] - fg = string(default='default') -[[search]] - [[[thread]]] - fg = string(default='default') - [[[thread_authors]]] - fg = string(default='default') - [[[thread_authors_focus]]] - fg = string(default='default') - [[[thread_content]]] - fg = string(default='default') - [[[thread_content_focus]]] - fg = string(default='default') - [[[thread_date]]] - fg = string(default='default') - [[[thread_date_focus]]] - fg = string(default='default') - [[[thread_focus]]] - fg = string(default='default') - [[[thread_mailcount]]] - fg = string(default='default') - [[[thread_mailcount_focus]]] - fg = string(default='default') - [[[thread_subject]]] - fg = string(default='default') - [[[thread_subject_isunread]]] - fg = string(default='default') - [[[thread_subject_isflagged]]] - fg = string(default='default') - [[[thread_subject_focus]]] - fg = string(default='default') - [[[thread_subject_isunread_focus]]] - fg = string(default='default') - [[[thread_subject_isflagged_focus]]] - fg = string(default='default') - [[[thread_tags]]] - fg = string(default='default') - [[[thread_tags_focus]]] - fg = string(default='default') -[[thread]] - [[[attachment]]] - fg = string(default='default') - [[[attachment_focus]]] - fg = string(default='default') - [[[body]]] - fg = string(default='default') - [[[header]]] - fg = string(default='default') - [[[header_key]]] - fg = string(default='default') - [[[header_value]]] - fg = string(default='default') - [[[summary_even]]] - fg = string(default='default') - [[[summary_focus]]] - fg = string(default='default') - [[[summary_odd]]] - fg = string(default='default') -[[envelope]] - [[[body]]] - fg = string(default='default') - [[[header]]] - fg = string(default='default') - [[[header_key]]] - fg = string(default='default') - [[[header_value]]] - fg = string(default='default') - -[16] -[[global]] - [[[footer]]] - bg = string(default='default') - fg = string(default='default') - [[[body]]] - bg = string(default='default') - fg = string(default='default') - [[[notify_error]]] - bg = string(default='default') - fg = string(default='default') - [[[notify_normal]]] - bg = string(default='default') - fg = string(default='default') - [[[prompt]]] - bg = string(default='default') - fg = string(default='default') - [[[tag]]] - bg = string(default='default') - fg = string(default='default') - [[[tag_focus]]] - bg = string(default='default') - fg = string(default='default') -[[help]] - [[[text]]] - bg = string(default='default') - fg = string(default='default') - [[[section]]] - bg = string(default='default') - fg = string(default='default') - [[[title]]] - bg = string(default='default') - fg = string(default='default') -[[bufferlist]] - [[[focus]]] - bg = string(default='default') - fg = string(default='default') - [[[results_even]]] - bg = string(default='default') - fg = string(default='default') - [[[results_odd]]] - bg = string(default='default') - fg = string(default='default') -[[thread]] - [[[attachment]]] - bg = string(default='default') - fg = string(default='default') - [[[attachment_focus]]] - bg = string(default='default') - fg = string(default='default') - [[[body]]] - bg = string(default='default') - fg = string(default='default') - [[[header]]] - bg = string(default='default') - fg = string(default='default') - [[[header_key]]] - bg = string(default='default') - fg = string(default='default') - [[[header_value]]] - bg = string(default='default') - fg = string(default='default') - [[[summary_even]]] - bg = string(default='default') - fg = string(default='default') - [[[summary_focus]]] - bg = string(default='default') - fg = string(default='default') - [[[summary_odd]]] - bg = string(default='default') - fg = string(default='default') -[[envelope]] - [[[body]]] - bg = string(default='default') - fg = string(default='default') - [[[header]]] - bg = string(default='default') - fg = string(default='default') - [[[header_key]]] - bg = string(default='default') - fg = string(default='default') - [[[header_value]]] - bg = string(default='default') - fg = string(default='default') -[[search]] - [[[thread]]] - bg = string(default='default') - fg = string(default='default') - [[[thread_focus]]] - bg = string(default='default') - fg = string(default='default') - [[[thread_authors]]] - bg = string(default='default') - fg = string(default='default') - [[[thread_authors_focus]]] - bg = string(default='default') - fg = string(default='default') - [[[thread_content]]] - bg = string(default='default') - fg = string(default='default') - [[[thread_content_focus]]] - bg = string(default='default') - fg = string(default='default') - [[[thread_date]]] - bg = string(default='default') - fg = string(default='default') - [[[thread_date_focus]]] - bg = string(default='default') - fg = string(default='default') - [[[thread_mailcount]]] - bg = string(default='default') - fg = string(default='default') - [[[thread_mailcount_focus]]] - bg = string(default='default') - fg = string(default='default') - [[[thread_subject]]] - bg = string(default='default') - fg = string(default='default') - [[[thread_subject_focus]]] - bg = string(default='default') - fg = string(default='default') - [[[thread_tags]]] - bg = string(default='default') - fg = string(default='default') - [[[thread_tags_focus]]] - bg = string(default='default') - fg = string(default='default') - -[256] -[[global]] +[global] # attributes used in all modi - [[[footer]]] - bg = string(default=None) - fg = string(default=None) - [[[body]]] - bg = string(default=None) - fg = string(default=None) - [[[notify_error]]] - bg = string(default=None) - fg = string(default=None) - [[[notify_normal]]] - bg = string(default=None) - fg = string(default=None) - [[[prompt]]] - bg = string(default=None) - fg = string(default=None) - [[[tag]]] - bg = string(default=None) - fg = string(default=None) - [[[tag_focus]]] - bg = string(default=None) - fg = string(default=None) -[[help]] + footer = attrtriple + body = attrtriple + notify_error = attrtriple + notify_normal = attrtriple + prompt = attrtriple + tag = attrtriple + tag_focus = attrtriple +[help] # formatting of the `help bindings` overlay - [[[text]]] - bg = string(default=None) - fg = string(default=None) - [[[section]]] - bg = string(default=None) - fg = string(default=None) - [[[title]]] - bg = string(default=None) - fg = string(default=None) + text = attrtriple + section = attrtriple + title = attrtriple # mode specific attributes -[[bufferlist]] - [[[focus]]] - bg = string(default=None) - fg = string(default=None) - [[[results_even]]] - bg = string(default=None) - fg = string(default=None) - [[[results_odd]]] - bg = string(default=None) - fg = string(default=None) -[[search]] - [[[thread]]] - fg = string(default=None) - [[[thread_authors]]] - bg = string(default=None) - fg = string(default=None) - [[[thread_authors_focus]]] - bg = string(default=None) - fg = string(default=None) - [[[thread_content]]] - bg = string(default=None) - fg = string(default=None) - [[[thread_content_focus]]] - bg = string(default=None) - fg = string(default=None) - [[[thread_date]]] - bg = string(default=None) - fg = string(default=None) - [[[thread_date_focus]]] - bg = string(default=None) - fg = string(default=None) - [[[thread_focus]]] - bg = string(default=None) - fg = string(default=None) - [[[thread_mailcount]]] - bg = string(default=None) - fg = string(default=None) - [[[thread_mailcount_focus]]] - bg = string(default=None) - fg = string(default=None) - [[[thread_subject]]] - bg = string(default=None) - fg = string(default=None) - [[[thread_subject_focus]]] - bg = string(default=None) - fg = string(default=None) - [[[thread_tags]]] - bg = string(default=None) - fg = string(default=None) - [[[thread_tags_focus]]] - bg = string(default=None) - fg = string(default=None) -[[thread]] - [[[attachment]]] - bg = string(default=None) - fg = string(default=None) - [[[attachment_focus]]] - bg = string(default=None) - fg = string(default=None) - [[[body]]] - bg = string(default=None) - fg = string(default=None) - [[[header]]] - bg = string(default=None) - fg = string(default=None) - [[[header_key]]] - bg = string(default=None) - fg = string(default=None) - [[[header_value]]] - bg = string(default=None) - fg = string(default=None) - [[[summary_even]]] - bg = string(default=None) - fg = string(default=None) - [[[summary_focus]]] - bg = string(default=None) - fg = string(default=None) - [[[summary_odd]]] - bg = string(default=None) - fg = string(default=None) -[[envelope]] - [[[body]]] - bg = string(default=None) - fg = string(default=None) - [[[header]]] - bg = string(default=None) - fg = string(default=None) - [[[header_key]]] - bg = string(default=None) - fg = string(default=None) - [[[header_value]]] - bg = string(default=None) - fg = string(default=None) +[bufferlist] + line_focus = attrtriple + line_even = attrtriple + line_odd = attrtriple + +[taglist] + line_focus = attrtriple + line_even = attrtriple + line_odd = attrtriple +[search] + [[threadline]] + normal = attrtriple + focus = attrtriple + # order subwidgets are displayed. subset of {date,mailcount,tags,authors,subject,count} + # every element listed must have its own subsection below + parts = string_list(default=None) + [[[__many__]]] + normal = attrtriple + focus = attrtriple + width = widthtuple(default=None) + alignment = align(default='left') + [[__many__]] + normal = attrtriple(default=None) + focus = attrtriple(default=None) + parts = string_list(default=None) + query = string(default=None) + tagged_with = force_list(default=None) + [[[__many__]]] + normal = attrtriple(default=None) + focus = attrtriple(default=None) + width = widthtuple(default=None) + alignment = align(default='right') +[thread] + arrow_heads = attrtriple + arrow_bars = attrtriple + attachment = attrtriple + attachment_focus = attrtriple + body = attrtriple + header = attrtriple + header_key = attrtriple + header_value = attrtriple + [[summary]] + even = attrtriple + odd = attrtriple + focus = attrtriple +[envelope] + body = attrtriple + header = attrtriple + header_key = attrtriple + header_value = attrtriple diff --git a/alot/errors.py b/alot/errors.py index 00336fc8..881acf1f 100644 --- a/alot/errors.py +++ b/alot/errors.py @@ -1,6 +1,8 @@ # 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 + + class GPGProblem(Exception): """GPG Error""" pass diff --git a/alot/helper.py b/alot/helper.py index f4ec07cd..78744edf 100644 --- a/alot/helper.py +++ b/alot/helper.py @@ -22,7 +22,6 @@ from twisted.internet.protocol import ProcessProtocol from twisted.internet.defer import Deferred import StringIO import logging -import tempfile def split_commandstring(cmdstring): @@ -35,6 +34,7 @@ def split_commandstring(cmdstring): cmdstring = cmdstring.encode('utf-8', errors='ignore') return shlex.split(cmdstring) + def safely_get(clb, E, on_error=''): """ returns result of :func:`clb` and falls back to `on_error` diff --git a/alot/settings/__init__.py b/alot/settings/__init__.py index eaa8cd16..0edfc24c 100644 --- a/alot/settings/__init__.py +++ b/alot/settings/__init__.py @@ -7,9 +7,7 @@ import re import errno import mailcap import logging -import urwid import shutil -from urwid import AttrSpecError from configobj import ConfigObj, Section from alot.account import SendmailAccount @@ -18,9 +16,12 @@ from alot.helper import pretty_datetime, string_decode from errors import ConfigError from utils import read_config +from utils import resolve_att from checks import force_list from checks import mail_container from checks import gpg_key +from checks import attr_triple +from checks import align_mode from theme import Theme @@ -62,6 +63,8 @@ class SettingsManager(object): newconfig = read_config(path, spec, checks={'mail_container': mail_container, 'force_list': force_list, + 'align': align_mode, + 'attrtriple': attr_triple, 'gpg_key_hint': gpg_key}) self._config.merge(newconfig) @@ -90,7 +93,11 @@ class SettingsManager(object): raise ConfigError(err_msg % (themestring, themes_dir)) else: theme_path = os.path.join(themes_dir, themestring) - self._theme = Theme(theme_path) + try: + self._theme = Theme(theme_path) + except ConfigError as e: + err_msg = 'Theme file %s failed validation:\n' + raise ConfigError((err_msg % themestring) + e.message) self._accounts = self._parse_accounts(self._config) self._accountmap = self._account_table(self._accounts) @@ -213,7 +220,7 @@ class SettingsManager(object): value = fallback return value - def get_theming_attribute(self, mode, name): + def get_theming_attribute(self, mode, name, part=None): """ looks up theming attribute @@ -221,54 +228,85 @@ class SettingsManager(object): :type mode: str :param name: identifier of the atttribute :type name: str + :rtype: urwid.AttrSpec """ colours = int(self._config.get('colourmode')) - return self._theme.get_attribute(mode, name, colours) + return self._theme.get_attribute(colours, mode, name, part) - def get_tagstring_representation(self, tag): + def get_threadline_theming(self, thread): """ - looks up user's preferred way to represent a given tagstring + looks up theming info a threadline displaying a given thread. This + wraps around :meth:`~alot.settings.theme.Theme.get_threadline_theming`, + filling in the current colour mode. - This returns a dictionary mapping - 'normal' and 'focussed' to `urwid.AttrSpec` sttributes, - and 'translated' to an alternative string representation + :param thread: thread to theme + :type thread: alot.db.thread.Thread """ colours = int(self._config.get('colourmode')) - # default attributes: normal and focussed - default = self._theme.get_attribute('global', 'tag', colours) - default_f = self._theme.get_attribute('global', 'tag_focus', colours) - for sec in self._config['tags'].sections: - if re.match('^' + sec + '$', tag): - fg = self._config['tags'][sec]['fg'] or default.foreground - bg = self._config['tags'][sec]['bg'] or default.background - try: - normal = urwid.AttrSpec(fg, bg, colours) - except AttrSpecError: - normal = default - focus_fg = self._config['tags'][sec]['focus_fg'] - focus_fg = focus_fg or default_f.foreground - focus_bg = self._config['tags'][sec]['focus_bg'] - focus_bg = focus_bg or default_f.background - try: - focussed = urwid.AttrSpec(focus_fg, focus_bg, colours) - except AttrSpecError: - focussed = default_f + return self._theme.get_threadline_theming(thread, colours) - hidden = self._config['tags'][sec]['hidden'] or False + def get_tagstring_representation(self, tag, onebelow_normal=None, + onebelow_focus=None): + """ + looks up user's preferred way to represent a given tagstring. - translated = self._config['tags'][sec]['translated'] or tag - translation = self._config['tags'][sec]['translation'] + :param tag: tagstring + :type tag: str + :param onebelow_normal: attribute that shines through if unfocussed + :type onebelow_normal: urwid.AttrSpec + :param onebelow_focus: attribute that shines through if focussed + :type onebelow_focus: urwid.AttrSpec + + If `onebelow_normal` or `onebelow_focus` is given these attributes will + be used as fallbacks for fg/bg values '' and 'default'. + + This returns a dictionary mapping + :normal: to :class:`urwid.AttrSpec` used if unfocussed + :focussed: to :class:`urwid.AttrSpec` used if focussed + :translated: to an alternative string representation + """ + colourmode = int(self._config.get('colourmode')) + theme = self._theme + cfg = self._config + colours = [1, 16, 256] + + def colourpick(triple): + """ pick attribute from triple (mono,16c,256c) according to current + colourmode""" + if triple is None: + return None + return triple[colours.index(colourmode)] + + # global default attributes for tagstrings. + # These could contain values '' and 'default' which we interpret as + # "use the values from the widget below" + default_normal = theme.get_attribute(colourmode, 'global', 'tag') + default_focus = theme.get_attribute(colourmode, 'global', 'tag_focus') + + # local defaults for tagstring attributes. depend on next lower widget + fallback_normal = resolve_att(onebelow_normal, default_normal) + fallback_focus = resolve_att(onebelow_focus, default_focus) + + for sec in cfg['tags'].sections: + if re.match('^' + sec + '$', tag): + normal = resolve_att(colourpick(cfg['tags'][sec]['normal']), + fallback_normal) + focus = resolve_att(colourpick(cfg['tags'][sec]['focus']), + fallback_focus) + + translated = cfg['tags'][sec]['translated'] + if translated is None: + translated = tag + translation = cfg['tags'][sec]['translation'] if translation: translated = re.sub(translation[0], translation[1], tag) break else: - normal = default - focussed = default_f - hidden = False + normal = fallback_normal + focus = fallback_focus translated = tag - return {'normal': normal, 'focussed': focussed, - 'hidden': hidden, 'translated': translated} + return {'normal': normal, 'focussed': focus, 'translated': translated} def get_hook(self, key): """return hook (`callable`) identified by `key`""" diff --git a/alot/settings/checks.py b/alot/settings/checks.py index eab1253d..76d22780 100644 --- a/alot/settings/checks.py +++ b/alot/settings/checks.py @@ -3,15 +3,88 @@ # For further details see the COPYING file import mailbox import re +from urwid import AttrSpec, AttrSpecError from urlparse import urlparse from validate import VdtTypeError from validate import is_list -from validate import ValidateError +from validate import ValidateError, VdtValueTooLongError, VdtValueError from alot import crypto from alot.errors import GPGProblem +def attr_triple(value): + """ + Check that interprets the value as `urwid.AttrSpec` triple for the colour + modes 1,16 and 256. It assumes a <6 tuple of attribute strings for + mono foreground, mono background, 16c fg, 16c bg, 256 fg and 256 bg + respectively. If any of these are missing, we downgrade to the next + lower available pair, defaulting to 'default'. + + :raises: VdtValueTooLongError, VdtTypeError + :rtype: triple of `urwid.AttrSpec` + """ + keys = ['dfg', 'dbg', '1fg', '1bg', '16fg', '16bg', '256fg', '256bg'] + acc = {} + if not isinstance(value, (list, tuple)): + value = value, + if len(value) > 6: + raise VdtValueTooLongError(value) + # ensure we have exactly 6 attribute strings + attrstrings = (value + (6 - len(value)) * [None])[:6] + # add fallbacks for the empty list + attrstrings = (2 * ['default']) + attrstrings + for i, value in enumerate(attrstrings): + if value: + acc[keys[i]] = value + else: + acc[keys[i]] = acc[keys[i - 2]] + try: + mono = AttrSpec(acc['1fg'], acc['1bg'], 1) + normal = AttrSpec(acc['16fg'], acc['16bg'], 16) + high = AttrSpec(acc['256fg'], acc['256bg'], 256) + except AttrSpecError, e: + raise ValidateError(e.message) + return mono, normal, high + + +def align_mode(value): + """ + test if value is one of 'left', 'right' or 'center' + """ + if value not in ['left', 'right', 'center']: + raise VdtValueError + return value + + +def width_tuple(value): + """ + test if value is a valid width indicator (for a sub-widget in a column). + This can either be + ('fit', min, max): use the length actually needed for the content, padded + to use at least width min, and cut of at width max. + Here, min and max are positive integers or 0 to disable + the boundary. + ('weight',n): have it relative weight of n compared to other columns. + Here, n is an int. + """ + if value is None: + res = 'fit', 0, 0 + elif not isinstance(value, (list, tuple)): + raise VdtTypeError(value) + elif value[0] not in ['fit', 'weight']: + raise VdtTypeError(value) + if value[0] == 'fit': + if not isinstance(value[1], int) or not isinstance(value[2], int): + VdtTypeError(value) + res = 'fit', int(value[1]), int(value[2]) + else: + if not isinstance(value[1], int): + VdtTypeError(value) + res = 'weight', int(value[1]) + return res + + def mail_container(value): """ Check that the value points to a valid mail container, diff --git a/alot/settings/errors.py b/alot/settings/errors.py index 9095e897..606f78e1 100644 --- a/alot/settings/errors.py +++ b/alot/settings/errors.py @@ -1,6 +1,8 @@ # 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 + + class ConfigError(Exception): """could not parse user config""" pass diff --git a/alot/settings/theme.py b/alot/settings/theme.py index c6c36708..78f86fc5 100644 --- a/alot/settings/theme.py +++ b/alot/settings/theme.py @@ -2,9 +2,12 @@ # This file is released under the GNU GPL, version 3 or a later revision. # For further details see the COPYING file import os -from urwid import AttrSpec, AttrSpecError from utils import read_config +from checks import align_mode +from checks import attr_triple +from checks import width_tuple +from checks import force_list from errors import ConfigError DEFAULTSPATH = os.path.join(os.path.dirname(__file__), '..', 'defaults') @@ -19,55 +22,110 @@ class Theme(object): :raises: :class:`~alot.settings.errors.ConfigError` """ self._spec = os.path.join(DEFAULTSPATH, 'theme.spec') - self._config = read_config(path, self._spec) - self.attributes = self._parse_attributes(self._config) + self._config = read_config(path, self._spec, + checks={'align': align_mode, + 'widthtuple': width_tuple, + 'force_list': force_list, + 'attrtriple': attr_triple}) + self._colours = [1, 16, 256] + # make sure every entry in 'order' lists have their own subsections + threadline = self._config['search']['threadline'] + for sec in self._config['search']: + if sec.startswith('threadline'): + tline = self._config['search'][sec] + if tline['parts'] is not None: + listed = set(tline['parts']) + here = set(tline.sections) + indefault = set(threadline.sections) + diff = listed.difference(here.union(indefault)) + if diff: + msg = 'missing threadline parts: %s' % ', '.join(diff) + raise ConfigError(msg) - def _parse_attributes(self, c): - """ - parse a (previously validated) valid theme file - into urwid AttrSpec attributes for internal use. - - :param c: config object for theme file - :type c: `configobj.ConfigObj` - :raises: `ConfigError` - """ - - attributes = {} - for sec in c.sections: - try: - colours = int(sec) - except ValueError: - err_msg = 'section name %s is not a valid colour mode' - raise ConfigError(err_msg % sec) - attributes[colours] = {} - for mode in c[sec].sections: - attributes[colours][mode] = {} - for themable in c[sec][mode].sections: - block = c[sec][mode][themable] - fg = block['fg'] - if colours == 1: - bg = 'default' - else: - bg = block['bg'] - if colours == 256: - fg = fg or c['16'][mode][themable][fg] - bg = bg or c['16'][mode][themable][bg] - try: - att = AttrSpec(fg, bg, colours) - except AttrSpecError, e: - raise ConfigError(e) - attributes[colours][mode][themable] = att - return attributes - - def get_attribute(self, mode, name, colourmode): + def get_attribute(self, colourmode, mode, name, part=None): """ returns requested attribute :param mode: ui-mode (e.g. `search`,`thread`...) :type mode: str - :param name: identifier of the atttribute + :param name: of the atttribute :type name: str :param colourmode: colour mode; in [1, 16, 256] :type colourmode: int + :rtype: urwid.AttrSpec """ - return self.attributes[colourmode][mode][name] + thmble = self._config[mode][name] + if part is not None: + thmble = thmble[part] + return thmble[self._colours.index(colourmode)] + + def get_threadline_theming(self, thread, colourmode): + """ + look up how to display a Threadline wiidget in search mode + for a given thread. + + :param thread: Thread to theme Threadline for + :type thread: alot.db.thread.Thread + :param colourmode: colourmode to use, one of 1,16,256. + :type colourmode: int + + This will return a dict mapping + :normal: to `urwid.AttrSpec`, + :focus: to `urwid.AttrSpec`, + :parts: to a list of strings indentifying subwidgets + to be displayed in this order. + + Moreover, for every part listed this will map 'part' to a dict mapping + :normal: to `urwid.AttrSpec`, + :focus: to `urwid.AttrSpec`, + :width: to a tuple indicating the width of the subpart. + This is either `('fit', min, max)` to force the widget + to be at least `min` and at most `max` characters wide, + or `('weight', n)` which makes it share remaining space + with other 'weight' parts. + :alignment: where to place the content if shorter than the widget. + This is either 'right', 'left' or 'center'. + """ + def pickcolour(triple): + return triple[self._colours.index(colourmode)] + + def matches(sec, thread): + if sec.get('tagged_with') is not None: + if not set(sec['tagged_with']).issubset(thread.get_tags()): + return False + if sec.get('query') is not None: + if not thread.matches(sec['query']): + return False + return True + + default = self._config['search']['threadline'] + match = default + + candidates = self._config['search'].sections + for candidatename in candidates: + candidate = self._config['search'][candidatename] + if candidatename.startswith('threadline') and\ + (not candidatename == 'threadline') and\ + matches(candidate, thread): + match = candidate + break + + # fill in values + res = {} + res['normal'] = pickcolour(match.get('normal') or default['normal']) + res['focus'] = pickcolour(match.get('focus') or default['focus']) + res['parts'] = match.get('parts') or default['parts'] + for part in res['parts']: + defaultsec = default.get(part) + partsec = match.get(part) + + def fill(key, fallback=None): + pvalue = partsec.get(key) or defaultsec.get(key) + return pvalue or fallback + + res[part] = {} + res[part]['width'] = fill('width', ('fit', 0, 0)) + res[part]['alignment'] = fill('alignment', 'right') + res[part]['normal'] = pickcolour(fill('normal')) + res[part]['focus'] = pickcolour(fill('focus')) + return res diff --git a/alot/settings/utils.py b/alot/settings/utils.py index 6667fa67..f6912f98 100644 --- a/alot/settings/utils.py +++ b/alot/settings/utils.py @@ -4,6 +4,7 @@ from configobj import ConfigObj, ConfigObjError, flatten_errors from validate import Validator from errors import ConfigError +from urwid import AttrSpec def read_config(configpath=None, specpath=None, checks={}): @@ -23,22 +24,49 @@ def read_config(configpath=None, specpath=None, checks={}): try: config = ConfigObj(infile=configpath, configspec=specpath, file_error=True, encoding='UTF8') - except (ConfigObjError, IOError), e: - raise ConfigError('Could not read "%s": %s' % (configpath, e)) + except (ConfigObjError, IOError): + raise ConfigError('Couls not read %s' % configpath) + except UnboundLocalError: + # this works around a bug in configobj + msg = '%s is malformed. Check for sections without parents..' + raise ConfigError(msg % configpath) if specpath: validator = Validator() validator.functions.update(checks) - results = config.validate(validator) + try: + results = config.validate(validator, preserve_errors=True) + except ConfigObjError as e: + raise ConfigError(e.message) + if results != True: - error_msg = 'Validation errors occurred:\n' - for (section_list, key, _) in flatten_errors(config, results): + error_msg = '' + for (section_list, key, res) in flatten_errors(config, results): if key is not None: - msg = 'key "%s" in section "%s" failed validation' - msg = msg % (key, ', '.join(section_list)) + if res == False: + msg = 'key "%s" in section "%s" is missing.' + msg = msg % (key, ', '.join(section_list)) + else: + msg = 'key "%s" in section "%s" failed validation: %s' + msg = msg % (key, ', '.join(section_list), res) else: - msg = 'section "%s" is malformed' % ', '.join(section_list) + msg = 'section "%s" is missing' % '.'.join(section_list) error_msg += msg + '\n' raise ConfigError(error_msg) return config + + +def resolve_att(a, fallback): + """ replace '' and 'default' by fallback values """ + if a is None: + return fallback + if a.background in ['default', '']: + bg = fallback.background + else: + bg = a.background + if a.foreground in ['default', '']: + fg = fallback.foreground + else: + fg = a.foreground + return AttrSpec(fg, bg) @@ -4,7 +4,6 @@ import urwid import logging from twisted.internet import reactor, defer -import sys from settings import settings from buffers import BufferlistBuffer diff --git a/alot/widgets.py b/alot/widgets.py index e084d0bf..16d4b53b 100644 --- a/alot/widgets.py +++ b/alot/widgets.py @@ -10,10 +10,21 @@ from alot.helper import tag_cmp from alot.helper import string_decode import alot.db.message as message from alot.db.attachment import Attachment -import time 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) @@ -61,94 +72,86 @@ class ThreadlineWidget(urwid.AttrMap): """ selectable line widget that represents a :class:`~alot.db.Thread` in the :class:`~alot.buffers.SearchBuffer`. - - Respected settings: - * `general.display_content_in_threadline` - * `general.timestamp_format` - * `general.authors_maxlength` - Theme settings: - * `search_thread, search_thread_focus` - * `search_thread_date, search_thread_date_focus` - * `search_thread_mailcount, search_thread_mailcount_focus` - * `search_thread_authors, search_thread_authors_focus` - * `search_thread_subject, search_thread_subject_focus` - * `search_thread_content, search_thread_content_focus` """ - # The pretty_datetime needs 9 characters, but only 8 if locale - # doesn't use am/pm (in which case "jan 2012" is the longest) - pretty_datetime_len = 8 if len(time.strftime("%P")) == 0 else 9 - def __init__(self, tid, dbman): self.dbman = dbman - #logging.debug('tid: %s' % tid) self.thread = dbman.get_thread(tid) - #logging.debug('tid: %s' % self.thread) self.tag_widgets = [] self.display_content = settings.get('display_content_in_threadline') + self.structure = None self.rebuild() - normal = settings.get_theming_attribute('search', 'thread') - focussed = settings.get_theming_attribute('search', 'thread_focus') + normal = self.structure['normal'] + focussed = self.structure['focus'] urwid.AttrMap.__init__(self, self.columns, normal, focussed) - def rebuild(self): - cols = [] - if self.thread: - newest = self.thread.get_newest_date() - else: + 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 - if newest == None: datestring = '' - else: - datestring = settings.represent_datetime(newest) - datestring = datestring.rjust(self.pretty_datetime_len) - self.date_w = urwid.AttrMap(urwid.Text(datestring), - self._get_theme('date')) - cols.append(('fixed', len(datestring), self.date_w)) - - if self.thread: - mailcountstring = "(%d)" % self.thread.get_total_messages() - else: - mailcountstring = "(?)" - self.mailcount_w = urwid.AttrMap(urwid.Text(mailcountstring), - self._get_theme('mailcount')) - cols.append(('fixed', len(mailcountstring), self.mailcount_w)) - - if self.thread: - self.tag_widgets = [TagWidget(t) - for t in self.thread.get_tags()] - else: - self.tag_widgets = [] - self.tag_widgets.sort(tag_cmp, - lambda tag_widget: tag_widget.translated) - for tag_widget in self.tag_widgets: - if not tag_widget.hidden: - cols.append(('fixed', tag_widget.width(), tag_widget)) + 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']) - if self.thread: - authors = self.thread.get_authors_string() or '(None)' - else: - authors = '(None)' - maxlength = settings.get('authors_maxlength') - authorsstring = shorten_author_string(authors, maxlength) - self.authors_w = urwid.AttrMap(urwid.Text(authorsstring), - self._get_theme('authors')) - cols.append(('fixed', len(authorsstring), self.authors_w)) - - if self.thread: - subjectstring = self.thread.get_subject() or '' - else: - subjectstring = '' - # sanitize subject string: - subjectstring = subjectstring.replace('\n', ' ') - subjectstring = subjectstring.replace('\r', '') - subjectstring = subjectstring.strip() - - self.subject_w = urwid.AttrMap(urwid.Text(subjectstring, wrap='clip'), - self._get_theme('subject')) - if subjectstring: - cols.append(('weight', 2, self.subject_w)) - - if self.display_content: + 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: @@ -156,39 +159,68 @@ class ThreadlineWidget(urwid.AttrMap): # 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 = lastcontent.replace('\n', ' ').strip() - self.content_w = urwid.AttrMap(urwid.Text( + contentstring = pad(lastcontent.replace('\n', ' ').strip()) + content_w = AttrFlipWidget(urwid.Text( contentstring, wrap='clip'), - self._get_theme('content')) - cols.append(self.content_w) + 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 - self.columns = urwid.Columns(cols, dividechars=1) + 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): - if focus: - self.date_w.set_attr_map({None: self._get_theme('date', focus)}) - self.mailcount_w.set_attr_map({None: - self._get_theme('mailcount', focus)}) - for tw in self.tag_widgets: - tw.set_focussed() - self.authors_w.set_attr_map({None: self._get_theme('authors', - focus)}) - self.subject_w.set_attr_map({None: self._get_theme('subject', - focus)}) - if self.display_content: - self.content_w.set_attr_map( - {None: self._get_theme('content', focus=True)}) - else: - self.date_w.set_attr_map({None: self._get_theme('date')}) - self.mailcount_w.set_attr_map({None: self._get_theme('mailcount')}) - for tw in self.tag_widgets: - tw.set_unfocussed() - self.authors_w.set_attr_map({None: self._get_theme('authors')}) - self.subject_w.set_attr_map({None: self._get_theme('subject')}) - if self.display_content: - self.content_w.set_attr_map({None: self._get_theme('content')}) + for w in self.widgets: + w.set_map('focus' if focus else 'normal') return urwid.AttrMap.render(self, size, focus) def selectable(self): @@ -201,10 +233,12 @@ class ThreadlineWidget(urwid.AttrMap): return self.thread def _get_theme(self, component, focus=False): - attr_key = 'thread_{0}'.format(component) + path = ['search', 'threadline', component] if focus: - attr_key += '_focus' - return settings.get_theming_attribute('search', attr_key) + path.append('focus') + else: + path.append('normal') + return settings.get_theming_attribute(path) class BufferlineWidget(urwid.Text): @@ -232,20 +266,24 @@ class TagWidget(urwid.AttrMap): """ text widget that renders a tagstring. - It looks up the string it displays in the `tag-translate` section - of the config as well as custom theme settings for its tag. The - tag may also be configured as hidden, which users of this widget - should honour. + 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): + def __init__(self, tag, fallback_normal=None, fallback_focus=None): self.tag = tag - representation = settings.get_tagstring_representation(tag) - self.hidden = representation['hidden'] + 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') - self.normal_att = representation['normal'] - self.focus_att = representation['focussed'] - urwid.AttrMap.__init__(self, self.txt, self.normal_att, self.focus_att) + 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 @@ -262,10 +300,10 @@ class TagWidget(urwid.AttrMap): return self.tag def set_focussed(self): - self.set_attr_map({None: self.focus_att}) + self.set_attr_map(self.attmap['focus']) def set_unfocussed(self): - self.set_attr_map({None: self.normal_att}) + self.set_attr_map(self.attmap['normal']) class ChoiceWidget(urwid.Text): @@ -361,9 +399,6 @@ class CompleteEdit(urwid.Edit): class MessageWidget(urwid.WidgetWrap): """ Flow widget that renders a :class:`~alot.db.message.Message`. - - Respected settings: - * `general.displayed_headers` """ #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 @@ -410,6 +445,12 @@ class MessageWidget(urwid.WidgetWrap): 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) @@ -444,12 +485,15 @@ class MessageWidget(urwid.WidgetWrap): bc = list() # box_columns if self.depth > 1: bc.append(0) - cols.append(self._get_spacer(self.bars_at[1:-1])) + spacer = self._get_spacer(self.bars_at[1:-1]) + cols.append(spacer) if self.depth > 0: if self.bars_at[-1]: - arrowhead = u'\u251c\u25b6' + arrowhead = [(self.arrow_bars_att, u'\u251c'), + (self.arrow_heads_att, u'\u25b6')] else: - arrowhead = u'\u2514\u25b6' + 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) @@ -475,10 +519,10 @@ class MessageWidget(urwid.WidgetWrap): lines = [] for key in self._displayed_headers: if key in mail: - if key.lower() in ['cc','bcc', 'to']: + if key.lower() in ['cc', 'bcc', 'to']: values = mail.get_all(key) - dvalues = [decode_header(v, normalize=norm) for v in values] - lines.append((key, ', '.join(dvalues))) + 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) @@ -548,6 +592,7 @@ class MessageWidget(urwid.WidgetWrap): 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): @@ -555,7 +600,8 @@ class MessageWidget(urwid.WidgetWrap): aligner = u'\u2502' else: aligner = ' ' - return ('fixed', 1, urwid.SolidFill(aligner)) + aligner = urwid.SolidFill(aligner) + return ('fixed', 1, urwid.AttrMap(aligner, self.arrow_bars_att)) def selectable(self): return True @@ -575,11 +621,6 @@ class MessageWidget(urwid.WidgetWrap): class MessageSummaryWidget(urwid.WidgetWrap): """ one line summary of a :class:`~alot.db.message.Message`. - - Theme settings: - * `thread_summary_even` - * `thread_summary_odd` - * `thread_summary_focus` """ def __init__(self, message, even=True): @@ -592,9 +633,11 @@ class MessageSummaryWidget(urwid.WidgetWrap): self.message = message self.even = even if even: - attr = settings.get_theming_attribute('thread', 'summary_even') + attr = settings.get_theming_attribute('thread', 'summary', 'even') else: - attr = settings.get_theming_attribute('thread', 'summary_odd') + attr = settings.get_theming_attribute('thread', 'summary', 'odd') + focus_att = settings.get_theming_attribute('thread', 'summary', + 'focus') cols = [] sumstr = self.__str__() @@ -603,12 +646,11 @@ class MessageSummaryWidget(urwid.WidgetWrap): thread_tags = message.get_thread().get_tags(intersection=True) outstanding_tags = set(message.get_tags()).difference(thread_tags) - tag_widgets = [TagWidget(t) for t in outstanding_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)) - focus_att = settings.get_theming_attribute('thread', 'summary_focus') line = urwid.AttrMap(urwid.Columns(cols, dividechars=1), attr, focus_att) urwid.WidgetWrap.__init__(self, line) @@ -662,9 +704,6 @@ class HeadersList(urwid.WidgetWrap): class MessageBodyWidget(urwid.AttrMap): """ displays printable parts of an email - - Theme settings: - * `thread_body` """ def __init__(self, msg): @@ -676,10 +715,6 @@ class MessageBodyWidget(urwid.AttrMap): class AttachmentWidget(urwid.WidgetWrap): """ one-line summary of an :class:`~alot.db.attachment.Attachment`. - - Theme settings: - * `thread_attachment` - * `thread_attachment_focus` """ def __init__(self, attachment, selectable=True): self._selectable = selectable diff --git a/docs/source/configuration/index.rst b/docs/source/configuration/index.rst index b14af2e1..b714133f 100644 --- a/docs/source/configuration/index.rst +++ b/docs/source/configuration/index.rst @@ -273,63 +273,142 @@ Alot can be run in 1, 16 or 256 colour mode. The requested mode is determined by from option `colourmode` config value. The default is 256, which scales down depending on how many colours your terminal supports. -To specify the theme to use, set the `theme` config option to the name of a theme-file. +To specify the theme to use, set the :ref:`theme <theme>` config option to the name of a theme-file. A file by that name will be looked up in the path given by the :ref:`themes_dir <themes-dir>` config setting which defaults to :file:`~/.config/alot/themes/`. -Theme-files can contain sections `[1], [16]` and `[256]` for different colour modes, -each of which has subsections named after the :ref:`MODE <modes>` they are used in -plus "help" for the bindings-help overlay and "global" for globally used themables -like footer, prompt etc. -The themables live in sub-sub-sections and define the attributes `fg` and `bg` for foreground -and backround colours and attributes, the names of the themables should be self-explanatory. -Have a look at the default theme file at :file:`alot/defaults/default.theme` -and the config spec :file:`alot/defaults/default.theme` for the format. +.. _config.theming.themefiles: + +Theme Files +----------- +contain a section for each :ref:`MODE <modes>` plus "help" for the bindings-help overlay +and "global" for globally used themables like footer, prompt etc. +Each such section defines colour :ref:`attributes <config.theming.attributes>` for the parts that +can be themed. The names of the themables should be self-explanatory. +Have a look at the default theme file at :file:`alot/defaults/default.theme` and the config spec +:file:`alot/defaults/default.theme` for the exact format. + +.. _config.theming.attributes: + +Colour Attributes +----------------- +Attributes are *sextuples* of `urwid Attribute strings <http://excess.org/urwid/wiki/DisplayAttributes>`__ +that specify foreground and background for mono, 16 and 256-colour modes respectively. +For mono-mode only the flags `blink`, `standup`, `underline` and `bold` are available, +16c mode supports these in combination with the colour names:: + + brown dark red dark magenta dark blue dark cyan dark green + yellow light red light magenta light blue light cyan light green + black dark gray light gray white + +In high-colour mode, you may use the above plus grayscales `g0` to `g100` and +colour codes given as `#` followed by three hex values. +See `here <http://excess.org/urwid/wiki/DisplayAttributes>`__ +and `here <http://excess.org/urwid/reference.html#AttrSpec>`__ +for more details on the interpreted values. A colour picker that makes choosing colours easy can be +found in :file:`alot/extra/colour_picker.py`. As an example, check the setting below that makes the footer line appear as underlined bold red text on a bright green background:: - [256] - [[global]] - [[[footer]]] - fg = 'light red, bold, underline' - bg = '#8f6' - -Values can be colour names (`light red`, `dark green`..), RGB colour codes (e.g. `#868`), -font attributes (`bold`, `underline`, `blink`, `standout`) or a comma separated combination of -colour and font attributes. - -.. note:: In monochromatic mode only the entry `fg` is interpreted. It may only contain - (a comma-separated list of) font attributes: 'bold', 'underline', 'blink', 'standout'. + [[global]] + #name mono fg mono bg 16c fg 16c bg 256c fg 256c bg + # | | | | | | + # v v v v v v + footer = 'bold,underline', '', 'light red, bold, underline', 'light green', 'light red, bold, underline', '#8f6' + +Highlighting Thread lines in Search Mode +---------------------------------------- +The subsection '[[threadline]]' of the '[search]' section in :ref:`Theme Files <config.theming.themefiles>` +determines how to present a thread: here, :ref:`attributes <config.theming.attributes>` 'normal' and +'focus' provide fallback/spacer themes and 'parts' is a (string) list of displayed subwidgets. +Possible part strings are: + +* date +* mailcount +* tags +* authors +* subject + +For every listed part there must be a subsection with the same name, defining + +:normal: :ref:`attribute <config.theming.attributes>` used for this part if unfocussed +:focus: :ref:`attribute <config.theming.attributes>` used for this part if focussed +:width: tuple indicating the width of the part. This is either `('fit', min, max)` to force the widget + to be at least `min` and at most `max` characters wide, + or `('weight', n)` which makes it share remaining space + with other 'weight' parts. +:alignment: how to place the content string if the widget space is larger. + This must be one of 'right', 'left' or 'center'. + +To "highlight" some thread lines (use different attributes than the defaults found in the +'[[threadline]]' section), one can define sections with prefix 'threadline'. +Each one of those can redefine any part of the structure outlined above, the rest defaults to +values defined in '[[threadline]]'. + +The section used to theme a particular thread is the first one (in file-order) that matches +the criteria defined by its 'query' and 'taggeswith' values: + +* If 'query' is defined, the thread must match that querystring. +* If 'tagged_with' is defined, is value (string list) must be a subset of the accumulated tags of all messages in the thread. + +.. note:: that 'tagged_with = A,B' is different from 'query = "is:A AND is:B"': + the latter will match only if the thread contains a single message that is both tagged with + A and B. + + Moreover, note that if both query and tagged_with is undefined, this section will always match + and thus overwrite the defaults. + +The example below shows how to highlight unread threads: +The date-part will be bold red if the thread has unread messages and flagged messages +and just bold if the thread has unread but no flagged messages:: + + [search] + # default threadline + [[threadline]] + normal = 'default','default','default','default','#6d6','default' + focus = 'standout','default','light gray','dark gray','white','#68a' + parts = date,mailcount,tags,authors,subject + [[[date]]] + normal = 'default','default','light gray','default','g58','default' + focus = 'standout','default','light gray','dark gray','g89','#68a' + width = 'fit',10,10 + ... -See `urwids docs on Attributes <http://excess.org/urwid/reference.html#AttrSpec>`_ for more details -on the interpreted values. Urwid provides a `neat colour picker script`_ that makes choosing -colours easy. + # highlight threads containing unread and flagged messages + [[threadline-flagged-unread]] + tagged_with = 'unread','flagged' + [[[date]]] + normal = 'default','default','light red,bold','default','light red,bold','default' -.. _neat colour picker script: http://excess.org/urwid/browser/palette_test.py + # highlight threads containing unread messages + [[threadline-unread]] + query = 'is:unread' + [[[date]]] + normal = 'default','default','light gray,bold','default','g58,bold','default' +.. _config.theming.tags: Custom Tagstring Formatting -=========================== +--------------------------- To specify how a particular tagstring is displayed throughout the interface you can add a subsection named after the tag to the `[tags]` config section. -The following attribute keys will interpreted and may contain urwid attribute strings -as described in the :ref:`Themes <themes>` section above: +Such a section may define -`fg` (foreground), `bg` (background), `focus_fg` (foreground if focused) and `focus_bg` (background if focused). -An alternative string representation is read from the option `translated` or can be given -as pair of strings in `translation`. - -The tag can also be hidden from view, if the key `hidden` is present and set to -True. The tag can still be seen in the taglist buffer. +:normal: :ref:`attribute <config.theming.attributes>` used if unfocussed +:focus: :ref:`attribute <config.theming.attributes>` used if focussed +:translated: fixed string representation for this tag. The tag can be hidden from view, + if the key `translated` is set to '', the empty string. +:translation: a pair of strings that define a regular substitution to compute the string + representation on the fly using `re.sub`. This only really makes sense if + one uses a regular expression to match more than one tagstring (see below). The following will make alot display the "todo" tag as "TODO" in white on red. :: [tags] [[todo]] - bg = '#d66' - fg = white + normal = '','', 'white','light red', 'white','#d66' translated = TODO Utf-8 symbols are welcome here, see e.g. @@ -337,13 +416,14 @@ http://panmental.de/symbols/info.htm for some fancy symbols. I personally displa like this:: [tags] + [[flagged]] translated = ⚑ - fg = light red + normal = '','','light red','','light red','' + focus = '','','light red','','light red','' [[unread]] translated = ✉ - fg = white [[replied]] translated = ⏎ @@ -358,12 +438,11 @@ rename a matching tagstring. `translation` takes a comma separated *pair* of str do the following:: [[notmuch::bug]] - fg = 'light red, bold' - bg = '#88d' - translated = 'nm:bug' + translated = 'nm:bug' + normal = "", "", "light red, bold", "light blue", "light red, bold", "#88d" + [[notmuch::.*]] - fg = '#fff' - bg = '#88d' - translation = 'notmuch::(.*)','nm:\1' + translation = 'notmuch::(.*)','nm:\1' + normal = "", "", "white", "light blue", "#fff", "#88d" .. _nmbug: http://notmuchmail.org/nmbug/ diff --git a/extra/colour_picker.py b/extra/colour_picker.py new file mode 100755 index 00000000..17fcf449 --- /dev/null +++ b/extra/colour_picker.py @@ -0,0 +1,259 @@ +#!/usr/bin/python +# +# COLOUR PICKER. +# This is a lightly modified version of urwids palette_test.py example script as +# found at https://raw.github.com/wardi/urwid/master/examples/palette_test.py +# +# This version simply omits resetting the screens default colour palette, +# and therefore displays the colour attributes as alot would render them in +# your terminal. +# +# Urwid Palette Test. Showing off highcolor support +# Copyright (C) 2004-2009 Ian Ward +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Urwid web site: http://excess.org/urwid/ + +""" +Palette test. Shows the available foreground and background settings +in monochrome, 16 color, 88 color and 256 color modes. +""" + +import re +import sys + +import urwid +import urwid.raw_display + +CHART_256 = """ +brown__ dark_red_ dark_magenta_ dark_blue_ dark_cyan_ dark_green_ +yellow_ light_red light_magenta light_blue light_cyan light_green + + #00f#06f#08f#0af#0df#0ff black_______ dark_gray___ + #60f#00d#06d#08d#0ad#0dd#0fd light_gray__ white_______ + #80f#60d#00a#06a#08a#0aa#0da#0fa + #a0f#80d#60a#008#068#088#0a8#0d8#0f8 + #d0f#a0d#80d#608#006#066#086#0a6#0d6#0f6 + #f0f#d0d#a0a#808#606#000#060#080#0a0#0d0#0f0#0f6#0f8#0fa#0fd#0ff + #f0d#d0a#a08#806#600#660#680#6a0#6d0#6f0#6f6#6f8#6fa#6fd#6ff#0df + #f0a#d08#a06#800#860#880#8a0#8d0#8f0#8f6#8f8#8fa#8fd#8ff#6df#0af + #f08#d06#a00#a60#a80#aa0#ad0#af0#af6#af8#afa#afd#aff#8df#6af#08f + #f06#d00#d60#d80#da0#dd0#df0#df6#df8#dfa#dfd#dff#adf#8af#68f#06f + #f00#f60#f80#fa0#fd0#ff0#ff6#ff8#ffa#ffd#fff#ddf#aaf#88f#66f#00f + #fd0#fd6#fd8#fda#fdd#fdf#daf#a8f#86f#60f + #66d#68d#6ad#6dd #fa0#fa6#fa8#faa#fad#faf#d8f#a6f#80f + #86d#66a#68a#6aa#6da #f80#f86#f88#f8a#f8d#f8f#d6f#a0f + #a6d#86a#668#688#6a8#6d8 #f60#f66#f68#f6a#f6d#f6f#d0f +#d6d#a6a#868#666#686#6a6#6d6#6d8#6da#6dd #f00#f06#f08#f0a#f0d#f0f + #d6a#a68#866#886#8a6#8d6#8d8#8da#8dd#6ad + #d68#a66#a86#aa6#ad6#ad8#ada#add#8ad#68d + #d66#d86#da6#dd6#dd8#dda#ddd#aad#88d#66d g78_g82_g85_g89_g93_g100 + #da6#da8#daa#dad#a8d#86d g52_g58_g62_g66_g70_g74_ + #88a#8aa #d86#d88#d8a#d8d#a6d g27_g31_g35_g38_g42_g46_g50_ + #a8a#888#8a8#8aa #d66#d68#d6a#d6d g0__g3__g7__g11_g15_g19_g23_ + #a88#aa8#aaa#88a + #a88#a8a +""" + +CHART_88 = """ +brown__ dark_red_ dark_magenta_ dark_blue_ dark_cyan_ dark_green_ +yellow_ light_red light_magenta light_blue light_cyan light_green + + #00f#08f#0cf#0ff black_______ dark_gray___ + #80f#00c#08c#0cc#0fc light_gray__ white_______ + #c0f#80c#008#088#0c8#0f8 +#f0f#c0c#808#000#080#0c0#0f0#0f8#0fc#0ff #88c#8cc + #f0c#c08#800#880#8c0#8f0#8f8#8fc#8ff#0cf #c8c#888#8c8#8cc + #f08#c00#c80#cc0#cf0#cf8#cfc#cff#8cf#08f #c88#cc8#ccc#88c + #f00#f80#fc0#ff0#ff8#ffc#fff#ccf#88f#00f #c88#c8c + #fc0#fc8#fcc#fcf#c8f#80f + #f80#f88#f8c#f8f#c0f g62_g74_g82_g89_g100 + #f00#f08#f0c#f0f g0__g19_g35_g46_g52 +""" + +CHART_16 = """ +brown__ dark_red_ dark_magenta_ dark_blue_ dark_cyan_ dark_green_ +yellow_ light_red light_magenta light_blue light_cyan light_green + +black_______ dark_gray___ light_gray__ white_______ +""" + +ATTR_RE = re.compile("(?P<whitespace>[ \n]*)(?P<entry>[^ \n]+)") +SHORT_ATTR = 4 # length of short high-colour descriptions which may +# be packed one after the next + +def parse_chart(chart, convert): + """ + Convert string chart into text markup with the correct attributes. + + chart -- palette chart as a string + convert -- function that converts a single palette entry to an + (attr, text) tuple, or None if no match is found + """ + out = [] + for match in re.finditer(ATTR_RE, chart): + if match.group('whitespace'): + out.append(match.group('whitespace')) + entry = match.group('entry') + entry = entry.replace("_", " ") + while entry: + # try the first four characters + attrtext = convert(entry[:SHORT_ATTR]) + if attrtext: + elen = SHORT_ATTR + entry = entry[SHORT_ATTR:].strip() + else: # try the whole thing + attrtext = convert(entry.strip()) + assert attrtext, "Invalid palette entry: %r" % entry + elen = len(entry) + entry = "" + attr, text = attrtext + out.append((attr, text.ljust(elen))) + return out + +def foreground_chart(chart, background, colors): + """ + Create text markup for a foreground colour chart + + chart -- palette chart as string + background -- colour to use for background of chart + colors -- number of colors (88 or 256) + """ + def convert_foreground(entry): + try: + attr = urwid.AttrSpec(entry, background, colors) + except urwid.AttrSpecError: + return None + return attr, entry + return parse_chart(chart, convert_foreground) + +def background_chart(chart, foreground, colors): + """ + Create text markup for a background colour chart + + chart -- palette chart as string + foreground -- colour to use for foreground of chart + colors -- number of colors (88 or 256) + + This will remap 8 <= colour < 16 to high-colour versions + in the hopes of greater compatibility + """ + def convert_background(entry): + try: + attr = urwid.AttrSpec(foreground, entry, colors) + except urwid.AttrSpecError: + return None + # fix 8 <= colour < 16 + if colors > 16 and attr.background_basic and \ + attr.background_number >= 8: + # use high-colour with same number + entry = 'h%d'%attr.background_number + attr = urwid.AttrSpec(foreground, entry, colors) + return attr, entry + return parse_chart(chart, convert_background) + + +def main(): + palette = [ + ('header', 'black,underline', 'light gray', 'standout,underline', + 'black,underline', '#88a'), + ('panel', 'light gray', 'dark blue', '', + '#ffd', '#00a'), + ('focus', 'light gray', 'dark cyan', 'standout', + '#ff8', '#806'), + ] + + screen = urwid.raw_display.Screen() + screen.register_palette(palette) + + lb = urwid.SimpleListWalker([]) + chart_offset = None # offset of chart in lb list + + mode_radio_buttons = [] + chart_radio_buttons = [] + + def fcs(widget): + # wrap widgets that can take focus + return urwid.AttrMap(widget, None, 'focus') + + def set_mode(colors, is_foreground_chart): + # set terminal mode and redraw chart + screen.set_terminal_properties(colors) + + chart_fn = (background_chart, foreground_chart)[is_foreground_chart] + if colors == 1: + lb[chart_offset] = urwid.Divider() + else: + chart = {16: CHART_16, 88: CHART_88, 256: CHART_256}[colors] + txt = chart_fn(chart, 'default', colors) + lb[chart_offset] = urwid.Text(txt, wrap='clip') + + def on_mode_change(rb, state, colors): + # if this radio button is checked + if state: + is_foreground_chart = chart_radio_buttons[0].state + set_mode(colors, is_foreground_chart) + + def mode_rb(text, colors, state=False): + # mode radio buttons + rb = urwid.RadioButton(mode_radio_buttons, text, state) + urwid.connect_signal(rb, 'change', on_mode_change, colors) + return fcs(rb) + + def on_chart_change(rb, state): + # handle foreground check box state change + set_mode(screen.colors, state) + + def click_exit(button): + raise urwid.ExitMainLoop() + + lb.extend([ + urwid.AttrMap(urwid.Text("Urwid Palette Test"), 'header'), + urwid.AttrMap(urwid.Columns([ + urwid.Pile([ + mode_rb("Monochrome", 1), + mode_rb("16-Color", 16, True), + mode_rb("88-Color", 88), + mode_rb("256-Color", 256),]), + urwid.Pile([ + fcs(urwid.RadioButton(chart_radio_buttons, + "Foreground Colors", True, on_chart_change)), + fcs(urwid.RadioButton(chart_radio_buttons, + "Background Colors")), + urwid.Divider(), + fcs(urwid.Button("Exit", click_exit)), + ]), + ]),'panel') + ]) + + chart_offset = len(lb) + lb.extend([ + urwid.Divider() # placeholder for the chart + ]) + + set_mode(16, True) # displays the chart + + def unhandled_input(key): + if key in ('Q','q','esc'): + raise urwid.ExitMainLoop() + + urwid.MainLoop(urwid.ListBox(lb), screen=screen, + unhandled_input=unhandled_input).run() + +if __name__ == "__main__": + main() + + diff --git a/extra/tagsections_convert.py b/extra/tagsections_convert.py new file mode 100755 index 00000000..3a73f60f --- /dev/null +++ b/extra/tagsections_convert.py @@ -0,0 +1,77 @@ +#!/usr/bin/python +""" + CONFIG CONVERTER + this script converts your custom tag string section from the v.3.1 syntax + to the current format. + + >>> tagsections_convert.py -o config.new config.old + + will convert your whole alot config safely to the new format. +""" + +from configobj import ConfigObj +import argparse +import sys +import re + + +def get_leaf_value(cfg, path, fallback=''): + if len(path) == 1: + if isinstance(cfg, ConfigObj): + if path[0] not in cfg.scalars: + return fallback + else: + return cfg[path[0]] + else: + if path[0] not in cfg: + return fallback + else: + return cfg[path[0]] + else: + if path[0] in cfg: + scfg = cfg[path[0]] + sp = path[1:] + return get_leaf_value(scfg, sp, fallback) + else: + return None + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='update alot theme files') + parser.add_argument('configfile', type=argparse.FileType('r'), + help='theme file to convert') + parser.add_argument('-o', type=argparse.FileType('w'), dest='out', + help='destination', default=sys.stdout) + args = parser.parse_args() + + cfg = ConfigObj(args.configfile) + out = args.out + print args + + def is_256(att): + r = r'(g\d{1,3}(?!\d))|(#[0-9A-Fa-f]{3}(?![0-9A-Fa-f]))' + return re.search(r, att) + + if 'tags' in cfg: + for tag in cfg['tags'].sections: + sec = cfg['tags'][tag] + att = [''] * 6 + + if 'fg' in sec: + fg = sec['fg'] + if not is_256(fg): + att[2] = fg + att[4] = fg + del(sec['fg']) + + if 'bg' in sec: + bg = sec['bg'] + if not is_256(bg): + att[3] = bg + att[5] = bg + del(sec['bg']) + sec['normal'] = att + + if sec.get('hidden'): + sec['translated'] = '' + cfg.write(out) diff --git a/extra/theme_convert.py b/extra/theme_convert.py new file mode 100755 index 00000000..20113be0 --- /dev/null +++ b/extra/theme_convert.py @@ -0,0 +1,124 @@ +#!/usr/bin/python +""" + THEME CONVERTER + this script converts your custom alot theme files from the v.3.1 syntax + to the current format. + + >>> theme_convert.py -o themefile.new themefile.old +""" + +from configobj import ConfigObj +import argparse +import sys + + +def get_leaf_value(cfg, path, fallback=''): + if len(path) == 1: + if isinstance(cfg, ConfigObj): + if path[0] not in cfg.scalars: + return fallback + else: + return cfg[path[0]] + else: + if path[0] not in cfg: + return fallback + else: + return cfg[path[0]] + else: + if path[0] in cfg: + scfg = cfg[path[0]] + sp = path[1:] + return get_leaf_value(scfg, sp, fallback) + else: + return None + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='update alot theme files') + parser.add_argument('themefile', type=argparse.FileType('r'), + help='theme file to convert') + parser.add_argument('-o', type=argparse.FileType('w'), dest='out', + help='destination', default=sys.stdout) + args = parser.parse_args() + + old = ConfigObj(args.themefile) + new = ConfigObj() + out = args.out + + def lookup(path): + values = [] + for c in ['1', '16', '256']: + values.append(get_leaf_value(old, [c] + path + ['fg']) or 'default') + values.append(get_leaf_value(old, [c] + path + ['bg']) or 'default') + return values + values = map(lambda s: '\'' + s + '\'', values) + return ','.join(values) + + for bmode in ['global', 'help', 'envelope']: + new[bmode] = {} + #out.write('[%s]\n' % bmode) + for themable in old['16'][bmode].sections: + new[bmode][themable] = lookup([bmode, themable]) + #out.write(' %s = %s\n' % (themable, lookup([bmode, themable]))) + + # BUFFERLIST + new['bufferlist'] = {} + new['bufferlist']['line_even'] = lookup(['bufferlist','results_even']) + new['bufferlist']['line_odd'] = lookup(['bufferlist','results_odd']) + new['bufferlist']['line_focus'] = lookup(['bufferlist','focus']) + + # TAGLIST + new['taglist'] = {} + new['taglist']['line_even'] = lookup(['bufferlist','results_even']) + new['taglist']['line_odd'] = lookup(['bufferlist','results_odd']) + new['taglist']['line_focus'] = lookup(['bufferlist','focus']) + + # SEARCH + new['search'] = {} + + new['search']['threadline'] = {} + new['search']['threadline']['normal'] = lookup(['search', 'thread']) + new['search']['threadline']['focus'] = lookup(['search', 'thread_focus']) + new['search']['threadline']['parts'] = ['date','mailcount','tags','authors','subject'] + + new['search']['threadline']['date'] = {} + new['search']['threadline']['date']['normal'] = lookup(['search', 'thread_date']) + new['search']['threadline']['date']['focus'] = lookup(['search', 'thread_date_focus']) + + new['search']['threadline']['mailcount'] = {} + new['search']['threadline']['mailcount']['normal'] = lookup(['search', 'thread_mailcount']) + new['search']['threadline']['mailcount']['focus'] = lookup(['search', 'thread_mailcount_focus']) + + new['search']['threadline']['tags'] = {} + new['search']['threadline']['tags']['normal'] = lookup(['search', 'thread_tags']) + new['search']['threadline']['tags']['focus'] = lookup(['search', 'thread_tags_focus']) + + new['search']['threadline']['authors'] = {} + new['search']['threadline']['authors']['normal'] = lookup(['search', 'thread_authors']) + new['search']['threadline']['authors']['focus'] = lookup(['search', 'thread_authors_focus']) + + new['search']['threadline']['subject'] = {} + new['search']['threadline']['subject']['normal'] = lookup(['search', 'thread_subject']) + new['search']['threadline']['subject']['focus'] = lookup(['search', 'thread_subject_focus']) + + new['search']['threadline']['content'] = {} + new['search']['threadline']['content']['normal'] = lookup(['search', 'thread_content']) + new['search']['threadline']['content']['focus'] = lookup(['search', 'thread_content_focus']) + + # THREAD + new['thread'] = {} + new['thread']['attachment'] = lookup(['thread','attachment']) + new['thread']['attachment_focus'] = lookup(['thread','attachment_focus']) + new['thread']['body'] = lookup(['thread','body']) + new['thread']['arrow_heads'] = lookup(['thread','body']) + new['thread']['arrow_bars'] = lookup(['thread','body']) + new['thread']['header'] = lookup(['thread','header']) + new['thread']['header_key'] = lookup(['thread','header_key']) + new['thread']['header_value'] = lookup(['thread','header_value']) + new['thread']['summary'] = {} + new['thread']['summary']['even'] = lookup(['thread','summary_even']) + new['thread']['summary']['odd'] = lookup(['thread','summary_odd']) + new['thread']['summary']['focus'] = lookup(['thread','summary_focus']) + + # write out + new.write(out) diff --git a/extra/themes/mutt b/extra/themes/mutt new file mode 100644 index 00000000..2a6c6b09 --- /dev/null +++ b/extra/themes/mutt @@ -0,0 +1,76 @@ +############################################################################### +# MUTT +# +# colour theme for alot. © 2012 Patrick Totzke, GNU GPL3+ +# https://github.com/pazz/alot +############################################################################### + +[global] + footer = 'standout,bold','','light green,bold','dark blue','light green,bold','dark blue' + body = '','','light gray','black','light gray','black' + notify_error = 'standout','','light gray','dark red','light gray','dark red' + notify_normal = '','','light gray','black','light gray','#68a' + prompt = '','','light gray','black','light gray','black' + tag = '','','yellow','','yellow','' + tag_focus = 'standout, bold','','yellow','','yellow','' +[help] + text = '','','light gray','dark gray','light gray','dark gray' + section = 'underline','','white,underline','dark gray','white,underline','dark gray' + title = 'standout','','white,underline','dark gray','white,underline','dark gray' +[bufferlist] + line_even = '','','light gray','black','light gray','black' + line_odd = '','','light gray','black','light gray','black' + line_focus = 'standout','','black','dark cyan','black','dark cyan' +[taglist] + line_even = '','','light gray','black','light gray','black' + line_odd = '','','light gray','black','light gray','black' + line_focus = 'standout','','black','dark cyan','black','dark cyan' +[thread] + arrow_heads = '','','dark red','black','dark red','black' + arrow_bars = '','','dark red','black','dark red','black' + attachment = '','','yellow,bold','black','yellow,bold','black' + attachment_focus = 'standout','','black','yellow','black','yellow' + body = '','','light gray','black','light gray','black' + header = '','','dark cyan','black','dark cyan','black' + header_key = '','','dark cyan','black','dark cyan','black' + header_value = '','','dark cyan','black','dark cyan','black' + + [[summary]] + even = '','','light gray','black','light gray','black' + odd = '','','light gray','black','light gray','black' + focus = 'standout','','black','dark cyan','black','dark cyan' + +[envelope] + body = '','','light gray','black','light gray','black' + header = '','','dark cyan','black','dark cyan','black' + header_key = '','','dark cyan','black','dark cyan','black' + header_value = '','','dark cyan','black','dark cyan','black' +[search] + [[threadline]] + normal = '','','light gray','black','light gray','black' + focus = 'standout','','black','dark cyan','black','dark cyan' + parts = date,authors,mailcount,subject,tags + [[[date]]] + normal = '','','light gray','black','light gray','black' + focus = 'standout','','black','dark cyan','black','dark cyan' + width = 'fit',10,10 + alignment = right + [[[mailcount]]] + normal = '','','light gray','black','light gray','black' + focus = 'standout','','black','dark cyan','black','dark cyan' + width = 'fit', 5,5 + [[[tags]]] + normal = '','','yellow','black','yellow','black' + focus = 'standout','','black','dark cyan','black','dark cyan' + [[[authors]]] + normal = '','','light gray','black','light gray','black' + focus = 'standout','','black','dark cyan','black','dark cyan' + width = 'fit',25,25 + [[[subject]]] + normal = '','','light gray','black','light gray','black' + focus = 'standout','','black','dark cyan','black','dark cyan' + width = 'weight', 1 + [[[content]]] + normal = '','','light gray','black','light gray','black' + focus = 'standout','','black','dark cyan','black','dark cyan' + width = 'weight', 1 diff --git a/extra/themes/screenshots/mutt.search.png b/extra/themes/screenshots/mutt.search.png Binary files differnew file mode 100644 index 00000000..517061e1 --- /dev/null +++ b/extra/themes/screenshots/mutt.search.png diff --git a/extra/themes/screenshots/mutt.thread.png b/extra/themes/screenshots/mutt.thread.png Binary files differnew file mode 100644 index 00000000..39b6566c --- /dev/null +++ b/extra/themes/screenshots/mutt.thread.png diff --git a/extra/themes/screenshots/solarized.search.png b/extra/themes/screenshots/solarized.search.png Binary files differnew file mode 100644 index 00000000..ea78e9ff --- /dev/null +++ b/extra/themes/screenshots/solarized.search.png diff --git a/extra/themes/screenshots/solarized.thread.png b/extra/themes/screenshots/solarized.thread.png Binary files differnew file mode 100644 index 00000000..4a0fe79a --- /dev/null +++ b/extra/themes/screenshots/solarized.thread.png diff --git a/extra/themes/screenshots/solarized_dark.search.png b/extra/themes/screenshots/solarized_dark.search.png Binary files differnew file mode 100644 index 00000000..33b71ea2 --- /dev/null +++ b/extra/themes/screenshots/solarized_dark.search.png diff --git a/extra/themes/screenshots/solarized_dark.thread.png b/extra/themes/screenshots/solarized_dark.thread.png Binary files differnew file mode 100644 index 00000000..03548cac --- /dev/null +++ b/extra/themes/screenshots/solarized_dark.thread.png diff --git a/extra/themes/solarized b/extra/themes/solarized new file mode 100644 index 00000000..11030e1c --- /dev/null +++ b/extra/themes/solarized @@ -0,0 +1,126 @@ +############################################################################### +# SOLARIZED DARK +# +# colour theme for alot. © 2012 Patrick Totzke, GNU GPL3+ +# http://ethanschoonover.com/solarized +# https://github.com/pazz/alot +############################################################################### +# +# Define mappings from solarized colour names to urwid attribute names for 16 +# and 256 colour modes. These work well assuming you use the solarized term +# colours via Xressources/Xdefaults. You might want to change this otherwise + +16_base03 = 'dark gray' +16_base02 = 'black' +16_base01 = 'light green' +16_base00 = 'yellow' +16_base0 = 'light blue' +16_base1 = 'light cyan' +16_base2 = 'light gray' +16_base3 = 'white' +16_yellow = 'brown' +16_orange = 'light red' +16_red = 'dark red' +16_magenta = 'dark magenta' +16_violet = 'light magenta' +16_blue = 'dark blue' +16_cyan = 'dark cyan' +16_green = 'dark green' + +# Use a slightly different mapping here to be able to use "bold" in 256c mode +256_base03 = 'dark gray' +256_base02 = 'black' +256_base01 = 'light green' +256_base00 = 'yellow' +256_base0 = 'g66' +256_base1 = 'g70' +256_base2 = 'light gray' +256_base3 = 'white' +256_yellow = 'brown' +256_orange = 'light red' +256_red = 'dark red' +256_magenta = 'dark magenta' +256_violet = 'light magenta' +256_blue = 'dark blue' +256_cyan = '#088' +256_green = 'dark green' + +# This is the actual alot theme +[global] + footer = 'standout','default','%(16_base01)s','%(16_base2)s','%(256_base01)s','%(256_base2)s' + body = 'default','default','%(16_base00)s','%(16_base3)s','%(256_base00)s','%(256_base3)s' + notify_error = 'standout','default','%(16_base3)s','%(16_red)s','%(256_base3)s','%(256_red)s' + notify_normal = 'default','default','%(16_base00)s','%(16_base2)s','%(256_base00)s','%(256_base2)s' + prompt = 'default','default','%(16_base00)s','%(16_base2)s','%(256_base00)s','%(256_base2)s' + tag = 'default','default','%(16_yellow)s','%(16_base3)s','%(256_yellow)s','%(256_base3)s' + tag_focus = 'standout','default','%(16_base3)s','%(16_yellow)s','%(256_base3)s','%(256_yellow)s' +[help] + text = 'default','default','light gray','dark gray','%(256_base00)s','%(256_base3)s' + section = 'underline','default','%(16_base01)s','%(16_base2)s','%(256_base01)s','%(256_base2)s' + title = 'standout','default','%(16_base01)s','%(16_base3)s','%(256_base01)s','%(256_base3)s' +[taglist] + line_focus = 'standout','default','%(16_base2)s','%(16_yellow)s','%(256_base2)s','%(256_yellow)s' + line_even = 'default','default','%(16_base00)s','%(16_base3)s','%(256_base00)s','%(256_base3)s' + line_odd = 'default','default','%(16_base00)s','%(16_base2)s','%(256_base00)s','%(256_base2)s' +[bufferlist] + line_focus = 'standout','default','%(16_base2)s','%(16_yellow)s','%(256_base2)s','%(256_yellow)s' + line_even = 'default','default','%(16_base00)s','%(16_base3)s','%(256_base00)s','%(256_base3)s' + line_odd = 'default','default','%(16_base00)s','%(16_base2)s','%(256_base00)s','%(256_base2)s' +[thread] + attachment = 'default','default','%(16_base00)s','%(16_base3)s','%(256_base00)s','%(256_base3)s' + attachment_focus = 'underline','default','%(16_base2)s','%(16_yellow)s','%(256_base2)s','%(256_yellow)s' + body = 'default','default','%(16_base00)s','%(16_base3)s','%(256_base00)s','%(256_base3)s' + arrow_bars = 'default','default','%(16_yellow)s','%(16_base3)s','%(256_yellow)s','%(256_base3)s' + arrow_heads = 'default','default','%(16_yellow)s','%(16_base3)s','%(256_yellow)s','%(256_base3)s' + header = 'default','default','%(16_base00)s','%(16_base2)s','%(256_base00)s','%(256_base2)s' + header_key = 'default','default','%(16_magenta)s','%(16_base2)s','%(256_magenta)s','%(256_base2)s' + header_value = 'default','default','%(16_blue)s','%(16_base2)s','%(256_blue)s','%(256_base2)s' + [[summary]] + even = 'default','default','%(16_base00)s','%(16_base2)s','%(256_base00)s','%(256_base2)s' + focus = 'standout','default','%(16_base3)s','%(16_yellow)s','%(256_base3)s','%(256_yellow)s' + odd = 'default','default','%(16_base00)s','%(16_base3)s','%(256_base00)s','%(256_base3)s' +[envelope] + body = 'default','default','%(16_base00)s','%(16_base3)s','%(256_base00)s','%(256_base3)s' + header = 'default','default','%(16_base00)s','%(16_base2)s','%(256_base00)s','%(256_base2)s' + header_key = 'default','default','%(16_orange)s','%(16_base2)s','%(256_orange)s','%(256_base2)s' + header_value = 'default','default','%(16_violet)s','%(16_base2)s','%(256_violet)s','%(256_base2)s' +[search] + [[threadline]] + normal = 'default','default','%(16_base01)s','%(16_base3)s','%(256_base01)s','%(256_base3)s' + focus = 'standout','default','%(16_base2)s','%(16_yellow)s','%(256_base2)s','%(256_yellow)s' + parts = date,mailcount,tags,authors,subject + [[[date]]] + normal = 'default','default','%(16_base01)s','%(16_base3)s','%(256_base01)s','%(256_base3)s' + focus = 'standout','default','%(16_base3)s','%(16_yellow)s','%(256_base3)s','%(256_yellow)s' + [[[mailcount]]] + normal = 'default','default','%(16_base01)s','%(16_base3)s','%(256_base01)s','%(256_base3)s' + focus = 'standout','default','%(16_base2)s','%(16_yellow)s','%(256_base2)s','%(256_yellow)s' + [[[tags]]] + normal = 'bold','default','%(16_yellow)s','%(16_base3)s','%(256_yellow)s','%(256_base3)s' + focus = 'standout','default','%(16_base3)s','%(16_yellow)s','%(256_base3)s','%(256_yellow)s' + [[[authors]]] + normal = 'default,underline','default','%(16_cyan)s','%(16_base3)s','%(256_cyan)s','%(256_base3)s' + focus = 'standout','default','%(16_base2)s','%(16_yellow)s','%(256_base2)s','%(256_yellow)s' + width = 'fit',0,30 + [[[subject]]] + normal = 'default','default','%(16_base00)s','%(16_base3)s','%(256_base00)s','%(256_base3)s' + focus = 'standout','default','%(16_base3)s','%(16_yellow)s','%(256_base3)s','%(256_yellow)s' + width = 'weight',1 + [[[content]]] + normal = 'default','default','%(16_base1)s','%(16_base3)s','%(256_base1)s','%(256_base3)s' + focus = 'standout','default','%(16_base2)s','%(16_yellow)s','%(256_base2)s','%(256_yellow)s' + [[threadline-unread]] + normal = 'default','default','%(16_base01)s,bold','%(16_base3)s','%(256_base01)s,bold','%(256_base3)s' + tagged_with = 'unread' + [[[date]]] + normal = 'default','default','%(16_base01)s,bold','%(16_base3)s','%(256_base01)s,bold','%(256_base3)s' + [[[mailcount]]] + normal = 'default','default','%(16_base01)s,bold','%(16_base3)s','%(256_base01)s,bold','%(256_base3)s' + [[[tags]]] + normal = 'bold','default','%(16_yellow)s,bold','%(16_base3)s','%(256_yellow)s,bold','%(256_base3)s' + [[[authors]]] + normal = 'default,underline','default','%(16_cyan)s','%(16_base3)s','%(256_cyan)s,bold','%(256_base3)s' + [[[subject]]] + normal = 'default','default','%(16_base00)s,bold','%(16_base3)s','%(256_base00)s,bold','%(256_base3)s' + [[[content]]] + normal = 'default','default','%(16_base1)s,bold','%(16_base3)s','%(256_base1)s,bold','%(256_base3)s' diff --git a/extra/themes/solarized_dark b/extra/themes/solarized_dark new file mode 100644 index 00000000..ce414836 --- /dev/null +++ b/extra/themes/solarized_dark @@ -0,0 +1,127 @@ +############################################################################### +# SOLARIZED DARK +# +# colour theme for alot. © 2012 Patrick Totzke, GNU GPL3+ +# http://ethanschoonover.com/solarized +# https://github.com/pazz/alot +############################################################################### +# +# Define mappings from solarized colour names to urwid attribute names for 16 +# and 256 colour modes. These work well assuming you use the solarized term +# colours via Xressources/Xdefaults. You might want to change this otherwise + +16_base03 = 'dark gray' +16_base02 = 'black' +16_base01 = 'light green' +16_base00 = 'yellow' +16_base0 = 'light blue' +16_base1 = 'light cyan' +16_base2 = 'light gray' +16_base3 = 'white' +16_yellow = 'brown' +16_orange = 'light red' +16_red = 'dark red' +16_magenta = 'dark magenta' +16_violet = 'light magenta' +16_blue = 'dark blue' +16_cyan = 'dark cyan' +16_green = 'dark green' + +# Use a slightly different mapping here to be able to use "bold" in 256c mode +256_base03 = 'dark gray' +256_base02 = 'black' +256_base01 = 'light green' +256_base00 = 'yellow' +256_base0 = 'g66' +256_base1 = 'g70' +256_base2 = 'light gray' +256_base3 = 'white' +256_yellow = 'brown' +256_orange = 'light red' +256_red = 'dark red' +256_magenta = 'dark magenta' +256_violet = 'light magenta' +256_blue = 'dark blue' +256_cyan = '#088' +256_green = 'dark green' + + +# This is the actual alot theme +[global] + footer = 'standout','default','%(16_base1)s','%(16_base02)s','%(256_base1)s','%(256_base02)s' + body = 'default','default','%(16_base0)s','%(16_base03)s','%(256_base0)s','%(256_base03)s' + notify_error = 'standout','default','%(16_base3)s','%(16_red)s','%(256_base3)s','%(256_red)s' + notify_normal = 'default','default','%(16_base01)s','%(16_base02)s','%(256_base01)s','%(256_base02)s' + prompt = 'default','default','%(16_base0)s','%(16_base02)s','%(256_base0)s','%(256_base02)s' + tag = 'default','default','%(16_yellow)s','%(16_base03)s','%(256_yellow)s','%(256_base03)s' + tag_focus = 'standout','default','%(16_base03)s','%(16_yellow)s','%(256_base03)s','%(256_yellow)s' +[help] + text = 'default','default','light gray','dark gray','%(256_base0)s','%(256_base03)s' + section = 'underline','default','%(16_base1)s','%(16_base02)s','%(256_base1)s','%(256_base02)s' + title = 'standout','default','%(16_base1)s','%(16_base03)s','%(256_base1)s','%(256_base03)s' +[taglist] + line_focus = 'standout','default','%(16_base02)s','%(16_yellow)s','%(256_base02)s','%(256_yellow)s' + line_even = 'default','default','%(16_base0)s','%(16_base03)s','%(256_base0)s','%(256_base03)s' + line_odd = 'default','default','%(16_base0)s','%(16_base02)s','%(256_base0)s','%(256_base02)s' +[bufferlist] + line_focus = 'standout','default','%(16_base02)s','%(16_yellow)s','%(256_base02)s','%(256_yellow)s' + line_even = 'default','default','%(16_base0)s','%(16_base03)s','%(256_base0)s','%(256_base03)s' + line_odd = 'default','default','%(16_base0)s','%(16_base02)s','%(256_base0)s','%(256_base02)s' +[thread] + attachment = 'default','default','%(16_base0)s','%(16_base03)s','%(256_base0)s','%(256_base03)s' + attachment_focus = 'underline','default','%(16_base02)s','%(16_yellow)s','%(256_base02)s','%(256_yellow)s' + arrow_bars = 'default','default','%(16_yellow)s','%(16_base03)s','%(256_yellow)s','%(256_base03)s' + arrow_heads = 'default','default','%(16_yellow)s','%(16_base03)s','%(256_yellow)s','%(256_base03)s' + body = 'default','default','%(16_base0)s','%(16_base03)s','%(256_base0)s','%(256_base03)s' + header = 'default','default','%(16_base0)s','%(16_base02)s','%(256_base0)s','%(256_base02)s' + header_key = 'default','default','%(16_magenta)s','%(16_base02)s','%(256_magenta)s','%(256_base02)s' + header_value = 'default','default','%(16_blue)s','%(16_base02)s','%(256_blue)s','%(256_base02)s' + [[summary]] + even = 'default','default','%(16_base0)s','%(16_base02)s','%(256_base0)s','%(256_base02)s' + focus = 'standout','default','%(16_base03)s','%(16_yellow)s','%(256_base03)s','%(256_yellow)s' + odd = 'default','default','%(16_base0)s','%(16_base03)s','%(256_base0)s','%(256_base03)s' +[envelope] + body = 'default','default','%(16_base0)s','%(16_base03)s','%(256_base0)s','%(256_base03)s' + header = 'default','default','%(16_base0)s','%(16_base02)s','%(256_base0)s','%(256_base02)s' + header_key = 'default','default','%(16_orange)s','%(16_base02)s','%(256_orange)s','%(256_base02)s' + header_value = 'default','default','%(16_violet)s','%(16_base02)s','%(256_violet)s','%(256_base02)s' +[search] + [[threadline]] + normal = 'default','default','%(16_base1)s','%(16_base03)s','%(256_base1)s','%(256_base03)s' + focus = 'standout','default','%(16_base02)s','%(16_yellow)s','%(256_base02)s','%(256_yellow)s' + parts = date,mailcount,tags,authors,subject + [[[date]]] + normal = 'default','default','%(16_base1)s','%(16_base03)s','%(256_base1)s','%(256_base03)s' + focus = 'standout','default','%(16_base02)s,bold','%(16_yellow)s','%(256_base02)s,bold','%(256_yellow)s' + [[[mailcount]]] + normal = 'default','default','%(16_base1)s','%(16_base03)s','%(256_base1)s','%(256_base03)s' + focus = 'standout','default','%(16_base02)s','%(16_yellow)s','%(256_base02)s','%(256_yellow)s' + [[[tags]]] + normal = 'bold','default','%(16_yellow)s','%(16_base03)s','%(256_yellow)s','%(256_base03)s' + focus = 'standout','default','%(16_base02)s,bold','%(16_yellow)s','%(256_base02)s,bold','%(256_yellow)s' + [[[authors]]] + normal = 'default,underline','default','%(16_cyan)s','%(16_base03)s','%(256_cyan)s','%(256_base03)s' + focus = 'standout','default','%(16_base02)s','%(16_yellow)s','%(256_base02)s','%(256_yellow)s' + width = 'fit',0,30 + [[[subject]]] + normal = 'default','default','%(16_base0)s','%(16_base03)s','%(256_base0)s','%(256_base03)s' + focus = 'standout','default','%(16_base02)s,bold','%(16_yellow)s','%(256_base02)s,bold','%(256_yellow)s' + width = 'weight',1 + [[[content]]] + normal = 'default','default','%(16_base01)s','%(16_base03)s','%(256_base01)s','%(256_base03)s' + focus = 'standout','default','%(16_base02)s','%(16_yellow)s','%(256_base02)s','%(256_yellow)s' + [[threadline-unread]] + normal = 'default','default','%(16_base1)s,bold','%(16_base03)s','%(256_base1)s,bold','%(256_base03)s' + tagged_with = 'unread' + [[[date]]] + normal = 'default','default','%(16_base1)s,bold','%(16_base03)s','%(256_base1)s,bold','%(256_base03)s' + [[[mailcount]]] + normal = 'default','default','%(16_base1)s,bold','%(16_base03)s','%(256_base1)s,bold','%(256_base03)s' + [[[tags]]] + normal = 'bold','default','%(16_yellow)s,bold','%(16_base03)s','%(256_yellow)s,bold','%(256_base03)s' + [[[authors]]] + normal = 'default,underline','default','%(16_cyan)s','%(16_base03)s','%(256_cyan)s,bold','%(256_base03)s' + [[[subject]]] + normal = 'default','default','%(16_base0)s,bold','%(16_base03)s','%(256_base0)s,bold','%(256_base03)s' + [[[content]]] + normal = 'default','default','%(16_base01)s,bold','%(16_base03)s','%(256_base01)s,bold','%(256_base03)s' |