[core/theme] Add support for icon themes
Allow sub-themes ("iconsets") to be merged into the "main" theme. That way, effectively, it's possible to define colors and icons in separate JSON files. see #23
This commit is contained in:
parent
394ef61760
commit
562fd85ca2
5 changed files with 71 additions and 15 deletions
|
@ -1,6 +1,7 @@
|
||||||
"""Theme support"""
|
"""Theme support"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import copy
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import bumblebee.error
|
import bumblebee.error
|
||||||
|
@ -12,11 +13,13 @@ def theme_path():
|
||||||
class Theme(object):
|
class Theme(object):
|
||||||
"""Represents a collection of icons and colors"""
|
"""Represents a collection of icons and colors"""
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
theme = self.load(name)
|
|
||||||
self._init(self.load(name))
|
self._init(self.load(name))
|
||||||
|
|
||||||
def _init(self, data):
|
def _init(self, data):
|
||||||
"""Initialize theme from data structure"""
|
"""Initialize theme from data structure"""
|
||||||
|
for iconset in data.get("icons", []):
|
||||||
|
self._merge(data, self._load_icons(iconset))
|
||||||
|
self._theme = data
|
||||||
self._defaults = data.get("defaults", {})
|
self._defaults = data.get("defaults", {})
|
||||||
|
|
||||||
def prefix(self, widget):
|
def prefix(self, widget):
|
||||||
|
@ -31,10 +34,14 @@ class Theme(object):
|
||||||
theme = json.loads(data)
|
theme = json.loads(data)
|
||||||
self._init(theme)
|
self._init(theme)
|
||||||
|
|
||||||
def load(self, name):
|
def _load_icons(self, name):
|
||||||
|
path = "{}/icons/".format(theme_path())
|
||||||
|
return self.load(name, path=path)
|
||||||
|
|
||||||
|
def load(self, name, path=theme_path()):
|
||||||
"""Load and parse a theme file"""
|
"""Load and parse a theme file"""
|
||||||
path = theme_path()
|
|
||||||
themefile = "{}/{}.json".format(path, name)
|
themefile = "{}/{}.json".format(path, name)
|
||||||
|
|
||||||
if os.path.isfile(themefile):
|
if os.path.isfile(themefile):
|
||||||
try:
|
try:
|
||||||
with open(themefile) as data:
|
with open(themefile) as data:
|
||||||
|
@ -44,10 +51,32 @@ class Theme(object):
|
||||||
else:
|
else:
|
||||||
raise bumblebee.error.ThemeLoadError("no such theme: {}".format(name))
|
raise bumblebee.error.ThemeLoadError("no such theme: {}".format(name))
|
||||||
|
|
||||||
def _get(self, widget, name,default=None):
|
def _get(self, widget, name, default=None):
|
||||||
value = default
|
|
||||||
value = self._defaults.get(name, value)
|
module_theme = self._theme.get(widget.module(), {})
|
||||||
|
|
||||||
|
value = self._defaults.get(name, default)
|
||||||
|
value = module_theme.get(name, value)
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
# algorithm copied from
|
||||||
|
# http://blog.impressiver.com/post/31434674390/deep-merge-multiple-python-dicts
|
||||||
|
# nicely done :)
|
||||||
|
def _merge(self, target, *args):
|
||||||
|
if len(args) > 1:
|
||||||
|
for item in args:
|
||||||
|
self._merge(item)
|
||||||
|
return target
|
||||||
|
|
||||||
|
item = args[0]
|
||||||
|
if not isinstance(item, dict):
|
||||||
|
return item
|
||||||
|
for key, value in item.items():
|
||||||
|
if key in target and isinstance(target[key], dict):
|
||||||
|
self._merge(target[key], value)
|
||||||
|
else:
|
||||||
|
target[key] = copy.deepcopy(value)
|
||||||
|
return target
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
||||||
|
|
|
@ -9,8 +9,15 @@ class TestTheme(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.nonexistentThemeName = "no-such-theme"
|
self.nonexistentThemeName = "no-such-theme"
|
||||||
self.invalidThemeName = "invalid"
|
self.invalidThemeName = "invalid"
|
||||||
self.validThemeName = "solarized-powerline"
|
self.validThemeName = "test"
|
||||||
self.someWidget = MockWidget("foo")
|
self.someWidget = MockWidget("foo")
|
||||||
|
self.theme = Theme(self.validThemeName)
|
||||||
|
|
||||||
|
self.widgetTheme = "test-widget"
|
||||||
|
self.defaultPrefix = "default-prefix"
|
||||||
|
self.defaultSuffix = "default-suffix"
|
||||||
|
self.widgetPrefix = "widget-prefix"
|
||||||
|
self.widgetSuffix = "widget-suffix"
|
||||||
|
|
||||||
def test_load_valid_theme(self):
|
def test_load_valid_theme(self):
|
||||||
try:
|
try:
|
||||||
|
@ -26,14 +33,14 @@ class TestTheme(unittest.TestCase):
|
||||||
with self.assertRaises(ThemeLoadError):
|
with self.assertRaises(ThemeLoadError):
|
||||||
Theme(self.invalidThemeName)
|
Theme(self.invalidThemeName)
|
||||||
|
|
||||||
def test_prefix(self):
|
def test_default_prefix(self):
|
||||||
theme = Theme(self.validThemeName)
|
self.assertEquals(self.theme.prefix(self.someWidget), self.defaultPrefix)
|
||||||
theme.loads('{"defaults": { "prefix": "test" }}')
|
|
||||||
self.assertEquals(theme.prefix(self.someWidget), "test")
|
|
||||||
|
|
||||||
def test_suffix(self):
|
def test_default_suffix(self):
|
||||||
theme = Theme(self.validThemeName)
|
self.assertEquals(self.theme.suffix(self.someWidget), self.defaultSuffix)
|
||||||
theme.loads('{"defaults": { "suffix": "test" }}')
|
|
||||||
self.assertEquals(theme.suffix(self.someWidget), "test")
|
def test_widget_prefix(self):
|
||||||
|
self.someWidget.set_module(self.widgetTheme)
|
||||||
|
self.assertEquals(self.theme.prefix(self.someWidget), self.widgetPrefix)
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
||||||
|
|
|
@ -22,10 +22,17 @@ class MockOutput(object):
|
||||||
class MockWidget(object):
|
class MockWidget(object):
|
||||||
def __init__(self, text):
|
def __init__(self, text):
|
||||||
self._text = text
|
self._text = text
|
||||||
|
self._module = None
|
||||||
|
|
||||||
|
def set_module(self, name):
|
||||||
|
self._module = name
|
||||||
|
|
||||||
def update(self, widgets):
|
def update(self, widgets):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def module(self):
|
||||||
|
return self._module
|
||||||
|
|
||||||
def full_text(self):
|
def full_text(self):
|
||||||
return self._text
|
return self._text
|
||||||
|
|
||||||
|
|
6
themes/icons/test.json
Normal file
6
themes/icons/test.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"test-widget": {
|
||||||
|
"prefix": "widget-prefix",
|
||||||
|
"suffix": "widget-suffix"
|
||||||
|
}
|
||||||
|
}
|
7
themes/test.json
Normal file
7
themes/test.json
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"icons": [ "test" ],
|
||||||
|
"defaults": {
|
||||||
|
"prefix": "default-prefix",
|
||||||
|
"suffix": "default-suffix"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue