| 1 | # -*- coding: iso-8859-1 -*- |
|---|
| 2 | # |
|---|
| 3 | # Copyright (C) 2011 Mikael Relbe <mikael@relbe.se> |
|---|
| 4 | # All rights reserved. |
|---|
| 5 | # |
|---|
| 6 | # Parts of this code (smileys) are |
|---|
| 7 | # Copyright (C) 2006 Christian Boos <cboos@neuf.fr> |
|---|
| 8 | # All rights reserved. |
|---|
| 9 | # |
|---|
| 10 | # This software is licensed as described in the file COPYING, which |
|---|
| 11 | # you should have received as part of this distribution. The terms |
|---|
| 12 | # are also available at http://trac.edgewall.com/license.html. |
|---|
| 13 | # |
|---|
| 14 | # Author: Christian Boos <cboos@neuf.fr> |
|---|
| 15 | # Mikael Relbe <mikael@relbe.se> |
|---|
| 16 | |
|---|
| 17 | """Decorate wiki pages with a huge set of modern icons. |
|---|
| 18 | |
|---|
| 19 | More than 3.000 icons are available using the wiki markup `(|name, size|)`, or |
|---|
| 20 | the equivalent `Icon` macro, and as smileys. Smiley character sequences are |
|---|
| 21 | defined in the `[wikiextras-smileys]` section in `trac.ini`. The icon library |
|---|
| 22 | contains two sets of icons, shadowed and shadowless. Which set to use is |
|---|
| 23 | defined in the `[wikiextras]` section in `trac.ini`. |
|---|
| 24 | |
|---|
| 25 | '''Icon Library License Terms''' |
|---|
| 26 | |
|---|
| 27 | The icon library contained herein is composed of the |
|---|
| 28 | [http://p.yusukekamiyamane.com Fugue icon library] with additional icons, and |
|---|
| 29 | can be used for any commercial or personal projects, but you may not lease, |
|---|
| 30 | license or sublicense the icons. |
|---|
| 31 | |
|---|
| 32 | The [http://p.yusukekamiyamane.com Fugue icon library] is released under |
|---|
| 33 | [http://creativecommons.org/licenses/by/3.0/ Creative Commons Attribution 3.0 license]. |
|---|
| 34 | [[BR]] |
|---|
| 35 | Some icons are Copyright (C) [http://p.yusukekamiyamane.com/ Yusuke Kamiyamane]. |
|---|
| 36 | All rights reserved. |
|---|
| 37 | |
|---|
| 38 | Additional icons are released under same |
|---|
| 39 | [http://trac.edgewall.org/wiki/TracLicense license terms] as Trac. |
|---|
| 40 | [[BR]] |
|---|
| 41 | Some icons are Copyright (C) [http://www.edgewall.org Edgewall Software]. |
|---|
| 42 | All rights reserved. |
|---|
| 43 | """ |
|---|
| 44 | |
|---|
| 45 | import fnmatch |
|---|
| 46 | import os |
|---|
| 47 | import re |
|---|
| 48 | |
|---|
| 49 | from pkg_resources import resource_filename |
|---|
| 50 | |
|---|
| 51 | from trac.util.html import html as tag |
|---|
| 52 | |
|---|
| 53 | from trac.config import BoolOption, ConfigSection, IntOption, ListOption |
|---|
| 54 | from trac.core import implements, Component |
|---|
| 55 | from trac.util.compat import cleandoc |
|---|
| 56 | from trac.web.chrome import ITemplateProvider |
|---|
| 57 | from trac.wiki import IWikiMacroProvider, IWikiSyntaxProvider, format_to_html |
|---|
| 58 | |
|---|
| 59 | from tracwikiextras.util import prepare_regexp, reduce_names, render_table |
|---|
| 60 | |
|---|
| 61 | |
|---|
| 62 | SIZE_DESCR = {'S': 'small', 'M': 'medium-sized', 'L': 'large'} |
|---|
| 63 | |
|---|
| 64 | FUGUE_ICONS = { |
|---|
| 65 | False: { # with shadow |
|---|
| 66 | 'S': ('wikiextras-icons-16', |
|---|
| 67 | resource_filename(__name__, 'htdocs/icons/fugue/icons')), |
|---|
| 68 | 'M': ('wikiextras-icons-24', |
|---|
| 69 | resource_filename(__name__, |
|---|
| 70 | 'htdocs/icons/fugue/bonus/icons-24')), |
|---|
| 71 | 'L': ('wikiextras-icons-32', |
|---|
| 72 | resource_filename(__name__, |
|---|
| 73 | 'htdocs/icons/fugue/bonus/icons-32')), |
|---|
| 74 | }, |
|---|
| 75 | True: { # shadowless |
|---|
| 76 | 'S': ('wikiextras-icons-shadowless-16', |
|---|
| 77 | resource_filename(__name__, |
|---|
| 78 | 'htdocs/icons/fugue/icons-shadowless')), |
|---|
| 79 | 'M': ('wikiextras-icons-shadowless-24', |
|---|
| 80 | resource_filename( |
|---|
| 81 | __name__, 'htdocs/icons/fugue/bonus/icons-shadowless-24')), |
|---|
| 82 | 'L': ('wikiextras-icons-shadowless-32', |
|---|
| 83 | resource_filename( |
|---|
| 84 | __name__, 'htdocs/icons/fugue/bonus/icons-shadowless-32')), |
|---|
| 85 | }, |
|---|
| 86 | } |
|---|
| 87 | |
|---|
| 88 | |
|---|
| 89 | |
|---|
| 90 | class Icons(Component): |
|---|
| 91 | """Display icons in lined with text. |
|---|
| 92 | |
|---|
| 93 | The wiki markup `(|name|)`, or the equivalent `Icon` macro, shows a named |
|---|
| 94 | icon that can be in line with text. During side-by-side wiki editing, the |
|---|
| 95 | same wiki markup, or macro, can be used as a temporary search facility to |
|---|
| 96 | find icons in the vast library. The number of icons presented to the wiki |
|---|
| 97 | author can be limited to prevent exhaustive network traffic. This limit is |
|---|
| 98 | defined in the `[wikiextras]` section in `trac.ini`. |
|---|
| 99 | """ |
|---|
| 100 | |
|---|
| 101 | implements(ITemplateProvider, IWikiMacroProvider, IWikiSyntaxProvider) |
|---|
| 102 | |
|---|
| 103 | icon_limit = IntOption('wikiextras', 'icon_limit', 32, |
|---|
| 104 | """To prevent exhaustive network traffic, limit the maximum number of |
|---|
| 105 | icons generated by the macro `Icon`. Set to 0 for unlimited number of |
|---|
| 106 | icons (this will produce exhaustive network traffic--you have been |
|---|
| 107 | warned!)""") |
|---|
| 108 | |
|---|
| 109 | shadowless = BoolOption('wikiextras', 'shadowless_icons', 'false', |
|---|
| 110 | 'Use shadowless icons.') |
|---|
| 111 | |
|---|
| 112 | def icon_location(self, size='S'): |
|---|
| 113 | """ Returns `(prefix, abspath)` tuple based on `size` which is one |
|---|
| 114 | of `small`, `medium` or `large` (or an abbreviation thereof.. |
|---|
| 115 | |
|---|
| 116 | The `prefix` part defines the path in the URL that requests to these |
|---|
| 117 | resources are prefixed with. |
|---|
| 118 | |
|---|
| 119 | The `abspath` is the absolute path to the directory containing the |
|---|
| 120 | resources on the local file system. |
|---|
| 121 | """ |
|---|
| 122 | try: |
|---|
| 123 | return FUGUE_ICONS[self.shadowless][size[0].upper()] |
|---|
| 124 | except Exception: |
|---|
| 125 | return FUGUE_ICONS[self.shadowless]['S'] |
|---|
| 126 | |
|---|
| 127 | def _render_icon(self, formatter, name, size): |
|---|
| 128 | if not name: |
|---|
| 129 | return |
|---|
| 130 | size = size.upper()[0] if size else 'S' |
|---|
| 131 | name = name.lower() |
|---|
| 132 | if any(x in name for x in ['*', '?']): |
|---|
| 133 | #noinspection PyArgumentList |
|---|
| 134 | return ShowIcons(self.env)._render( |
|---|
| 135 | formatter, 2, name, size, True, self.icon_limit) |
|---|
| 136 | else: |
|---|
| 137 | loc = self.icon_location(size) |
|---|
| 138 | return tag.img(src=formatter.href.chrome(loc[0], '%s.png' % name), |
|---|
| 139 | alt=name, style="vertical-align: text-bottom") |
|---|
| 140 | |
|---|
| 141 | # ITemplateProvider methods |
|---|
| 142 | |
|---|
| 143 | def get_htdocs_dirs(self): |
|---|
| 144 | dirs = [] |
|---|
| 145 | for data in FUGUE_ICONS.itervalues(): |
|---|
| 146 | for d in data.itervalues(): |
|---|
| 147 | dirs.append(tuple(d)) |
|---|
| 148 | return dirs |
|---|
| 149 | |
|---|
| 150 | def get_templates_dirs(self): |
|---|
| 151 | return [] |
|---|
| 152 | |
|---|
| 153 | # IWikiSyntaxProvider methods |
|---|
| 154 | |
|---|
| 155 | wiki_pat = r'!?\(\|([-*?._a-z0-9]+)(?:,\s*(\w*))?\|\)' |
|---|
| 156 | wiki_re = re.compile(wiki_pat) |
|---|
| 157 | |
|---|
| 158 | #noinspection PyUnusedLocal |
|---|
| 159 | def _format_icon(self, formatter, match, fullmatch=None): |
|---|
| 160 | m = Icons.wiki_re.match(match) |
|---|
| 161 | name, size = m.group(1, 2) |
|---|
| 162 | return self._render_icon(formatter, name, size) |
|---|
| 163 | |
|---|
| 164 | def get_wiki_syntax(self): |
|---|
| 165 | yield (Icons.wiki_pat, self._format_icon) |
|---|
| 166 | |
|---|
| 167 | def get_link_resolvers(self): |
|---|
| 168 | return [] |
|---|
| 169 | |
|---|
| 170 | # IWikiMacroProvider methods |
|---|
| 171 | |
|---|
| 172 | def get_macros(self): |
|---|
| 173 | yield 'Icon' |
|---|
| 174 | |
|---|
| 175 | #noinspection PyUnusedLocal |
|---|
| 176 | def get_macro_description(self, name): |
|---|
| 177 | return cleandoc("""Shows a named icon that can be in line with text. |
|---|
| 178 | |
|---|
| 179 | Syntax: |
|---|
| 180 | {{{ |
|---|
| 181 | [[Icon(name, size)]] |
|---|
| 182 | }}} |
|---|
| 183 | where |
|---|
| 184 | * `name` is the name of the icon. When `name` contains a |
|---|
| 185 | pattern character (`*` or `?`), a 2-column preview of |
|---|
| 186 | matching icons is presented, which should mainly be used for |
|---|
| 187 | finding and selecting an icon during wiki page editing in |
|---|
| 188 | side-by-side mode (however, no more than %d icons are |
|---|
| 189 | presented to prevent exhaustive network traffic.) |
|---|
| 190 | * `size` is optionally one of `small`, `medium` or `large` or |
|---|
| 191 | an abbreviation thereof (defaults `small`). |
|---|
| 192 | |
|---|
| 193 | Example: |
|---|
| 194 | {{{ |
|---|
| 195 | [[Icon(smiley)]] |
|---|
| 196 | }}} |
|---|
| 197 | |
|---|
| 198 | Use `ShowIcons` for static presentation of available icons. |
|---|
| 199 | Smileys like `:-)` are automatically rendered as icons. Use |
|---|
| 200 | `ShowSmileys` to se all available smileys. |
|---|
| 201 | |
|---|
| 202 | Following wiki markup is equivalent to using this macro: |
|---|
| 203 | {{{ |
|---|
| 204 | (|name, size|) |
|---|
| 205 | }}} |
|---|
| 206 | """ % self.icon_limit) |
|---|
| 207 | |
|---|
| 208 | #noinspection PyUnusedLocal |
|---|
| 209 | def expand_macro(self, formatter, name, content): |
|---|
| 210 | # content = name, size |
|---|
| 211 | if not content: |
|---|
| 212 | return |
|---|
| 213 | args = [a.strip() for a in content.split(',')] + [None, None] |
|---|
| 214 | name, size = args[0], args[1] |
|---|
| 215 | return self._render_icon(formatter, name, size) |
|---|
| 216 | |
|---|
| 217 | |
|---|
| 218 | class ShowIcons(Component): |
|---|
| 219 | """Macro to list available icons on a wiki page. |
|---|
| 220 | |
|---|
| 221 | The `ShowIcons` macro displays a table of available icons, matching a |
|---|
| 222 | search criteria. The number of presented icons can be limited to prevent |
|---|
| 223 | exhaustive network traffic. This limit is defined in the `[wikiextras]` |
|---|
| 224 | section in `trac.ini`. |
|---|
| 225 | """ |
|---|
| 226 | |
|---|
| 227 | implements(ITemplateProvider, IWikiMacroProvider) |
|---|
| 228 | |
|---|
| 229 | showicons_limit = IntOption('wikiextras', 'showicons_limit', 96, |
|---|
| 230 | """To prevent exhaustive network traffic, limit the maximum number of |
|---|
| 231 | icons generated by the macro `ShowIcons`. Set to 0 for |
|---|
| 232 | unlimited number of icons (this will produce exhaustive network |
|---|
| 233 | traffic--you have been warned!)""") |
|---|
| 234 | |
|---|
| 235 | def _render(self, formatter, cols, name_pat, size, header, limit): |
|---|
| 236 | #noinspection PyArgumentList |
|---|
| 237 | icon = Icons(self.env) |
|---|
| 238 | icon_dir = icon.icon_location(size)[1] |
|---|
| 239 | files = fnmatch.filter(os.listdir(icon_dir), '%s.png' % name_pat) |
|---|
| 240 | icon_names = [os.path.splitext(p)[0] for p in files] |
|---|
| 241 | if limit: |
|---|
| 242 | displayed_icon_names = reduce_names(icon_names, limit) |
|---|
| 243 | else: |
|---|
| 244 | displayed_icon_names = icon_names |
|---|
| 245 | icon_table = render_table(displayed_icon_names, cols, |
|---|
| 246 | lambda name: icon._render_icon(formatter, |
|---|
| 247 | name, size)) |
|---|
| 248 | if not len(icon_names): |
|---|
| 249 | message = 'No %s icon matches %s' % (SIZE_DESCR[size], name_pat) |
|---|
| 250 | elif len(icon_names) == 1: |
|---|
| 251 | message = 'Showing the only %s icon matching %s' % \ |
|---|
| 252 | (SIZE_DESCR[size], name_pat) |
|---|
| 253 | elif len(displayed_icon_names) == len(icon_names): |
|---|
| 254 | message = 'Showing all %d %s icons matching %s' % \ |
|---|
| 255 | (len(icon_names), SIZE_DESCR[size], name_pat) |
|---|
| 256 | else: |
|---|
| 257 | message = 'Showing %d of %d %s icons matching %s' % \ |
|---|
| 258 | (len(displayed_icon_names), len(icon_names), |
|---|
| 259 | SIZE_DESCR[size], name_pat) |
|---|
| 260 | return tag.div(tag.p(tag.small(message)) if header else '', icon_table) |
|---|
| 261 | |
|---|
| 262 | # ITemplateProvider methods |
|---|
| 263 | |
|---|
| 264 | def get_htdocs_dirs(self): |
|---|
| 265 | return [] |
|---|
| 266 | |
|---|
| 267 | def get_templates_dirs(self): |
|---|
| 268 | return [] |
|---|
| 269 | |
|---|
| 270 | # IWikiMacroProvider methods |
|---|
| 271 | |
|---|
| 272 | def get_macros(self): |
|---|
| 273 | yield 'ShowIcons' |
|---|
| 274 | |
|---|
| 275 | #noinspection PyUnusedLocal |
|---|
| 276 | def get_macro_description(self, name): |
|---|
| 277 | #noinspection PyStringFormat |
|---|
| 278 | return cleandoc("""Renders in a table a list of available icons. |
|---|
| 279 | No more than %(showicons_limit)d icons are displayed to prevent |
|---|
| 280 | exhaustive network traffic. |
|---|
| 281 | |
|---|
| 282 | Syntax: |
|---|
| 283 | {{{ |
|---|
| 284 | [[ShowIcons(cols, name-pattern, size, header, limit)]] |
|---|
| 285 | }}} |
|---|
| 286 | where |
|---|
| 287 | * `cols` is optionally the number of columns in the table |
|---|
| 288 | (defaults 3). |
|---|
| 289 | * `name-pattern` selects which icons to list (use `*` and |
|---|
| 290 | `?`). |
|---|
| 291 | * `size` is optionally one of `small`, `medium` or `large` or |
|---|
| 292 | an abbreviation thereof (defaults `small`). |
|---|
| 293 | * `header` is optionally one of `header` and `noheader` or |
|---|
| 294 | an abbreviation thereof (header is displayed by default) |
|---|
| 295 | * `limit` specifies an optional upper limit of number of |
|---|
| 296 | displayed icons (however, no more than %(showicons_limit)d |
|---|
| 297 | will be displayed). |
|---|
| 298 | |
|---|
| 299 | The last three optional parameters (`size`, `header` and |
|---|
| 300 | `limit`) can be stated in any order. |
|---|
| 301 | |
|---|
| 302 | Example: |
|---|
| 303 | |
|---|
| 304 | {{{ |
|---|
| 305 | [[ShowIcons(smile*)]] # all small icons matching smile* |
|---|
| 306 | [[ShowIcons(4, smile*)]] # four columns |
|---|
| 307 | [[ShowIcons(smile*, 10)]] # limit to 10 icons |
|---|
| 308 | [[ShowIcons(smile*, 10, nohead)]] # no header |
|---|
| 309 | [[ShowIcons(smile*, m)]] # medium-size |
|---|
| 310 | }}} |
|---|
| 311 | """ % {'showicons_limit': self.showicons_limit}) |
|---|
| 312 | |
|---|
| 313 | #noinspection PyUnusedLocal |
|---|
| 314 | def expand_macro(self, formatter, name, content, args=None): |
|---|
| 315 | # content = cols, name-pattern, size, header, limit |
|---|
| 316 | args = [] |
|---|
| 317 | if content: |
|---|
| 318 | args = [a.strip() for a in content.split(',')] |
|---|
| 319 | args += [''] * 2 |
|---|
| 320 | a = args.pop(0) |
|---|
| 321 | # cols |
|---|
| 322 | if a.isdigit(): |
|---|
| 323 | cols = max(int(a), 1) |
|---|
| 324 | a = args.pop(0) |
|---|
| 325 | else: |
|---|
| 326 | cols = 3 |
|---|
| 327 | # name_pat |
|---|
| 328 | name_pat = a |
|---|
| 329 | if not name_pat: |
|---|
| 330 | name_pat = '*' |
|---|
| 331 | # size, header and limit |
|---|
| 332 | size = 'S' |
|---|
| 333 | header = True |
|---|
| 334 | limit = self.showicons_limit |
|---|
| 335 | while args: |
|---|
| 336 | a = args.pop(0).lower() |
|---|
| 337 | if a.isdigit(): |
|---|
| 338 | limit = min(int(a), limit) |
|---|
| 339 | elif a and any(d.startswith(a) for d in SIZE_DESCR.values()): |
|---|
| 340 | size = a.upper()[0] |
|---|
| 341 | elif a and any(d.startswith(a) for d in ['header', 'noheader']): |
|---|
| 342 | header = a[0].startswith('h') |
|---|
| 343 | return self._render(formatter, cols, name_pat, size, header, limit) |
|---|
| 344 | |
|---|
| 345 | SMILEYS = { |
|---|
| 346 | ':)': 'smiley.png', |
|---|
| 347 | ':-)': 'smiley.png', |
|---|
| 348 | '=)': 'smiley.png', |
|---|
| 349 | ';-)': 'smiley-wink.png', |
|---|
| 350 | ';)': 'smiley-wink.png', |
|---|
| 351 | ':(': 'smiley-sad.png', |
|---|
| 352 | ':-(': 'smiley-sad.png', |
|---|
| 353 | ':|': 'smiley-neutral.png', |
|---|
| 354 | ':-|': 'smiley-neutral.png', |
|---|
| 355 | ':-?': 'smiley-confuse.png', |
|---|
| 356 | ':?': 'smiley-confuse.png', |
|---|
| 357 | ':D': 'smiley-lol.png', |
|---|
| 358 | ':-D': 'smiley-lol.png', |
|---|
| 359 | ':))': 'smiley-grin.png', |
|---|
| 360 | ':-))': 'smiley-grin.png', |
|---|
| 361 | ':-P': 'smiley-razz.png', |
|---|
| 362 | ':P': 'smiley-razz.png', |
|---|
| 363 | ':-O': 'smiley-red.png', |
|---|
| 364 | ':O': 'smiley-red.png', |
|---|
| 365 | ':-o': 'smiley-surprise.png', |
|---|
| 366 | ':o': 'smiley-surprise.png', |
|---|
| 367 | ':-X': 'smiley-zipper.png', |
|---|
| 368 | ':X': 'smiley-zipper.png', |
|---|
| 369 | 'B-)': 'smiley-cool.png', |
|---|
| 370 | '8-)': 'smiley-nerd.png', |
|---|
| 371 | 'B-O': 'smiley-eek.png', |
|---|
| 372 | '8-O': 'smiley-eek.png', |
|---|
| 373 | '>:>': 'smiley-evil.png', |
|---|
| 374 | |
|---|
| 375 | '(!)': 'exclamation-red.png', |
|---|
| 376 | '(?)': 'question.png', |
|---|
| 377 | '(I)': 'light-bulb.png', |
|---|
| 378 | '(*)': 'asterisk.png', |
|---|
| 379 | '(X)': 'cross-circle.png', |
|---|
| 380 | |
|---|
| 381 | '(Y)': 'thumb-up.png', |
|---|
| 382 | '(OK)': 'thumb-up.png', |
|---|
| 383 | '(N)': 'thumb.png', |
|---|
| 384 | '(NOK)': 'thumb.png', |
|---|
| 385 | |
|---|
| 386 | '(./)': 'tick.png', |
|---|
| 387 | } |
|---|
| 388 | |
|---|
| 389 | |
|---|
| 390 | class Smileys(Component): |
|---|
| 391 | """Replace smiley characters like `:-)` with icons. |
|---|
| 392 | |
|---|
| 393 | Smiley characters and icons are configurable in the `[wikiextras-smileys]` |
|---|
| 394 | section in `trac.ini`. Use the `ShowSmileys` macro to display a list of |
|---|
| 395 | currently defined smileys. |
|---|
| 396 | """ |
|---|
| 397 | |
|---|
| 398 | implements(IWikiMacroProvider, IWikiSyntaxProvider) |
|---|
| 399 | |
|---|
| 400 | smileys_section = ConfigSection('wikiextras-smileys', |
|---|
| 401 | """The set of smileys is configurable by providing associations |
|---|
| 402 | between icon names and wiki keywords. A default set of icons and |
|---|
| 403 | keywords is defined, which can be revoked one-by-one (_remove) or |
|---|
| 404 | all at once (_remove_defaults). |
|---|
| 405 | |
|---|
| 406 | Example: |
|---|
| 407 | {{{ |
|---|
| 408 | [wikiextras-smileys] |
|---|
| 409 | _remove_defaults = true |
|---|
| 410 | _remove = :-( :( |
|---|
| 411 | smiley = :-) :) |
|---|
| 412 | smiley-wink = ;-) ;) |
|---|
| 413 | clock = (CLOCK) (TIME) |
|---|
| 414 | calendar-month = (CALENDAR) (DATE) |
|---|
| 415 | chart = (CHART) |
|---|
| 416 | document-excel = (EXCEL) |
|---|
| 417 | document-word = (WORD) |
|---|
| 418 | eye = (EYE) |
|---|
| 419 | new = (NEW) |
|---|
| 420 | tick = (TICK) |
|---|
| 421 | }}} |
|---|
| 422 | |
|---|
| 423 | Keywords are space-separated! |
|---|
| 424 | |
|---|
| 425 | A smiley can also be removed by associating its icon with nothing: |
|---|
| 426 | {{{ |
|---|
| 427 | smiley = |
|---|
| 428 | }}} |
|---|
| 429 | |
|---|
| 430 | Use the `ShowSmileys` macro to find out the current set of icons |
|---|
| 431 | and keywords. |
|---|
| 432 | """) |
|---|
| 433 | |
|---|
| 434 | remove_defaults = BoolOption('wikiextras-smileys', '_remove_defaults', |
|---|
| 435 | False, doc="Set to true to remove all " |
|---|
| 436 | "default smileys.") |
|---|
| 437 | |
|---|
| 438 | remove = ListOption('wikiextras-smileys', '_remove', sep=' ', doc="""\ |
|---|
| 439 | Space-separated(!) list of keywords that shall not be interpreted |
|---|
| 440 | as smileys (even if defined in this section).""") |
|---|
| 441 | |
|---|
| 442 | def __init__(self): |
|---|
| 443 | self.smileys = None |
|---|
| 444 | |
|---|
| 445 | # IWikiSyntaxProvider methods |
|---|
| 446 | |
|---|
| 447 | def get_wiki_syntax(self): |
|---|
| 448 | if self.smileys is None: |
|---|
| 449 | self.smileys = SMILEYS.copy() |
|---|
| 450 | if self.remove_defaults: |
|---|
| 451 | self.smileys = {} |
|---|
| 452 | for icon_name, value in self.smileys_section.options(): |
|---|
| 453 | if not icon_name.startswith('_remove'): |
|---|
| 454 | icon_file = icon_name |
|---|
| 455 | if not icon_file.endswith('.png'): |
|---|
| 456 | icon_file = '%s.png' % icon_file |
|---|
| 457 | if value: |
|---|
| 458 | for keyword in value.split(): |
|---|
| 459 | self.smileys[keyword.strip()] = icon_file |
|---|
| 460 | else: |
|---|
| 461 | # no keyword, remove all smileys associated with icon |
|---|
| 462 | for k in self.smileys.keys(): |
|---|
| 463 | if self.smileys[k] == icon_file: |
|---|
| 464 | del self.smileys[k] |
|---|
| 465 | for keyword in self.remove: |
|---|
| 466 | if keyword in self.smileys: |
|---|
| 467 | del self.smileys[keyword] |
|---|
| 468 | |
|---|
| 469 | if self.smileys: |
|---|
| 470 | yield (r"(?<!\w)!?(?:%s)" % prepare_regexp(self.smileys), |
|---|
| 471 | self._format_smiley) |
|---|
| 472 | else: |
|---|
| 473 | yield (None, None) |
|---|
| 474 | |
|---|
| 475 | def get_link_resolvers(self): |
|---|
| 476 | return [] |
|---|
| 477 | |
|---|
| 478 | #noinspection PyUnusedLocal |
|---|
| 479 | def _format_smiley(self, formatter, match, fullmatch=None): |
|---|
| 480 | #noinspection PyArgumentList |
|---|
| 481 | loc = Icons(self.env).icon_location() |
|---|
| 482 | return tag.img(src=formatter.href.chrome(loc[0], self.smileys[match]), |
|---|
| 483 | alt=match, style="vertical-align: text-bottom") |
|---|
| 484 | |
|---|
| 485 | # IWikiMacroProvider methods |
|---|
| 486 | |
|---|
| 487 | def get_macros(self): |
|---|
| 488 | yield 'ShowSmileys' |
|---|
| 489 | |
|---|
| 490 | #noinspection PyUnusedLocal |
|---|
| 491 | def get_macro_description(self, name): |
|---|
| 492 | return cleandoc("""Renders in a table the list of available smileys. |
|---|
| 493 | Optional argument is the number of columns in the table |
|---|
| 494 | (defaults 3). |
|---|
| 495 | |
|---|
| 496 | Comment: Prefixing a character sequence with `!` prevents it |
|---|
| 497 | from being interpreted as a smiley. |
|---|
| 498 | """) |
|---|
| 499 | |
|---|
| 500 | #noinspection PyUnusedLocal |
|---|
| 501 | def expand_macro(self, formatter, name, content, args=None): |
|---|
| 502 | # Merge smileys for presentation |
|---|
| 503 | # First collect wikitexts for each unique filename |
|---|
| 504 | syelims = {} # key=filename, value=wikitext |
|---|
| 505 | for wikitext, filename in self.smileys.iteritems(): |
|---|
| 506 | if filename not in syelims: |
|---|
| 507 | syelims[filename] = [wikitext] |
|---|
| 508 | else: |
|---|
| 509 | syelims[filename].append(wikitext) |
|---|
| 510 | # Reverse |
|---|
| 511 | smileys = {} |
|---|
| 512 | for filename, wikitexts in syelims.iteritems(): |
|---|
| 513 | wikitexts.sort() |
|---|
| 514 | smileys[' '.join(wikitexts)] = filename |
|---|
| 515 | return render_table(smileys.keys(), content, |
|---|
| 516 | lambda s: self._format_smiley(formatter, |
|---|
| 517 | s.split(' ', 1)[0])) |
|---|
| 518 | |
|---|
| 519 | |
|---|
| 520 | class AboutWikiIcons(Component): |
|---|
| 521 | """Macro for displaying a wiki page on how to use icons and smileys. |
|---|
| 522 | |
|---|
| 523 | Create a wiki page `WikiIcons` and insert the following line to show |
|---|
| 524 | detailed instructions to wiki authors on how to use icons and smileys in |
|---|
| 525 | wiki pages: |
|---|
| 526 | {{{ |
|---|
| 527 | [[AboutWikiIcons]] |
|---|
| 528 | }}} |
|---|
| 529 | """ |
|---|
| 530 | |
|---|
| 531 | implements(IWikiMacroProvider) |
|---|
| 532 | |
|---|
| 533 | # IWikiMacroProvider methods |
|---|
| 534 | |
|---|
| 535 | def get_macros(self): |
|---|
| 536 | yield 'AboutWikiIcons' |
|---|
| 537 | |
|---|
| 538 | #noinspection PyUnusedLocal |
|---|
| 539 | def get_macro_description(self, name): |
|---|
| 540 | return "Display a wiki page on how to use icons." |
|---|
| 541 | |
|---|
| 542 | #noinspection PyUnusedLocal |
|---|
| 543 | def expand_macro(self, formatter, name, content, args=None): |
|---|
| 544 | help_file = resource_filename(__name__, 'doc/WikiIcons') |
|---|
| 545 | fd = open(help_file, 'r') |
|---|
| 546 | wiki_text = fd.read() |
|---|
| 547 | fd.close() |
|---|
| 548 | return format_to_html(self.env, formatter.context, wiki_text) |
|---|