source: themeengineplugin/tags/themeengine-2.3.0/themeengine/api.py

Last change on this file was 18428, checked in by Cinc-th, 2 years ago

ThemeEnginePlugin: allow to use a list of CSS files.

File size: 8.8 KB
Line 
1# -*- coding: utf-8 -*-
2#
3# Copyright (c) 2006-2010 Noah Kantrowitz <noah@coderanger.net>
4# Copyright (c) 2013      Olemis Lang <olemis+trac@gmail.com>
5# Copyright (c) 2021      Cinc
6#
7# All rights reserved.
8#
9# This software is licensed as described in the file COPYING, which
10# you should have received as part of this distribution.
11#
12
13import os.path
14import inspect
15
16from trac.core import *
17from trac.config import Option
18
19from themeengine.translation import _, add_domain, I18N_DOC_OPTIONS
20
21try:
22    from trac.util import lazy
23except ImportError:
24    lazy = None
25
26
27class ThemeNotFound(TracError):
28    """The requested theme isn't found."""
29
30    def __init__(self, name):
31        self.theme_name = name
32        TracError.__init__(self, 'Unknown theme %s' % name)
33
34
35class IThemeProvider(Interface):
36    """An interface to provide style information."""
37
38    def get_theme_names():
39        """Return an iterable of names."""
40
41    def get_template_overrides(name):
42        """(Optional) local changes to specific templates
43
44        Return a sequence of tuples (old_html, new_html, function) where
45
46         old_html::
47           The name of the template overriden by this theme.
48         new_html::
49           The name of the template file replacing the former.
50         function::
51           Optional callback (or None) to add further data . Signature:
52                   req::
53                       Request object
54                   template::
55                       The value of `old_html` above
56                   data::
57                       Template data, may be modified
58                   content_type::
59                       Reported MIME type
60
61        since 2.2.0
62        """
63
64    def get_theme_info(name):
65        """Return a dict containing 0 or more of the following pairs:
66
67         description::
68           A brief description of the theme.
69         template::
70           The name of the Genshi theme template file.
71         jinja_template::
72           The name of the Jinja2 theme template file.
73         css::
74           The filename of the CSS file or a list/tuple of filenames.
75         disable_trac_css::
76           A boolean indicating if the core Trac CSS should be diabled.
77         htdocs::
78           The folder containg the static content.
79         screenshot::
80           The name of the screenshot file.
81         colors::
82           A list of (name, css-property, selector) tuples.
83         schemes::
84           A list of (name, {color-name: value, ...}) tuples.
85         scripts::
86           A list of (filename, mimetype, charset, ie_if) respectively for
87           script (relative | absolute) URI (mandatory),
88           script MIME type (optional , defaults to 'text/javascript'),
89           script charset encoding (optional, defaults to 'utf-8'),
90           and a bool flag for MSIE-only shims (optional, defaults to False)
91           @since 2.2.2
92        """
93
94
95class ThemeEngineSystem(Component):
96    """Central functionality for the theme system."""
97
98    theme_name = Option('theme', 'theme', default='default',
99                        doc='The theme to use to style this Trac.',
100                        **I18N_DOC_OPTIONS)
101
102    implements(IThemeProvider)
103
104    def theme(self):
105        if self.theme_name.lower() == 'default' or self.theme_name == '':
106            return None
107        elif self.theme_name.lower() in self.info:
108            return self.info[self.theme_name.lower()]
109        else:
110            raise ThemeNotFound(self.theme_name)
111    theme = property(theme)
112
113    providers = ExtensionPoint(IThemeProvider)
114
115    def __init__(self):
116        import pkg_resources
117        locale_dir = pkg_resources.resource_filename(__name__, 'locale')
118        add_domain(self.env.path, locale_dir)
119
120        if lazy is None:
121            # Trac < 1.0 : this can safely go in here because the data can
122            # only change on a restart anyway
123            self.info = self.info()
124
125    def info(self):
126        # Trac >= 1.0 : Hack needed to deal with infinite recursion error
127        #    Details : http://trac-hacks.org/ticket/9580#comment:1
128        #    Details : http://trac-hacks.org/ticket/9580#comment:2
129        info = {}
130        for provider in self.providers:
131            for name in provider.get_theme_names():
132                theme = provider.get_theme_info(name)
133                theme['provider'] = provider
134                theme['module'] = provider.__class__.__module__
135                theme['name'] = name
136                info[name.lower()] = theme
137        return info
138
139    if lazy is not None:
140        info = lazy(info)
141
142    def is_active_theme(self, name, provider=None):
143        try:
144            active_theme = self.theme
145        except ThemeNotFound:
146            not_found = True
147        else:
148            not_found = False
149        if not_found or self.env[ThemeEngineSystem] is None or \
150                active_theme is None:
151            return name in ('default', None, '')
152        elif active_theme['name'].lower() == name:
153            return provider is None or provider is active_theme['provider']
154        return False
155
156    # IThemeProvider methods
157    def get_theme_names(self):
158        yield 'default'
159
160    def get_theme_info(self, name):
161        return {
162            'description': 'The default Trac theme.',
163            'screenshot': 'htdocs/default_screenshot.png',
164            'colors': [
165                ('text', 'color', 'body, .milestone .info h2 *:link, .milestone .info h2 *:visited'),
166                ('background', 'background-color', 'body'),
167                ('link', 'color', '*:link, *:visited, #tabs *:link, #tabs *:visited, .milestone .info h2 em'),
168                ('link_hover', 'color', '*:link:hover, *:visited:hover, #tabs *:link:hover, #tabs *:visited:hover'),
169                ('mainnav', 'background-color', '#mainnav'),
170                ('mainnav_active', 'background-color', '#mainnav .active *:link, #mainnav .active *:visited'),
171                ('mainnav_hover', 'background-color', '#mainnav *:link:hover, #mainnav *:visited:hover'),
172            ],
173            'schemes': [
174                ('default', {
175                    'text': '#000000',
176                    'background': '#ffffff',
177                    'link': '#bb0000',
178                    'link_hover': '#555555',
179                    'mainnav': '#ffffff',
180                    'mainnav_active': '#000000',
181                    'mainnav_hover': '#cccccc',
182                }),
183                ('omgponies', {
184                    'text': '#A42D8D',
185                    'background': '#FECDE3',
186                    'link': '#E600BC',
187                    'link_hover': '#8C366C',
188                    'mainnav': '#FB88C3',
189                    'mainnav_active': '#EB009B',
190                    'mainnav_hover': '#c15c9f',
191                }),
192            ],
193        }
194
195
196class ThemeBase(Component):
197    """A base class for themes."""
198
199    abstract = True
200
201    implements(IThemeProvider)
202
203    # Defaults
204    theme = css = htdocs = screenshot = False
205    colors = schemes = disable_trac_css = False
206
207    @property
208    def is_active_theme(self):
209        themesys = self.env[ThemeEngineSystem]
210        if themesys is None:
211            return False
212        try:
213            active_theme = themesys.theme
214        except ThemeNotFound:
215            # There is no way this component can be the active theme
216            return False
217        if active_theme is None:
218            return False
219        return active_theme.get('provider') is self
220
221    # IThemeProvider methods
222    def get_theme_names(self):
223        """Generate theme name off class name by removing Theme suffix
224        """
225        name = self.__class__.__name__
226        if name.endswith('Theme'):
227            name = name[:-5]
228        yield name
229
230    def get_theme_info(self, name):
231        """Generate theme info considering declared class attributes
232        """
233        info = {}
234
235        info['description'] = inspect.getdoc(self.__class__)
236        name = name.lower()
237        self._set_info(info, 'template',
238                       os.path.join('templates', name + '_theme.html'))
239        self._set_info(info, 'jinja_template',
240                       os.path.join('templates', name + '_theme_jinja.html'))
241        self._set_info(info, 'css', name + '.css')
242        self._set_info(info, 'htdocs', 'htdocs')
243        self._set_info(info, 'screenshot', 'htdocs/screenshot.png')
244        self._set_info(info, 'colors', ())
245        self._set_info(info, 'schemes', ())
246        self._set_info(info, 'disable_trac_css', True)
247        self._set_info(info, 'scripts', [])
248
249        return info
250
251    def get_template_overrides(self, name):
252        """No overrides by default
253        """
254        return []
255
256    # Internal methods
257    def _set_info(self, info, attr, default):
258        if not hasattr(self, attr):
259            return  # Skip some e.g. scripts
260
261        val = getattr(self, attr)
262        if val:
263            if val is True:
264                info[attr] = default
265            elif val is not False:
266                info[attr] = val
Note: See TracBrowser for help on using the repository browser.