~ubuntu-branches/ubuntu/natty/moin/natty-updates

« back to all changes in this revision

Viewing changes to MoinMoin/action/SlideShow.py

  • Committer: Bazaar Package Importer
  • Author(s): Jonas Smedegaard
  • Date: 2008-06-22 21:17:13 UTC
  • mto: This revision was merged to the branch mainline in revision 18.
  • Revision ID: james.westby@ubuntu.com-20080622211713-inlv5k4eifxckelr
ImportĀ upstreamĀ versionĀ 1.7.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: utf-8 -*-
2
 
"""
3
 
MoinMoin - SlideShow action
4
 
 
5
 
Treat a wiki page as a set of slides.  Displays a single slide at a
6
 
time, along with a navigation aid.
7
 
 
8
 
@copyright: 2005 Jim Clark,
9
 
            2005 Nir Soffer,
10
 
            2008 MoinMoin:ThomasWaldmann,
11
 
            2009 MoinMoin:ReimarBauer
12
 
@license: GNU GPL, see COPYING for details.
13
 
"""
14
 
 
15
 
import re, time
16
 
 
17
 
from MoinMoin import config, wikiutil, i18n, error
18
 
from MoinMoin.Page import Page
19
 
 
20
 
Dependencies = ['language']
21
 
 
22
 
 
23
 
class Error(error.Error):
24
 
    """ Raised for errors in this module """
25
 
 
26
 
# This could be delivered in a separate plugin, but
27
 
# it is more convenient to have everything in one module.
28
 
 
29
 
class WikiSlideParser(object):
30
 
    """ Parse slides using wiki format
31
 
 
32
 
    Typical usage::
33
 
        for title, start, end in WikiSlideParser().parse(text):
34
 
            slides.append((title, start, end))
35
 
 
36
 
    If you want to override this parser, you can add 'slideshow_wiki'
37
 
    parser plugin, that provides a SlideParser class.
38
 
    """
39
 
    _heading_pattern = re.compile(r"""
40
 
        # TODO: check, mhz found bug here
41
 
        (?P<skip>{{{(?:.*\n)+?}}}) |
42
 
        # Match headings level 1
43
 
        (?P<heading>^=\s(?P<text>.*)\s=$\n?)
44
 
        """, re.MULTILINE | re.UNICODE | re.VERBOSE)
45
 
 
46
 
    def parse(self, text):
47
 
        """ Parse slide data in text
48
 
 
49
 
        Wiki slides are defined by the headings, ignoring the text
50
 
        before the first heading. This parser finds all headings,
51
 
        skipping headings in preformatted code areas.
52
 
 
53
 
        Returns an iterator over slide data. For each slide, a tuple
54
 
        (title, bodyStart, bodyEnd) is returned. bodyStart and bodyEnd
55
 
        are indexes into text.
56
 
        """
57
 
        matches = [match for match in self._heading_pattern.finditer(text)
58
 
                   if match.start('skip') == -1]
59
 
 
60
 
        for i in range(len(matches)):
61
 
            title = matches[i].group('text').strip()
62
 
            bodyStart = matches[i].end('heading')
63
 
            try:
64
 
                bodyEnd = matches[i + 1].start('heading')
65
 
            except IndexError:
66
 
                bodyEnd = len(text)
67
 
            yield title, bodyStart, bodyEnd
68
 
 
69
 
 
70
 
class SlidePage(Page):
71
 
    """ A wiki page containing a slideshow
72
 
 
73
 
    The slides are parsed according to the page #format xxx processing
74
 
    instruction. This module implements only a wiki format slide parser.
75
 
 
76
 
    To support other formats like rst, add a 'slideshow_rst' parser
77
 
    plugin, providing SlideParser class, implementing the SlideParser
78
 
    protocol. See WikiSlideParser for details.
79
 
    """
80
 
    defaultFormat = 'wiki'
81
 
    defaultParser = WikiSlideParser
82
 
 
83
 
    def __init__(self, request, name, **keywords):
84
 
        Page.__init__(self, request, name, **keywords)
85
 
        self._slideIndex = None
86
 
        self.counter = ''
87
 
 
88
 
    def __len__(self):
89
 
        """ Return the slide count """
90
 
        return len(self.slideIndex())
91
 
 
92
 
    def isEmpty(self):
93
 
        return len(self) == 0
94
 
 
95
 
    # Slide accessing methods map 1 based slides to 0 based index.
96
 
 
97
 
    def titleAt(self, number):
98
 
        """ Return the title of slide number """
99
 
        try:
100
 
            return self.slideIndex()[number - 1][0]
101
 
        except IndexError:
102
 
            return 1
103
 
 
104
 
    def bodyAt(self, number):
105
 
        """ Return the body of slide number """
106
 
        try:
107
 
            start, end = self.slideIndex()[number - 1][1:]
108
 
            return self.get_raw_body()[start:end]
109
 
        except IndexError:
110
 
            return self.get_raw_body()
111
 
 
112
 
    # Private ----------------------------------------------------------------
113
 
 
114
 
    def slideIndex(self):
115
 
        if self._slideIndex is None:
116
 
            self.parseSlides()
117
 
        return self._slideIndex
118
 
 
119
 
    def parseSlides(self):
120
 
        body = self.get_raw_body()
121
 
        self._slideIndex = []
122
 
        parser = self.createSlideParser()
123
 
        for title, bodyStart, bodyEnd in parser.parse(body):
124
 
            self._slideIndex.append((title, bodyStart, bodyEnd))
125
 
 
126
 
    def createSlideParser(self):
127
 
        """ Import plugin and return parser class
128
 
 
129
 
        If plugin is not found, and format is not defaultFormat, raise an error.
130
 
        For defaultFormat, use builtin defaultParser in this module.
131
 
        """
132
 
        format = self.pi['format']
133
 
        plugin = 'slideshow_' + format
134
 
        try:
135
 
            Parser = wikiutil.importPlugin(self.request.cfg, 'parser', plugin, 'SlideParser')
136
 
        except wikiutil.PluginMissingError:
137
 
            if format != self.defaultFormat:
138
 
                raise Error('SlideShow does not support %s format.' % format)
139
 
            Parser = self.defaultParser
140
 
        return Parser()
141
 
 
142
 
 
143
 
class SlideshowAction:
144
 
 
145
 
    name = 'SlideShow'
146
 
    maxSlideLinks = 15
147
 
 
148
 
    def __init__(self, request, pagename, template):
149
 
        self.request = request
150
 
        self.page = SlidePage(self.request, pagename)
151
 
        self.template = template
152
 
 
153
 
        # Cache values used many times
154
 
        self.pageURL = self.page.url(request)
155
 
 
156
 
    def execute(self):
157
 
        _ = self.request.getText
158
 
        try:
159
 
            self.setSlideNumber()
160
 
            language = self.page.pi['language']
161
 
            self.request.content_type = "text/html; charset=%s" % (config.charset, )
162
 
            self.request.setContentLanguage(language)
163
 
            self.request.write(self.template % self)
164
 
        except Error, err:
165
 
            self.request.theme.add_msg(wikiutil.escape(unicode(err)), "error")
166
 
            self.page.send_page()
167
 
 
168
 
    # Private ----------------------------------------------------------------
169
 
 
170
 
    def setSlideNumber(self):
171
 
        try:
172
 
            slideNumber = int(self.request.values.get('n', 1))
173
 
            if not 1 <= slideNumber <= len(self.page):
174
 
                slideNumber = 1
175
 
        except ValueError:
176
 
            slideNumber = 1
177
 
        self.slideNumber = slideNumber
178
 
 
179
 
    def createParser(self, format, text):
180
 
        if format == "wiki":
181
 
            format = 'text_moin_wiki'
182
 
        try:
183
 
            Parser = wikiutil.importPlugin(self.request.cfg, 'parser', format,
184
 
                                           'Parser')
185
 
        except wikiutil.PluginMissingError:
186
 
            from MoinMoin.parser.text import Parser
187
 
        parser = Parser(text, self.request)
188
 
        return parser
189
 
 
190
 
    def createFormatter(self, format):
191
 
        try:
192
 
            Formatter = wikiutil.importPlugin(self.request.cfg, 'formatter',
193
 
                                              format, 'Formatter')
194
 
        except wikiutil.PluginMissingError:
195
 
            from MoinMoin.formatter.text_plain import Formatter
196
 
 
197
 
        formatter = Formatter(self.request)
198
 
        self.request.formatter = formatter
199
 
        formatter.page = self.page
200
 
        return formatter
201
 
 
202
 
    def languageAttributes(self, lang):
203
 
        return ' lang="%s" dir="%s"' % (lang, i18n.getDirection(lang))
204
 
 
205
 
    def linkToPage(self, text, query='', **attributes):
206
 
        """ Return a link to current page """
207
 
        if query:
208
 
            url = '%s?%s' % (self.pageURL, query)
209
 
        else:
210
 
            url = self.pageURL
211
 
        return self.formatLink(url, text, **attributes)
212
 
 
213
 
    def linkToSlide(self, number, text, **attributes):
214
 
        """ Return a link to current page """
215
 
        if number == self.slideNumber:
216
 
            return self.disabledLink(text, **attributes)
217
 
 
218
 
        url = '%s?action=%s&n=%s' % (self.pageURL, self.name, number)
219
 
        return self.formatLink(url, text, **attributes)
220
 
 
221
 
    def disabledLink(self, text, **attributes):
222
 
        return '<span%s>%s</span>' % (self.formatAttributes(attributes), text)
223
 
 
224
 
    def formatLink(self, url, text, **attributes):
225
 
        return '<a href="%(url)s"%(attributes)s>%(text)s</a>' % {
226
 
            'url': wikiutil.escape(url),
227
 
            'attributes': self.formatAttributes(attributes),
228
 
            'text': wikiutil.escape(text),
229
 
            }
230
 
 
231
 
    def formatAttributes(self, attributes):
232
 
        """ Return formatted attributes string """
233
 
        formattedPairs = [' %s="%s"' % (k, v) for k, v in attributes.items()]
234
 
        return ''.join(formattedPairs)
235
 
 
236
 
    def adaptToLanguage(self, direction):
237
 
        # In RTL, directional items should be switched
238
 
        if i18n.getDirection(self.request.lang) == 'rtl':
239
 
            return not direction
240
 
        return direction
241
 
 
242
 
    def forwardIcon(self, forward=True):
243
 
        return [u'\u2190', u'\u2192'][self.adaptToLanguage(forward)]
244
 
 
245
 
    def backIcon(self):
246
 
        return self.forwardIcon(False)
247
 
 
248
 
    # Key codes constants
249
 
    rightArrowKey = 39
250
 
    leftArrowKey = 37
251
 
 
252
 
    def slideLinksRange(self):
253
 
        """ Return range of slides to display, current centered """
254
 
        other = self.maxSlideLinks - 1 # other slides except current
255
 
        first, last = self.first_slide(), self.last_slide()
256
 
        start = max(first, self.slideNumber - other / 2)
257
 
        end = min(start + other, last)
258
 
        start = max(first, end - other)
259
 
        return range(start, end + 1)
260
 
 
261
 
    def first_slide(self):
262
 
        return 1
263
 
 
264
 
    def next_slide(self):
265
 
        return min(self.slideNumber + 1, self.last_slide())
266
 
 
267
 
    def previous_slide(self):
268
 
        return max(self.slideNumber - 1, self.first_slide())
269
 
 
270
 
    def last_slide(self):
271
 
        return max(len(self.page), 1)
272
 
 
273
 
    # Replacing methods ------------------------------------------------------
274
 
 
275
 
    def __getitem__(self, name):
276
 
        item = getattr(self, 'item_' + name)
277
 
        if callable(item):
278
 
            return item()
279
 
        else:
280
 
            return item
281
 
 
282
 
    def item_language_attribtues(self):
283
 
        return self.languageAttributes(self.request.content_lang)
284
 
 
285
 
    def item_theme_url(self):
286
 
        return '%s/%s' % (self.request.cfg.url_prefix_static, self.request.theme.name)
287
 
 
288
 
    item_action_name = name
289
 
 
290
 
    def item_title(self):
291
 
        return wikiutil.escape(self.page.page_name)
292
 
 
293
 
    def item_slide_title(self):
294
 
        return wikiutil.escape(self.page.titleAt(self.slideNumber))
295
 
 
296
 
    def item_slide_body(self):
297
 
        text = self.page.bodyAt(self.slideNumber)
298
 
        format = self.page.pi['format']
299
 
        parser = self.createParser(format, text)
300
 
        formatter = self.createFormatter('text_html')
301
 
        return self.request.redirectedOutput(parser.format, formatter)
302
 
 
303
 
    def item_navigation_language_attributes(self):
304
 
        return self.languageAttributes(self.request.lang)
305
 
 
306
 
    def item_navigation_edit(self):
307
 
        _ = self.request.getText
308
 
        text = _('Edit')
309
 
        if self.request.user.may.write(self.page.page_name):
310
 
            return self.linkToPage(text, 'action=edit', title=_('Edit slide show'))
311
 
        return self.disabledLink(text, title=_("You are not allowed to edit this page."))
312
 
 
313
 
    def item_navigation_quit(self):
314
 
        _ = self.request.getText
315
 
        return self.linkToPage(_('Quit'), title=_('Quit slide show'))
316
 
 
317
 
    def item_navigation_start(self):
318
 
        _ = self.request.getText
319
 
        number = self.first_slide()
320
 
        return self.linkToSlide(number, '|', title=_('Show first slide (up arrow)'))
321
 
 
322
 
    def item_navigation_end(self):
323
 
        _ = self.request.getText
324
 
        number = self.last_slide()
325
 
        return self.linkToSlide(number, '|', title=_('Show last slide (down arrow)'))
326
 
 
327
 
    def item_navigation_back(self):
328
 
        _ = self.request.getText
329
 
        number = self.previous_slide()
330
 
        return self.linkToSlide(number, text=self.backIcon(), title=_('Show previous slide (left arrow)'))
331
 
 
332
 
    def item_navigation_forward(self):
333
 
        _ = self.request.getText
334
 
        number = self.next_slide()
335
 
        return self.linkToSlide(number, self.forwardIcon(), title=_('Show next slide (right arrow)'))
336
 
 
337
 
    def item_forward_key(self, forward=True):
338
 
        return (self.leftArrowKey, self.rightArrowKey)[self.adaptToLanguage(forward)]
339
 
 
340
 
    def item_back_key(self):
341
 
        return self.item_forward_key(False)
342
 
 
343
 
    def item_navigation_slides(self):
344
 
        items = []
345
 
        for i in self.slideLinksRange():
346
 
            attributes = {'title': self.page.titleAt(i)}
347
 
            if i == self.slideNumber:
348
 
                attributes = {'class': 'current'}
349
 
            items.append(self.linkToSlide(i, i, **attributes))
350
 
        items = ['<li>%s</li>' % item for item in items]
351
 
        return '\n'.join(items)
352
 
 
353
 
    def item_slide_link_base(self):
354
 
        return wikiutil.escape(self.pageURL) + '?action=%s&n=' % self.name
355
 
 
356
 
    item_next_slide = next_slide
357
 
    item_previous_slide = previous_slide
358
 
    item_first_slide = first_slide
359
 
    item_last_slide = last_slide
360
 
 
361
 
    def item_date(self):
362
 
        return wikiutil.escape(self.request.getPragma('date', defval=''))
363
 
 
364
 
    def item_author(self):
365
 
        return wikiutil.escape(self.request.getPragma('author', defval=''))
366
 
 
367
 
    def item_counter(self):
368
 
        return "%d|%d" % (self.slideNumber, self.last_slide())
369
 
 
370
 
# This is quite stupid template, but it cleans most of the code from
371
 
# html. With smarter templates, there will be no html in the action code.
372
 
template = """
373
 
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
374
 
    "http://www.w3.org/TR/html4/strict.dtd">
375
 
 
376
 
<html%(language_attribtues)s>
377
 
<head>
378
 
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
379
 
    <meta name="robots" content="noindex,nofollow">
380
 
    <title>%(title)s</title>
381
 
 
382
 
    <script type="text/javascript">
383
 
        function getKey(e) {
384
 
            // Support multiple browsers stupidity
385
 
            var key;
386
 
            if (e == null) {
387
 
                // IE
388
 
                key = event.keyCode;
389
 
            } else {
390
 
                // Standards compliant
391
 
                if (e.altKey || e.ctrlKey) {
392
 
                    return null;
393
 
                }
394
 
                key = e.which;
395
 
            }
396
 
            return key;
397
 
        }
398
 
 
399
 
        function go(slide) {
400
 
            window.location="%(slide_link_base)s" + slide;
401
 
        }
402
 
 
403
 
        function onkeydown(e) {
404
 
            switch(getKey(e)) {
405
 
                // presenter maybe rather wants to use up/down for scrolling content!
406
 
                // case 38: go('%(first_slide)s'); break; // up arrow
407
 
                // case 40: go('%(last_slide)s'); break; // down arrow
408
 
                case %(forward_key)s: go('%(next_slide)s'); break;
409
 
                case %(back_key)s: go('%(previous_slide)s'); break;
410
 
                default: return true; // pass event to browser
411
 
            }
412
 
            // Return false to consume the event
413
 
            return false;
414
 
        }
415
 
 
416
 
        document.onkeydown = onkeydown
417
 
    </script>
418
 
 
419
 
    <link rel="stylesheet" type="text/css" charset="utf-8" media="all"
420
 
        href="%(theme_url)s/css/%(action_name)s.css">
421
 
</head>
422
 
 
423
 
<body>
424
 
    <h1>%(slide_title)s</h1>
425
 
 
426
 
    <div id="content">
427
 
        %(slide_body)s
428
 
    </div>
429
 
 
430
 
    <div id="navigation"%(navigation_language_attributes)s>
431
 
        <ul>
432
 
            <li>%(navigation_edit)s</li>
433
 
            <li>%(navigation_quit)s</li>
434
 
            <li>%(navigation_start)s</li>
435
 
            <li>%(navigation_back)s</li>
436
 
            %(navigation_slides)s
437
 
            <li>%(navigation_forward)s</li>
438
 
            <li>%(navigation_end)s</li>
439
 
        </ul>
440
 
    </div>
441
 
    <div id="footer">
442
 
    <ul id="date">%(date)s</ul>
443
 
    <ul id="author">%(author)s</ul>
444
 
    <ul id="counter">%(counter)s</ul>
445
 
    </div>
446
 
<!--
447
 
    <p><a href="http://validator.w3.org/check?uri=referer">
448
 
        Valid HTML 4.01</a>
449
 
    </p>
450
 
 -->
451
 
</body>
452
 
</html>
453
 
"""
454
 
 
455
 
 
456
 
def execute(pagename, request):
457
 
    """ Glue to current plugin system """
458
 
    SlideshowAction(request, pagename, template).execute()
459