Back to index

moin  1.9.0~rc2
SlideShow.py
Go to the documentation of this file.
00001 # -*- coding: utf-8 -*-
00002 """
00003 MoinMoin - SlideShow action
00004 
00005 Treat a wiki page as a set of slides.  Displays a single slide at a
00006 time, along with a navigation aid.
00007 
00008 @copyright: 2005 Jim Clark,
00009             2005 Nir Soffer,
00010             2008 MoinMoin:ThomasWaldmann,
00011             2009 MoinMoin:ReimarBauer
00012 @license: GNU GPL, see COPYING for details.
00013 """
00014 
00015 import re, time
00016 
00017 from MoinMoin import wikiutil, i18n, error
00018 from MoinMoin.Page import Page
00019 
00020 Dependencies = ['language']
00021 
00022 
00023 class Error(error.Error):
00024     """ Raised for errors in this module """
00025 
00026 # This could be delivered in a separate plugin, but
00027 # it is more convenient to have everything in one module.
00028 
00029 class WikiSlideParser(object):
00030     """ Parse slides using wiki format
00031 
00032     Typical usage::
00033         for title, start, end in WikiSlideParser().parse(text):
00034             slides.append((title, start, end))
00035 
00036     If you want to override this parser, you can add 'slideshow_wiki'
00037     parser plugin, that provides a SlideParser class.
00038     """
00039     _heading_pattern = re.compile(r"""
00040         # TODO: check, mhz found bug here
00041         (?P<skip>{{{(?:.*\n)+?}}}) |
00042         # Match headings level 1
00043         (?P<heading>^=\s(?P<text>.*)\s=$\n?)
00044         """, re.MULTILINE | re.UNICODE | re.VERBOSE)
00045 
00046     def parse(self, text):
00047         """ Parse slide data in text
00048 
00049         Wiki slides are defined by the headings, ignoring the text
00050         before the first heading. This parser finds all headings,
00051         skipping headings in preformatted code areas.
00052 
00053         Returns an iterator over slide data. For each slide, a tuple
00054         (title, bodyStart, bodyEnd) is returned. bodyStart and bodyEnd
00055         are indexes into text.
00056         """
00057         matches = [match for match in self._heading_pattern.finditer(text)
00058                    if match.start('skip') == -1]
00059 
00060         for i in range(len(matches)):
00061             title = matches[i].group('text').strip()
00062             bodyStart = matches[i].end('heading')
00063             try:
00064                 bodyEnd = matches[i + 1].start('heading')
00065             except IndexError:
00066                 bodyEnd = len(text)
00067             yield title, bodyStart, bodyEnd
00068 
00069 
00070 class SlidePage(Page):
00071     """ A wiki page containing a slideshow
00072 
00073     The slides are parsed according to the page #format xxx processing
00074     instruction. This module implements only a wiki format slide parser.
00075 
00076     To support other formats like rst, add a 'slideshow_rst' parser
00077     plugin, providing SlideParser class, implementing the SlideParser
00078     protocol. See WikiSlideParser for details.
00079     """
00080     defaultFormat = 'wiki'
00081     defaultParser = WikiSlideParser
00082 
00083     def __init__(self, request, name, **keywords):
00084         Page.__init__(self, request, name, **keywords)
00085         self._slideIndex = None
00086         self.counter = ''
00087 
00088     def __len__(self):
00089         """ Return the slide count """
00090         return len(self.slideIndex())
00091 
00092     def isEmpty(self):
00093         return len(self) == 0
00094 
00095     # Slide accessing methods map 1 based slides to 0 based index.
00096 
00097     def titleAt(self, number):
00098         """ Return the title of slide number """
00099         try:
00100             return self.slideIndex()[number - 1][0]
00101         except IndexError:
00102             return 1
00103 
00104     def bodyAt(self, number):
00105         """ Return the body of slide number """
00106         try:
00107             start, end = self.slideIndex()[number - 1][1:]
00108             return self.get_raw_body()[start:end]
00109         except IndexError:
00110             return self.get_raw_body()
00111 
00112     # Private ----------------------------------------------------------------
00113 
00114     def slideIndex(self):
00115         if self._slideIndex is None:
00116             self.parseSlides()
00117         return self._slideIndex
00118 
00119     def parseSlides(self):
00120         body = self.get_raw_body()
00121         self._slideIndex = []
00122         parser = self.createSlideParser()
00123         for title, bodyStart, bodyEnd in parser.parse(body):
00124             self._slideIndex.append((title, bodyStart, bodyEnd))
00125 
00126     def createSlideParser(self):
00127         """ Import plugin and return parser class
00128 
00129         If plugin is not found, and format is not defaultFormat, raise an error.
00130         For defaultFormat, use builtin defaultParser in this module.
00131         """
00132         format = self.pi['format']
00133         plugin = 'slideshow_' + format
00134         try:
00135             Parser = wikiutil.importPlugin(self.request.cfg, 'parser', plugin, 'SlideParser')
00136         except wikiutil.PluginMissingError:
00137             if format != self.defaultFormat:
00138                 raise Error('SlideShow does not support %s format.' % format)
00139             Parser = self.defaultParser
00140         return Parser()
00141 
00142 
00143 class SlideshowAction:
00144 
00145     name = 'SlideShow'
00146     maxSlideLinks = 15
00147 
00148     def __init__(self, request, pagename, template):
00149         self.request = request
00150         self.page = SlidePage(self.request, pagename)
00151         self.template = template
00152 
00153         # Cache values used many times
00154         self.pageURL = self.page.url(request)
00155 
00156     def execute(self):
00157         _ = self.request.getText
00158         try:
00159             self.setSlideNumber()
00160             language = self.page.pi['language']
00161             self.request.setContentLanguage(language)
00162             self.request.write(self.template % self)
00163         except Error, err:
00164             self.request.theme.add_msg(unicode(err), "error")
00165             self.page.send_page()
00166 
00167     # Private ----------------------------------------------------------------
00168 
00169     def setSlideNumber(self):
00170         try:
00171             slideNumber = int(self.request.values.get('n', 1))
00172             if not 1 <= slideNumber <= len(self.page):
00173                 slideNumber = 1
00174         except ValueError:
00175             slideNumber = 1
00176         self.slideNumber = slideNumber
00177 
00178     def createParser(self, format, text):
00179         if format == "wiki":
00180             format = 'text_moin_wiki'
00181         try:
00182             Parser = wikiutil.importPlugin(self.request.cfg, 'parser', format,
00183                                            'Parser')
00184         except wikiutil.PluginMissingError:
00185             from MoinMoin.parser.text import Parser
00186         parser = Parser(text, self.request)
00187         return parser
00188 
00189     def createFormatter(self, format):
00190         try:
00191             Formatter = wikiutil.importPlugin(self.request.cfg, 'formatter',
00192                                               format, 'Formatter')
00193         except wikiutil.PluginMissingError:
00194             from MoinMoin.formatter.text_plain import Formatter
00195 
00196         formatter = Formatter(self.request)
00197         self.request.formatter = formatter
00198         formatter.page = self.page
00199         return formatter
00200 
00201     def languageAttributes(self, lang):
00202         return ' lang="%s" dir="%s"' % (lang, i18n.getDirection(lang))
00203 
00204     def linkToPage(self, text, query='', **attributes):
00205         """ Return a link to current page """
00206         if query:
00207             url = '%s?%s' % (self.pageURL, query)
00208         else:
00209             url = self.pageURL
00210         return self.formatLink(url, text, **attributes)
00211 
00212     def linkToSlide(self, number, text, **attributes):
00213         """ Return a link to current page """
00214         if number == self.slideNumber:
00215             return self.disabledLink(text, **attributes)
00216 
00217         url = '%s?action=%s&n=%s' % (self.pageURL, self.name, number)
00218         return self.formatLink(url, text, **attributes)
00219 
00220     def disabledLink(self, text, **attributes):
00221         return '<span%s>%s</span>' % (self.formatAttributes(attributes), text)
00222 
00223     def formatLink(self, url, text, **attributes):
00224         return '<a href="%(url)s"%(attributes)s>%(text)s</a>' % {
00225             'url': wikiutil.escape(url),
00226             'attributes': self.formatAttributes(attributes),
00227             'text': wikiutil.escape(text),
00228             }
00229 
00230     def formatAttributes(self, attributes):
00231         """ Return formatted attributes string """
00232         formattedPairs = [' %s="%s"' % (k, v) for k, v in attributes.items()]
00233         return ''.join(formattedPairs)
00234 
00235     def adaptToLanguage(self, direction):
00236         # In RTL, directional items should be switched
00237         if i18n.getDirection(self.request.lang) == 'rtl':
00238             return not direction
00239         return direction
00240 
00241     def forwardIcon(self, forward=True):
00242         return [u'\u2190', u'\u2192'][self.adaptToLanguage(forward)]
00243 
00244     def backIcon(self):
00245         return self.forwardIcon(False)
00246 
00247     # Key codes constants
00248     rightArrowKey = 39
00249     leftArrowKey = 37
00250 
00251     def slideLinksRange(self):
00252         """ Return range of slides to display, current centered """
00253         other = self.maxSlideLinks - 1 # other slides except current
00254         first, last = self.first_slide(), self.last_slide()
00255         start = max(first, self.slideNumber - other / 2)
00256         end = min(start + other, last)
00257         start = max(first, end - other)
00258         return range(start, end + 1)
00259 
00260     def first_slide(self):
00261         return 1
00262 
00263     def next_slide(self):
00264         return min(self.slideNumber + 1, self.last_slide())
00265 
00266     def previous_slide(self):
00267         return max(self.slideNumber - 1, self.first_slide())
00268 
00269     def last_slide(self):
00270         return max(len(self.page), 1)
00271 
00272     # Replacing methods ------------------------------------------------------
00273 
00274     def __getitem__(self, name):
00275         item = getattr(self, 'item_' + name)
00276         if callable(item):
00277             return item()
00278         else:
00279             return item
00280 
00281     def item_language_attribtues(self):
00282         return self.languageAttributes(self.request.content_lang)
00283 
00284     def item_theme_url(self):
00285         return '%s/%s' % (self.request.cfg.url_prefix_static, self.request.theme.name)
00286 
00287     item_action_name = name
00288 
00289     def item_title(self):
00290         return wikiutil.escape(self.page.page_name)
00291 
00292     def item_slide_title(self):
00293         return wikiutil.escape(self.page.titleAt(self.slideNumber))
00294 
00295     def item_slide_body(self):
00296         text = self.page.bodyAt(self.slideNumber)
00297         format = self.page.pi['format']
00298         parser = self.createParser(format, text)
00299         formatter = self.createFormatter('text_html')
00300         return self.request.redirectedOutput(parser.format, formatter)
00301 
00302     def item_navigation_language_attributes(self):
00303         return self.languageAttributes(self.request.lang)
00304 
00305     def item_navigation_edit(self):
00306         _ = self.request.getText
00307         text = _('Edit')
00308         if self.request.user.may.write(self.page.page_name):
00309             return self.linkToPage(text, 'action=edit', title=_('Edit slide show'))
00310         return self.disabledLink(text, title=_("You are not allowed to edit this page."))
00311 
00312     def item_navigation_quit(self):
00313         _ = self.request.getText
00314         return self.linkToPage(_('Quit'), title=_('Quit slide show'))
00315 
00316     def item_navigation_start(self):
00317         _ = self.request.getText
00318         number = self.first_slide()
00319         return self.linkToSlide(number, '|', title=_('Show first slide (up arrow)'))
00320 
00321     def item_navigation_end(self):
00322         _ = self.request.getText
00323         number = self.last_slide()
00324         return self.linkToSlide(number, '|', title=_('Show last slide (down arrow)'))
00325 
00326     def item_navigation_back(self):
00327         _ = self.request.getText
00328         number = self.previous_slide()
00329         return self.linkToSlide(number, text=self.backIcon(), title=_('Show previous slide (left arrow)'))
00330 
00331     def item_navigation_forward(self):
00332         _ = self.request.getText
00333         number = self.next_slide()
00334         return self.linkToSlide(number, self.forwardIcon(), title=_('Show next slide (right arrow)'))
00335 
00336     def item_forward_key(self, forward=True):
00337         return (self.leftArrowKey, self.rightArrowKey)[self.adaptToLanguage(forward)]
00338 
00339     def item_back_key(self):
00340         return self.item_forward_key(False)
00341 
00342     def item_navigation_slides(self):
00343         items = []
00344         for i in self.slideLinksRange():
00345             attributes = {'title': self.page.titleAt(i)}
00346             if i == self.slideNumber:
00347                 attributes = {'class': 'current'}
00348             items.append(self.linkToSlide(i, i, **attributes))
00349         items = ['<li>%s</li>' % item for item in items]
00350         return '\n'.join(items)
00351 
00352     def item_slide_link_base(self):
00353         return wikiutil.escape(self.pageURL) + '?action=%s&n=' % self.name
00354 
00355     item_next_slide = next_slide
00356     item_previous_slide = previous_slide
00357     item_first_slide = first_slide
00358     item_last_slide = last_slide
00359 
00360     def item_date(self):
00361         return wikiutil.escape(self.request.getPragma('date', defval=''))
00362 
00363     def item_author(self):
00364         return wikiutil.escape(self.request.getPragma('author', defval=''))
00365 
00366     def item_counter(self):
00367         return "%d|%d" % (self.slideNumber, self.last_slide())
00368 
00369 # This is quite stupid template, but it cleans most of the code from
00370 # html. With smarter templates, there will be no html in the action code.
00371 template = """
00372 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
00373     "http://www.w3.org/TR/html4/strict.dtd">
00374 
00375 <html%(language_attribtues)s>
00376 <head>
00377     <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
00378     <meta name="robots" content="noindex,nofollow">
00379     <title>%(title)s</title>
00380 
00381     <script type="text/javascript">
00382         function getKey(e) {
00383             // Support multiple browsers stupidity
00384             var key;
00385             if (e == null) {
00386                 // IE
00387                 key = event.keyCode;
00388             } else {
00389                 // Standards compliant
00390                 if (e.altKey || e.ctrlKey) {
00391                     return null;
00392                 }
00393                 key = e.which;
00394             }
00395             return key;
00396         }
00397 
00398         function go(slide) {
00399             window.location="%(slide_link_base)s" + slide;
00400         }
00401 
00402         function onkeydown(e) {
00403             switch(getKey(e)) {
00404                 // presenter maybe rather wants to use up/down for scrolling content!
00405                 // case 38: go('%(first_slide)s'); break; // up arrow
00406                 // case 40: go('%(last_slide)s'); break; // down arrow
00407                 case %(forward_key)s: go('%(next_slide)s'); break;
00408                 case %(back_key)s: go('%(previous_slide)s'); break;
00409                 default: return true; // pass event to browser
00410             }
00411             // Return false to consume the event
00412             return false;
00413         }
00414 
00415         document.onkeydown = onkeydown
00416     </script>
00417 
00418     <link rel="stylesheet" type="text/css" charset="utf-8" media="all"
00419         href="%(theme_url)s/css/%(action_name)s.css">
00420 </head>
00421 
00422 <body>
00423     <h1>%(slide_title)s</h1>
00424 
00425     <div id="content">
00426         %(slide_body)s
00427     </div>
00428 
00429     <div id="navigation"%(navigation_language_attributes)s>
00430         <ul>
00431             <li>%(navigation_edit)s</li>
00432             <li>%(navigation_quit)s</li>
00433             <li>%(navigation_start)s</li>
00434             <li>%(navigation_back)s</li>
00435             %(navigation_slides)s
00436             <li>%(navigation_forward)s</li>
00437             <li>%(navigation_end)s</li>
00438         </ul>
00439     </div>
00440     <div id="footer">
00441     <ul id="date">%(date)s</ul>
00442     <ul id="author">%(author)s</ul>
00443     <ul id="counter">%(counter)s</ul>
00444     </div>
00445 <!--
00446     <p><a href="http://validator.w3.org/check?uri=referer">
00447         Valid HTML 4.01</a>
00448     </p>
00449  -->
00450 </body>
00451 </html>
00452 """
00453 
00454 
00455 def execute(pagename, request):
00456     """ Glue to current plugin system """
00457     SlideshowAction(request, pagename, template).execute()
00458