diff options
Diffstat (limited to 'alot/settings/theme.py')
-rw-r--r-- | alot/settings/theme.py | 144 |
1 files changed, 101 insertions, 43 deletions
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 |