| 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 | # All rights reserved. |
|---|
| 6 | # |
|---|
| 7 | # This software is licensed as described in the file COPYING, which |
|---|
| 8 | # you should have received as part of this distribution. |
|---|
| 9 | # |
|---|
| 10 | |
|---|
| 11 | import os.path |
|---|
| 12 | |
|---|
| 13 | from pkg_resources import resource_filename, resource_string |
|---|
| 14 | |
|---|
| 15 | from trac.admin.api import IAdminPanelProvider |
|---|
| 16 | from trac.core import * |
|---|
| 17 | from trac.web.chrome import add_script, add_stylesheet, Chrome, ITemplateProvider |
|---|
| 18 | from trac.web.api import IRequestHandler, HTTPNotFound |
|---|
| 19 | from trac.perm import IPermissionRequestor |
|---|
| 20 | |
|---|
| 21 | from themeengine.api import ThemeEngineSystem, ThemeNotFound |
|---|
| 22 | from themeengine.translation import _, dgettext |
|---|
| 23 | |
|---|
| 24 | |
|---|
| 25 | try: |
|---|
| 26 | dict.iteritems |
|---|
| 27 | except AttributeError: |
|---|
| 28 | # Python 3 |
|---|
| 29 | def iteritems(d): |
|---|
| 30 | return iter(d.items()) |
|---|
| 31 | else: |
|---|
| 32 | # Python 2 |
|---|
| 33 | def iteritems(d): |
|---|
| 34 | return d.iteritems() |
|---|
| 35 | |
|---|
| 36 | |
|---|
| 37 | class SimpleThemeAdminModule(Component): |
|---|
| 38 | """An admin panel for ThemeEngine.""" |
|---|
| 39 | |
|---|
| 40 | implements(IAdminPanelProvider, IPermissionRequestor, ITemplateProvider, IRequestHandler) |
|---|
| 41 | |
|---|
| 42 | def __init__(self): |
|---|
| 43 | self.system = ThemeEngineSystem(self.env) |
|---|
| 44 | |
|---|
| 45 | # IAdminPanelProvider methods |
|---|
| 46 | def get_admin_panels(self, req): |
|---|
| 47 | if req.perm.has_permission('THEME_ADMIN'): |
|---|
| 48 | yield 'theme', _('Theme'), 'theme', _('Theme') |
|---|
| 49 | |
|---|
| 50 | def render_admin_panel(self, req, cat, page, path_info): |
|---|
| 51 | if req.method == 'POST': |
|---|
| 52 | self.config.set('theme', 'theme', req.args['theme'].lower()) |
|---|
| 53 | self.config.save() |
|---|
| 54 | |
|---|
| 55 | req.redirect(req.href.admin(cat, page)) |
|---|
| 56 | |
|---|
| 57 | data = { |
|---|
| 58 | 'themeengine': { |
|---|
| 59 | 'info': self.system.info.items(), |
|---|
| 60 | }, |
|---|
| 61 | '_dgettext' : dgettext, |
|---|
| 62 | } |
|---|
| 63 | |
|---|
| 64 | try: |
|---|
| 65 | theme_name = self.system.theme and self.system.theme['name'] \ |
|---|
| 66 | or 'default' |
|---|
| 67 | except ThemeNotFound: |
|---|
| 68 | theme_name = 'default' |
|---|
| 69 | theme_name = theme_name.islower() and theme_name.title() or theme_name |
|---|
| 70 | data['themeengine']['current'] = theme_name |
|---|
| 71 | index = 0 |
|---|
| 72 | curtheme = None |
|---|
| 73 | for i, (k, v) in enumerate(data['themeengine']['info']): |
|---|
| 74 | if k.lower() == theme_name.lower(): |
|---|
| 75 | index = i |
|---|
| 76 | curtheme = v |
|---|
| 77 | break |
|---|
| 78 | data['themeengine']['current_index'] = index |
|---|
| 79 | data['themeengine']['current_theme'] = curtheme |
|---|
| 80 | |
|---|
| 81 | #add_stylesheet(req, 'themeengine/jquery.jcarousel.css') |
|---|
| 82 | #add_stylesheet(req, 'themeengine/skins/tango/skin.css') |
|---|
| 83 | #add_script(req, 'themeengine/jquery.jcarousel.pack.js') |
|---|
| 84 | add_script(req, 'themeengine/jquery.cycle2.min.js') |
|---|
| 85 | add_stylesheet(req, 'themeengine/admin.css') |
|---|
| 86 | add_stylesheet(req, 'themeengine/cycle2.css') |
|---|
| 87 | #add_script(req, 'themeengine/jcarousellite_1.0.1.js') |
|---|
| 88 | if hasattr(Chrome, 'jenv'): |
|---|
| 89 | return 'admin_theme_jinja.html', data |
|---|
| 90 | else: |
|---|
| 91 | return 'admin_theme.html', data |
|---|
| 92 | |
|---|
| 93 | # IPermissionRequestor methods |
|---|
| 94 | def get_permission_actions(self): |
|---|
| 95 | yield 'THEME_ADMIN' |
|---|
| 96 | |
|---|
| 97 | # ITemplateProvider methods |
|---|
| 98 | def get_htdocs_dirs(self): |
|---|
| 99 | yield 'themeengine', resource_filename(__name__, 'htdocs') |
|---|
| 100 | |
|---|
| 101 | def get_templates_dirs(self): |
|---|
| 102 | yield resource_filename(__name__, 'templates') |
|---|
| 103 | |
|---|
| 104 | # IRequestHandler methods |
|---|
| 105 | def match_request(self, req): |
|---|
| 106 | return req.path_info.startswith('/themeengine') |
|---|
| 107 | |
|---|
| 108 | def process_request(self, req): |
|---|
| 109 | data = {} |
|---|
| 110 | |
|---|
| 111 | path_info = req.path_info[13:] |
|---|
| 112 | if path_info.startswith('screenshot/'): |
|---|
| 113 | return self._send_screenshot(req, data, path_info[11:]) |
|---|
| 114 | |
|---|
| 115 | raise HTTPNotFound("The requested URL was not found on this server") |
|---|
| 116 | |
|---|
| 117 | def _send_screenshot(self, req, data, name): |
|---|
| 118 | if name not in self.system.info: |
|---|
| 119 | raise HTTPNotFound("Invalid theme name '%s'", name) |
|---|
| 120 | theme = self.system.info[name] |
|---|
| 121 | |
|---|
| 122 | if 'screenshot' in theme: |
|---|
| 123 | img = resource_string(theme['module'], theme['screenshot']) |
|---|
| 124 | else: |
|---|
| 125 | img = resource_string(__name__, 'htdocs/no_screenshot.png') |
|---|
| 126 | req.send(img, 'image/png') |
|---|
| 127 | |
|---|
| 128 | |
|---|
| 129 | class CustomThemeAdminModule(Component): |
|---|
| 130 | """An admin panel for ThemeEngine.""" |
|---|
| 131 | |
|---|
| 132 | implements(IAdminPanelProvider) |
|---|
| 133 | |
|---|
| 134 | def __init__(self): |
|---|
| 135 | self.system = ThemeEngineSystem(self.env) |
|---|
| 136 | |
|---|
| 137 | # IAdminPanelProvider methods |
|---|
| 138 | def get_admin_panels(self, req): |
|---|
| 139 | if req.perm.has_permission('THEME_ADMIN'): |
|---|
| 140 | yield 'theme', _('Theme'), 'custom', _('Customize') |
|---|
| 141 | yield 'theme', _('Theme'), 'advanced', _('Customize: Advanced') |
|---|
| 142 | |
|---|
| 143 | def render_admin_panel(self, req, cat, page, path_info): |
|---|
| 144 | data = { |
|---|
| 145 | 'themes': self.system.info.items(), |
|---|
| 146 | '_dgettext' : dgettext, |
|---|
| 147 | 'iteritems': iteritems |
|---|
| 148 | } |
|---|
| 149 | |
|---|
| 150 | theme_name = self.system.theme_name or 'Default' |
|---|
| 151 | data['current'] = theme_name |
|---|
| 152 | index = 0 |
|---|
| 153 | curtheme = {} |
|---|
| 154 | for i, (k, v) in enumerate(data['themes']): |
|---|
| 155 | if k == theme_name: |
|---|
| 156 | index = i |
|---|
| 157 | curtheme = v |
|---|
| 158 | break |
|---|
| 159 | data['current_index'] = index |
|---|
| 160 | data['current_theme'] = curtheme |
|---|
| 161 | |
|---|
| 162 | colors = {} |
|---|
| 163 | if curtheme: |
|---|
| 164 | for name, prop, selector in curtheme.get('colors', ()): |
|---|
| 165 | # Check the config first |
|---|
| 166 | val = self.config.get('theme', 'color.'+name) |
|---|
| 167 | if not val and curtheme.get('schemes'): |
|---|
| 168 | # Then look for a scheme |
|---|
| 169 | val = curtheme['schemes'][0][1].get(name) |
|---|
| 170 | if not val: |
|---|
| 171 | # Otherwise use black for foreground and white for background |
|---|
| 172 | if prop == 'color': |
|---|
| 173 | val = '#000000' |
|---|
| 174 | else: |
|---|
| 175 | val = '#ffffff' |
|---|
| 176 | colors[name] = val |
|---|
| 177 | data['colors'] = colors |
|---|
| 178 | data['enable'] = self.config.getbool('theme', 'enable_css', False) |
|---|
| 179 | |
|---|
| 180 | if hasattr(self.env, 'get_htdocs_dir'): |
|---|
| 181 | theme_css_file = os.path.join(self.env.get_htdocs_dir(), 'theme.css') |
|---|
| 182 | else: |
|---|
| 183 | theme_css_file = os.path.join(self.env.htdocs_dir, 'theme.css') |
|---|
| 184 | if page == 'advanced' and os.path.exists(theme_css_file): |
|---|
| 185 | data['css'] = open(theme_css_file).read() |
|---|
| 186 | |
|---|
| 187 | if req.method == 'POST': |
|---|
| 188 | if 'enable_css' in req.args: |
|---|
| 189 | self.config.set('theme', 'enable_css', 'enabled') |
|---|
| 190 | else: |
|---|
| 191 | self.config.set('theme', 'enable_css', 'disabled') |
|---|
| 192 | |
|---|
| 193 | for key, value in self.config.options('theme'): |
|---|
| 194 | if key.startswith('color.'): |
|---|
| 195 | self.config.remove('theme', key) |
|---|
| 196 | |
|---|
| 197 | for name, color in iteritems(colors): |
|---|
| 198 | color = req.args.get('color_'+name, color) |
|---|
| 199 | self.config.set('theme', 'color.'+name, color) |
|---|
| 200 | |
|---|
| 201 | f = open(theme_css_file, 'w') |
|---|
| 202 | if page == 'advanced': |
|---|
| 203 | f.write(req.args.get('css', '')) |
|---|
| 204 | else: |
|---|
| 205 | f.write('/* Warning: this file is auto-generated. If you edit it, changes will be lost the next time you use the simple customizer. */\n') |
|---|
| 206 | for name, prop, selector in curtheme.get('colors', ()): |
|---|
| 207 | color = req.args.get('color_'+name, colors.get(name, '#ffffff')) |
|---|
| 208 | f.write('%s {\n'%selector) |
|---|
| 209 | f.write(' %s: %s;\n'%(prop, color)) |
|---|
| 210 | f.write('}\n\n') |
|---|
| 211 | f.close() |
|---|
| 212 | |
|---|
| 213 | self.config.save() |
|---|
| 214 | req.redirect(req.href.admin(cat, page)) |
|---|
| 215 | |
|---|
| 216 | add_stylesheet(req, 'themeengine/farbtastic/farbtastic.css') |
|---|
| 217 | add_stylesheet(req, 'themeengine/admin.css') |
|---|
| 218 | add_script(req, 'themeengine/farbtastic/farbtastic.js') |
|---|
| 219 | if page == 'advanced': |
|---|
| 220 | if hasattr(Chrome, 'jenv'): |
|---|
| 221 | return 'admin_theme_advanced_jinja.html', data |
|---|
| 222 | else: |
|---|
| 223 | return 'admin_theme_advanced.html', data |
|---|
| 224 | else: |
|---|
| 225 | add_script(req, 'themeengine/jquery.rule.js') |
|---|
| 226 | if hasattr(Chrome, 'jenv'): |
|---|
| 227 | return 'admin_theme_custom_jinja.html', data |
|---|
| 228 | else: |
|---|
| 229 | return 'admin_theme_custom.html', data |
|---|