1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
"""
Theme.py
April 25, 2011
Classes for dealing with color themes
"""
import logging
import os
from gi.repository import Gdk, Gtk
from Config import Config
SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__))
# TODO: Using local theme path while testing, should update to
# an actual theme path, and then fallback to the local theme
# path if running from source
SYSTEM_THEME_PATH = os.path.abspath(os.path.join(SCRIPT_PATH, "..", "themes"))
USER_THEME_PATH = "~/.config/recent-notifications/themes"
DEFAULT_BACKGROUND = "#eee"
DEFAULT_ITEM_BACKGROUND = "#555"
DEFAULT_ITEM_FOREGROUND = "#fff"
DEFAULT_LINK_COLOR = "#ea1"
DEFAULT_VISITED_LINK_COLOR = "#fd7"
logger = logging.getLogger("Theme")
gtk_style_template = """
style "label" {{
GtkLabel::link-color="{0}"
GtkLabel::visited-link-color="{1}"
}}
widget_class "*GtkLabel" style "label"
"""
class ColorException(Exception):
pass
class Color(object):
"""Wraps a GdkColor"""
MAX_VALUE = 65535.0
def __init__(self, color_string):
"""Creates a new Color object from the given color string.
Uses Gdk.color_parse to parse the string."""
result, color = Gdk.color_parse(color_string)
if result != True:
raise ColorException("Failed to parse color: {0}".format(color_string))
self._color = color
self._red = self._float_color(color.red)
self._green = self._float_color(color.green)
self._blue = self._float_color(color.blue)
def _float_color(self, value):
"""Returns the color value as a float from 0.0 to 1.0"""
return float(value) / Color.MAX_VALUE
@property
def gdk_color(self):
"""Returns the gdk color"""
return self._color
@property
def red(self):
"""Returns the red component of the color as a float from 0.0 to 1.0"""
return self._red
@property
def green(self):
"""Returns the green component of the color as a float from 0.0 to 1.0"""
return self._green
@property
def blue(self):
"""Returns the blue component of the color as a float from 0.0 to 1.0"""
return self._blue
def set_fg_normal(self, widget):
"""Sets the NORMAL foreground color of the given widget to this color"""
widget.modify_fg(Gtk.StateType.NORMAL, self._color)
def set_bg_normal(self, widget):
"""Sets the NORMAL background color of the given widget to this color"""
widget.modify_bg(Gtk.StateType.NORMAL, self._color)
class ThemeException(Exception):
pass
class Theme(object):
"""Represents a color theme.
TODO: promote to a gobject so it can emit theme-changed signals"""
def __init__(self, filename):
"""Loads a theme with the given filename, or creates a new theme if not found.
First searches the user theme directory ~/.config/recent-notifications/themes/
then searches the system theme directory. If no theme is found, the new
theme is created in the user theme directory."""
self._filename = filename
user_path = os.path.expanduser(USER_THEME_PATH)
user_file = os.path.join(user_path, filename)
system_file = os.path.join(SYSTEM_THEME_PATH, filename)
if os.path.exists(user_file):
logger.debug("Loading theme from user path: {0}".format(user_file))
self._path = user_path
self._config = Config(user_file)
elif os.path.exists(system_file):
logger.debug("Loading theme from system path: {0}".format(system_file))
self._path = system_file
self._config = Config(system_file)
else:
logger.debug("Creating new user theme: {0}".format(user_file))
self._path = user_path
self._config = Config(user_file)
# Set default values if the properties are missing
self._config.set_if_unset("background", DEFAULT_BACKGROUND)
self._config.set_if_unset("item_background", DEFAULT_ITEM_BACKGROUND)
self._config.set_if_unset("item_foreground", DEFAULT_ITEM_FOREGROUND)
self._config.set_if_unset("link_color", DEFAULT_LINK_COLOR)
self._config.set_if_unset("visited_link_color", DEFAULT_VISITED_LINK_COLOR)
def apply_gtk_styles(self):
"""Applies settings to the global gtk style"""
link_color = self._config.get("link_color")
visited_link_color = self._config.get("visited_link_color")
Gtk.rc_parse_string(gtk_style_template.format(link_color,
visited_link_color))
def get_name(self):
"""Returns the name of this theme, or if unset, the filename"""
return self._config.get("name", self._filename)
def get_color(self, key):
"""Returns a new color for the given property"""
if key not in self._config:
raise ThemeException("Invalid property: {0}".format(key))
color_string = self._config.get(key)
return Color(color_string)
def get_background(self):
"""Returns the background property as a Color"""
return self.get_color("background")
def get_item_background(self):
"""Returns the item_background property as a Color"""
return self.get_color("item_background")
def get_item_foreground(self):
"""Returns the item_foreground property as a Color"""
return self.get_color("item_foreground")
|