7483d4ef826c020c5b589c099d73f9e30059be62
[hacks/simpleWebSlides.git] / simpleWebSlides.js
1 /*
2   Namespace object.
3 */
4
5 var SWS = SWS || {};
6
7
8
9 SWS.Utils = new function () {
10     var self = this;
11
12     self.isUndefined = function (o) { return typeof o == "undefined"; };
13     self.push2 = function (t, i, v) {
14         if ((typeof t[i]) == 'undefined') {
15             t[i] = new Array();
16         };
17         var l = t[i].length;
18         t[i][l] = v;
19     };
20
21     self.isEmpty = function (o) {
22         for(var _ in o) return false;
23         return true;
24     };
25
26     self.parseFrameSpec = function (s) {
27         var elems = s.split("_");
28         var result = {};
29         var min_last = 10000;
30         var max_value = -1;
31         for(var i = 0; i < elems.length; i++){
32             var bounds = elems[i].split("-");
33             if (bounds.length > 2 || bounds.length == 0) return {};
34             if (bounds.length == 1) bounds[1] = bounds[0];
35             var a = parseInt(bounds[0]);
36             var b = parseInt(bounds[1])
37             if (!isFinite(a) || !isFinite(b)) return {};
38             a = Math.min(a, 1000); // don't allow more than 1000 frames/slide
39             b = Math.min(b, 1000);
40             if (b > max_value) max_value = b;
41             for (var j = a; j <= b; j++)
42                 result[j] = true;
43         };
44         return result;
45     };
46
47     self.getParameterByName = function (name) {
48         name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
49         var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
50         results = regex.exec(location.search);
51         return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
52     }
53 };
54
55
56
57
58 SWS.Templates = new function () {
59     var self = this;
60     self.helpPanel = "<div id='sws-help-panel-canvas'>\
61 <h1>Keyboard shortcuts</h1>\
62 <table>\
63 <tr ><td style='color:#f55;'>h</td><td style='color:#f55;'> toggle help</td></tr>\
64 <tr><td>c</td><td> toggle the control panel</td></tr>\
65 <tr><td>Left, PgUp,swipe left</td><td> previous step</td></tr>\
66 <tr><td>Right, PgDown, Space, swipe right</td><td> next step</td></tr>\
67 <tr><td>p</td><td> previous slide</td></tr>\
68 <tr><td>n</td><td> next slide</td></tr>\
69 <tr><td>Home</td><td> first slide</td></tr>\
70 <tr><td>End</td><td> last slide</td></tr>\
71 </table>\
72 </div>";
73     self.controlPanel = "<div id='sws-control-panel-canvas'><div id='sws-control-panel'>\
74 <div id='sws-control-panel-title-bar'>\
75 <a title='Toggle fullscreen' id='sws-control-panel-fullscreen' class='sws-symbol' onclick='SWS.Presentation.toggleFullScreen();'></a>\
76 <a title='Close panel' id='sws-control-panel-close' onclick='$(\"#sws-control-panel-canvas\").toggle();'>&#x2716;</a>\
77 </div>\
78 <div id='sws-control-panel-options'>\
79 <span title='Change the aspect ratio' class='sws-symbol' >&#x1f4bb;</span><select id='sws-aspect-select' onchange='SWS.Presentation.changeAspect();'>\
80 <option value='sws-aspect-4-3' selected='selected'>4:3</option>\
81 <option value='sws-aspect-16-9'>16:9</option>\
82 <option value='sws-aspect-16-10'>16:10</option>\
83 </select>\
84 <span title='Change the theme' class='sws-symbol'>&#x1f3a8;</span><select id='sws-theme-select' onchange='SWS.Presentation.changeTheme();'></select>\
85 <a onclick='SWS.Presentation.openPrint()' ><span title='Open Print-Out' class='sws-symbol'>&#59158;</span></a>\
86 </div>\
87 <div id='sws-control-panel-navigation'>\
88 <a title='First slide' class='sws-symbol' onclick='SWS.Presentation.goToSlide(SWS.Presentation.firstSlide());' >&#x23ee;</a>\
89 <a title='Previous slide' class='sws-symbol' onclick='SWS.Presentation.previousSlide();SWS.Presentation.refresh();'>&#x23ea;</a>\
90 <a title='Previous step' class='sws-symbol' style='-webkit-transform: scaleX(-1);' onclick='SWS.Presentation.previous();SWS.Presentation.refresh();'>&#x25b6;</a>\
91 <a title='Next step' class='sws-symbol' onclick='SWS.Presentation.next();SWS.Presentation.refresh();'>&#x25b6;</a>\
92 <a title='Next slide' class='sws-symbol' onclick='SWS.Presentation.nextSlide();SWS.Presentation.refresh();'>&#x23e9;</a>\
93 <a title='Last slide' class='sws-symbol' onclick='SWS.Presentation.goToSlide(SWS.Presentation.lastSlide());'>&#x23ed;</a>\
94 <br/>\
95 <span class='sws-symbol'>&#xe4ae;</span><input type='text' id='sws-control-panel-slide-input' oninput='SWS.Presentation.goToSlide($(\"#sws-control-panel-slide-input\").val()-1);'></input><span id='sws-control-panel-total-slides'></span>\
96 <input type='range' title='Navigate the presentation' id='sws-control-panel-navigation-bar' onchange='SWS.Presentation.navigate();' step='1'></input>\
97 </div>\
98 </div>\
99 </div>";
100     self.slideActivate = function (o) {
101         if (!(o.hasClass("sws-active-slide"))){
102             o.removeClass("sws-inactive-slide").addClass("sws-active-slide");
103         };
104     };
105
106     self.slideDeactivate = function (o) {
107         if (!(o.hasClass("sws-inactive-slide"))){
108             o.removeClass("sws-active-slide").addClass("sws-inactive-slide");
109         };
110     };
111
112     self.slideChange = function (from, to) {
113         var canvas = $(".sws-canvas");
114         self.slideDeactivate($(canvas[from]));
115         self.slideActivate($(canvas[to]));
116     };
117
118     self.objectActivate = function (o) {
119         if (!(o.hasClass("sws-active-object"))){
120             o.removeClass("sws-inactive-object").addClass("sws-active-object");
121             return true;
122         };
123         return false;
124     };
125
126     self.objectDeactivate = function (o) {
127         if (!(o.hasClass("sws-inactive-object"))){
128             o.addClass("sws-inactive-object").removeClass("sws-active-object");
129             return true;
130         };
131         return false;
132     };
133
134     self.updateFooter = function (o) {
135         var footer = o.find(".sws-footer");
136         if (footer.length && (footer.children("*").length == 0)) {
137             var i = SWS.Presentation.getCurrentSlide();
138             var cur = $( "<span class='sws-current-slide-number'>"
139                          + (i + 1)
140                          +"</span>");
141             var sep = $( "<span class='sws-slide-num-sep' />");
142             var tot = $( "<span class='sws-last-slide-number'>"
143                          + (SWS.Presentation.getNumSlides())
144                          +"</span>");
145             footer.append(cur).append(sep).append(tot);
146         };
147     };
148     self.updateHeader = function (o) {};
149 };
150 SWS.ConfigBuilder = function () {
151     var self = this;
152     self['sws-object-activate'] = SWS.Templates.objectActivate;
153     self['sws-object-deactivate'] = SWS.Templates.objectDeactivate;
154     self['sws-slide-change'] = SWS.Templates.slideChange;
155     self['sws-update-footer'] = SWS.Templates.updateFooter;
156     self['sws-update-header'] = SWS.Templates.updateHeader;
157 };
158
159 SWS.Defaults = new SWS.ConfigBuilder ();
160
161 SWS.Config = new SWS.ConfigBuilder ();
162
163
164 SWS.Effects = new function () {
165     var self = this;
166     
167     self.objectDeactivateFadeOut = function (o) {
168         if (o.is("embed")) return;
169         o.animate({'opacity': '0'}, 200,
170                   function () {
171
172                       SWS.Templates.objectDeactivate(o);
173                   });
174     };
175
176     self.objectActivateFadeIn = function (o) {
177
178         if (SWS.Templates.objectActivate(o)){
179             o.animate({'opacity': '1' }, 200);
180         };
181
182     };
183
184     self.slideChangeHorizontalFlip = function (from, to){
185         var f = SWS.Presentation.getSlide(from);
186         var t = SWS.Presentation.getSlide(to);
187         f.animate({ 'left': '50%', 'width': '0pt', 'opacity':'0' }, 150,
188                   function  () {
189                       SWS.Templates.slideDeactivate(f);
190                       f.css({'left':'0%', 'width': '100%'});
191                       t.css({ 'left': '50%', 'width': '0pt','opacity':'0' });
192                       SWS.Templates.slideActivate(t);
193                       t.animate({'left':'0%', 'width': '100%','opacity':'1'});
194                   });
195     };
196     self.slideChangeFadeOutIn = function (from, to) {
197         var f = SWS.Presentation.getSlide(from);
198         var t = SWS.Presentation.getSlide(to);
199         f.animate({ 'opacity': '0'}, 150,
200                   function () { SWS.Templates.slideDeactivate(f);
201                                 SWS.Templates.slideActivate(t);
202                                 t.css('opacity', '0');
203                                 t.animate({ 'opacity': '1'}, 150);
204                               });
205     };
206     self.slideChangeHorizontalSlide = function (from, to) {
207         var f = SWS.Presentation.getSlide(from);
208         var t = SWS.Presentation.getSlide(to);
209         if (from < to) {
210             t.css('left', '100%');
211             t.css('opacity', '1');
212             SWS.Templates.slideActivate(t);
213             f.animate({ 'left': '-100%' }, 250, function () { SWS.Templates.slideDeactivate(f);
214                                                               f.css('opacity', '0');
215                                                               t.animate({ 'left': '0%' }, 250);
216                                                             });
217         } else {
218             t.css('left', '-100%');
219             SWS.Templates.slideActivate(t);
220             f.animate({ 'left': '100%' }, 250, function () { SWS.Templates.slideDeactivate(f);
221                                                              f.css('opacity', '0');
222                                                            });
223             t.css('opacity', '1');
224             t.animate({ 'left': '0%' }, 250);
225         };
226     };
227
228
229     self.slideChangeVerticalSlide = function (from, to) {
230         var f = SWS.Presentation.getSlide(from);
231         var t = SWS.Presentation.getSlide(to);
232         if (from < to) {
233             t.css('top', '100%');
234             SWS.Templates.slideActivate(t);
235             f.animate({ 'top': '-100%' }, 250, function () { SWS.Templates.slideDeactivate(f); });
236             t.animate({ 'top': '0%' }, 250);
237         } else {
238             t.css('top', '-100%');
239             SWS.Templates.slideActivate(t);
240             f.animate({ 'top': '100%' }, 250, function () { SWS.Templates.slideDeactivate(f); });
241             t.animate({ 'top': '0%' }, 250);
242         };
243     };
244
245 };
246
247 SWS.Fullscreen = new function () {
248     var self = this;
249
250     if (SWS.Utils.isUndefined(document.fullScreen)) {
251         if (SWS.Utils.isUndefined(document.mozfullScreen)) {
252             self.status = function () { return document.webkitIsFullScreen; };
253             self.enter = function(e) {
254                 e.webkitRequestFullScreen();
255             };
256             self.exit = function () {
257                 document.webkitCancelFullScreen();
258             };
259
260         } else {
261             self.status = function () { return document.mozfullScreen; };
262             self.enter = function(e) {
263                 e.mozRequestFullScreen();
264             };
265             self.exit = function () {
266                 document.mozCancelFullScreen();
267             };
268
269         };
270     } else {
271             self.status = function () { return document.fullScreen; };
272             self.enter = function(e) {
273                 e.requestFullScreen();
274             };
275             self.exit = function () {
276                 document.cancelFullScreen();
277             };
278
279     };
280
281
282 };
283
284 SWS.Presentation = new function () {
285
286
287     var self = this;
288
289     //TODO move outside of the Presentation object
290
291
292     var _total_slides;
293     var _initialized = false;
294     var _disable_input_events = false;
295     var _print_mode = false;
296     var _slide_callbacks = new Array ();
297     var _total_steps = -1;
298     var _current_theme = "";
299
300     self.getNumSlides = function () { return _total_slides; };
301
302     self.getSlide = function(i) {
303         return $($(".sws-canvas")[i]);
304     };
305
306     self.registerCallback = function (i, f) {
307         if (_initialized) return;
308         //jQuery does not seem to work well
309         //on a partial DOM
310         var slides = $(".sws-slide");
311         var h1s = $("body").children("h1");
312         var slide_num = slides.add(h1s).length - 1;
313         SWS.Utils.push2(_slide_callbacks, slide_num,{ 'fn': f, 'frame': i });
314
315     };
316
317     if (typeof(Storage)!=="undefined"){
318         self.getCurrentSlide = function () {
319             //unary + casts to integer
320             var i = +(sessionStorage.getItem("current_slide"));
321             if (!(i >= 0 && i < self.getNumSlides())){
322                 return 0;
323             } else {
324                 return i;
325             };
326         };
327
328         self.setCurrentSlide = function (i) {
329             sessionStorage.setItem("current_slide", i);
330         };
331
332         self.showHelpAtStartup = function () {
333             var r = sessionStorage.getItem("show_help");
334             if (r == "hide") return false;
335             sessionStorage.setItem("show_help", "hide");
336             return true;
337         };
338
339     } else {
340         var _current_slide = 0;
341         self.getCurrentSlide = function () { return _current_slide; };
342         self.setCurrentSlide = function (i) { _current_slide = i; };
343         self.showHelpAtStartup = function () { return false; };
344     };
345     self.firstSlide = function () { return 0; };
346     self.lastSlide = function () { return self.getNumSlides() - 1; };
347     self.refresh = function () {
348         /* block upcoming input event until all animations are finished */
349         _disable_input_events = true;
350
351         var canvas = $(".sws-canvas");
352         var from_slide_num = canvas.index($(".sws-active-slide"));
353         var to_slide_num = self.getCurrentSlide();
354         var watch_slide_anim = false;
355         var to_slide = $(canvas[to_slide_num]);
356         var from_slide = $(canvas[from_slide_num]);
357         var slide_change = (from_slide_num != to_slide_num);
358
359
360         var info = to_slide.data("sws-frame-info");
361         SWS.Config['sws-update-header'](to_slide);
362         SWS.Config['sws-update-footer'](to_slide);
363
364         if (slide_change) {
365             //Launch a slide transition:
366             SWS.Config['sws-slide-change'](from_slide_num, to_slide_num);
367             watch_slide_anim = true;
368             for (var i = 0; i < info.callbacks.at_slide.length;i++){
369                 info.callbacks.at_slide[i](to_slide);
370             };
371         };
372
373
374         var cur = info.current;
375         var custom = info.custom;
376         var real_slide = to_slide.find(".sws-slide");
377         var dont_touch = real_slide.find("sws-protect").find("*").addBack();
378         real_slide.find("*").addBack().not(dont_touch).each(function (i){
379             var frameset = $(this).data("sws-frame-set") || {};
380             if (frameset[cur])
381                 SWS.Config['sws-object-activate']($(this));
382             else
383                 SWS.Config['sws-object-deactivate']($(this));
384         });
385
386
387         var all = $(from_slide).add(to_slide);
388         all.find("*").addBack().promise().done(function() {
389             var callbacks;
390             //execute callbacks when all elements are finished transitioning
391             if (callbacks = info.callbacks.at_frame[self.getCurrentFrame()]){
392                 for (var k = 0; k < callbacks.length; k++)
393                     callbacks[k]($(to_slide));
394             };
395             all.find("*").addBack().promise().done(function() {
396                 //wait for all elements to finish transitionning, in case a callback animate something
397                 //and enable _input_events again.
398                 _disable_input_events = false;
399             });
400         });
401     };
402
403     self.nextSlide = function () {
404         self.setCurrentSlide(Math.min(self.getCurrentSlide()+1,
405                                       self.lastSlide()));
406         self.setCurrentFrame(self.firstFrame());
407     };
408
409     self.previousSlide = function () {
410         self.setCurrentSlide(Math.max(self.getCurrentSlide()-1,
411                                       self.firstSlide()));
412         self.setCurrentFrame(self.firstFrame());
413     };
414
415     self.getFrameInfo = function () {
416
417         var i = self.getCurrentSlide();
418         var canvas = $($(".sws-canvas")[i]);
419         var infos = canvas.data("sws-frame-info");
420         return infos;
421     };
422
423     self.getTotalSteps = function () {
424         if (_total_steps >= 0) return _total_steps;
425         _total_steps = 0;
426         $(".sws-canvas").each(function(i) {
427             var canvas = $($(".sws-canvas")[i]);
428             var infos = canvas.data("sws-frame-info");
429             _total_steps += infos.last + 1;
430         });
431         return _total_steps;
432     };
433
434     self.getCurrentFrame = function () { return self.getFrameInfo().current; };
435     self.setCurrentFrame = function (i) { self.getFrameInfo().current = i; };
436     self.firstFrame = function () { return 0; };
437     self.lastFrame = function () { return self.getFrameInfo().last; };
438
439     self.nextFrame = function () {
440         self.setCurrentFrame(Math.min(self.getCurrentFrame()+1,
441                                       self.lastFrame()));
442
443     };
444     self.previousFrame = function () {
445         self.setCurrentFrame(Math.max(self.getCurrentFrame()-1,
446                                        self.firstFrame()));
447     };
448
449     self.next = function () {
450         var i = self.getCurrentFrame();
451         if (i == self.lastFrame()) {
452             self.nextSlide();
453             self.setCurrentFrame(self.firstFrame());
454         } else
455             self.nextFrame();
456     };
457
458     self.previous = function () {
459         var i = self.getCurrentFrame();
460         if (i == self.firstFrame()){
461             self.previousSlide();
462             self.setCurrentFrame(self.lastFrame());
463         }
464         else
465             self.previousFrame();
466     };
467
468     self.goToSlide = function (s, f) {
469         if (SWS.Utils.isUndefined(f))
470             f = 0;
471         if (!(s >= self.firstSlide() && s <= self.lastSlide())) return;
472         self.setCurrentSlide(s);
473         if (!(f >= self.firstFrame() && f <= self.lastFrame())) f = 0;
474         self.setCurrentFrame(f);
475         self.refresh();
476     };
477
478     self.cycleStyle = function() {
479         var styles = $("head").children('link[rel$="stylesheet"][title]');
480         var j = styles.index(styles.filter(':not(:disabled)'));
481         styles[j].disabled = true;
482         if (++j == styles.length) j = 0;
483         styles[j].disabled = false;
484     };
485
486
487     self.printMode = function () {
488         _print_mode = true;
489
490         var old_fx_status = $.fx.off;
491         //disable animation while printing.
492
493         $.fx.off = true;
494         var progress = $("<div style='position:fixed;top:0pt;left:0pt;background:white;color:black;width:100%;height:100vh;z-index:200;' id='sws-print-progress'>Rendering presentation: <span id='sws-percent-progress'></span>%</div>");
495         $("body").append(progress);
496
497         $("html").removeClass("sws-display").addClass("sws-print");
498         self.goToSlide(0,0);
499         var steps = self.getTotalSteps();
500         var total_steps = steps;
501         var loop;
502         loop = function () {
503             if (steps >= 0) {
504                 //Crazy workaround for chromium
505                 ($("link.sws-theme[rel='stylesheet']")[0]).disabled = false;
506                 $(".sws-canvas").find("*").addBack().promise().done(function() {
507                     var percent = ((total_steps - steps) / total_steps) * 100;
508                     $("#sws-percent-progress").text(Math.round(percent));
509                     SWS.Config['sws-slide-change'] = SWS.Templates.slideChange;
510                     self.refresh();
511                     $($(".sws-canvas")[self.getCurrentSlide()]).css('opacity', 1 );
512                     self.next();
513                     steps--;
514                     loop();
515                     })
516             } else {
517                 $("#sws-percent-progress").text(100);
518                 progress.remove();
519                 window.status = 'Ready';
520                 $.fx.off = old_fx_status;
521                 if (SWS.Utils.getParameterByName("dialog") != "off")
522                     $("body").ready( function () { window.print(); });
523             }
524         };
525         loop();
526
527     }
528
529     self.buildFullTOC = function () {
530
531         var build_sections = function (doc) {
532             var res = [];
533             var h1s = doc.find("body").first().children("h1");
534             var slides = doc.find("body").first().children(".sws-slide");
535             var slide_num = 1;
536             var collection = h1s.add(slides);
537             collection.each (function () {
538                 if ($(this).is("h1")) {
539                     res.push({ 'title' : $(this).text(),
540                                'slide' : slide_num });
541                 } else {
542                     slide_num++;
543                 }
544             });
545             return res;
546         };
547
548         var toc = [];
549
550         var append = function (a,e) {
551             return a.push(e);
552         };
553         var prepend = function (a,e) {
554             return a.unshift(e);
555         };
556
557         var loop = function (doc, dir, add, ignoreFirst) {
558             if (ignoreFirst !== true) {
559                 var this_toc = { 'title' : doc.find("title").first().text()
560                                  .replace ("&", "&amp;")
561                                  .replace("'","&apos;")
562                                  .replace('"', "&quot;")
563                                  .replace("<", "&lt;")
564                                  .replace(">", "&gt;"),
565                                  'sections' : build_sections(doc) };
566                 add(toc, this_toc);
567             };
568             var url = doc.find(dir).first().attr("href");
569             if (!SWS.Utils.isUndefined(url) && url != "") {
570                 $.ajax({ 'url' : url, 'async' : false ,'success' : function (page) {
571                     loop ($(page), dir, add, false);
572                 }});
573             };
574         };
575         loop ($(document), ".sws-previous", prepend, false);
576         return toc;
577
578     };
579
580
581
582     var _startTouch = null;
583
584     self.inputHandler = function (event) {
585         if (_disable_input_events || _print_mode) return;
586         var code = 0;
587         switch (event.type) {
588         case 'touchstart':
589             _startTouch = event.changedTouches[0];
590             return;
591         case 'touchend':
592             if (!_startTouch) return;
593             var _endTouch = event.changedTouches[0];
594             var Xdist = _endTouch.clientX - _startTouch.clientX;
595             var Ydist = _endTouch.clientY - _startTouch.clientY;
596             if (Xdist > 40) code = 39
597             else if (Xdist < -40) code = 37
598             else if (Ydist > 20 && !$("#sws-control-panel-canvas").is(":visible")) code = 67;
599             else if (Ydist < -20  && $("#sws-control-panel-canvas").is(":visible")) code = 67;
600             else code = 39;
601             break;
602         case 'keydown':
603             code = event.which;
604             break;
605         default:
606             return;
607         };
608
609         switch (code) {
610         case 36:/* Home */
611             self.setCurrentSlide(self.firstSlide());
612             break;
613
614         case 35:/* End */
615             self.setCurrentSlide(self.lastSlide());
616             break;
617         case 32: /* space */
618         case 34: /* PgDown */
619         case 39: /* -> */
620         case 176: /* Multimedia skip forward */
621         case 179: /* Multimedia play/pause */
622             if (self.getCurrentSlide() == self.lastSlide()
623                 && self.getCurrentFrame() == self.lastFrame()) return;
624             self.next();
625             break;
626         case 78: /* n */
627             self.nextSlide();
628             break;
629         case 8: /* backspace */
630         case 33: /* PgUp */
631         case 37: /* <-   */
632         case 177: /* Multimedia skip backward */
633             self.previous();
634             break;
635         case 80: /* p */
636             self.previousSlide();
637             break;
638         case 83: /* s */
639                 self.cycleStyle();
640             return;
641         case 67: /* c */
642             $("#sws-control-panel-canvas").toggle();
643             return;
644         case 72: /* h */
645             $("#sws-help-panel-canvas").toggle();
646
647         default:
648             return;
649         };
650         self.refresh();
651 };
652
653
654
655     function init_canvas(canvas, custom) {
656         var cur_frame = 0;
657         var last_frame = canvas.find(".sws-pause").length;
658         //Add all regular elements to the frame list
659         var slide = $(canvas.find(".sws-slide")[0]);
660
661         var callbacks = { at_slide : new Array(),
662                           at_frame : new Array() }
663
664         if (SWS.Utils.isUndefined(custom)) {
665             custom = new Array ();
666         };
667
668         for (var i = 0; i < custom.length; i++) {
669             if (isFinite(custom[i].frame)){
670                 var num = +(custom[i].frame);
671                 if (num > last_frame) last_frame = num;
672                 SWS.Utils.push2(callbacks.at_frame, num, custom[i].fn);
673             } else if (custom[i].frame == "slide")
674                 callbacks.at_slide.push(custom[i].fn);
675             else {
676                 var frame_set = SWS.Utils.parseFrameSpec(custom[i].frame);
677                 for(var f in frame_set){
678                     if (f > last_frame) last_frame = f;
679                     SWS.Utils.push2(callbacks.at_frame, +(f), custom[i].fn);
680                 };
681             }
682         };
683
684         var specials = $([]);
685
686         slide.find('*[class*="sws-onframe-"]').each(function(_){
687             var cls = $(this).attr('class');
688             var idx = cls.indexOf("sws-onframe-");
689             if (idx >= 0) {
690                 var end = cls.indexOf(" ", idx);
691                 end = (end == -1) ? cls.length : end;
692                 var spec = cls.substring(idx+12, end);
693                 var o = SWS.Utils.parseFrameSpec(spec);
694                 for(var f in o)
695                     if (f > last_frame) last_frame = f;
696                 $(this).find("*").andSelf().each(function(_){
697                     if (!SWS.Utils.isEmpty(o)){
698                         $(this).data("sws-frame-set", o);
699                     }
700                     specials = specials.add($(this));
701                 });
702             };
703         });
704
705         slide.find("*").andSelf().not(specials).each(function(i) {
706             if ($(this).hasClass("sws-pause"))  cur_frame++;
707             var o = {};
708             for (var j = cur_frame; j <= last_frame; j++)
709                 o[ j ] = true;
710             if (!SWS.Utils.isEmpty(o))
711                 $(this).data("sws-frame-set", o);
712         });
713
714         canvas.data("sws-frame-info", { current: 0,
715                                         last: (last_frame - 0), // force cast to integer
716                                         callbacks: callbacks
717                                       });
718
719     };
720
721     /* Forces redrawing the page without reloading */
722     self.redraw = function (f) {
723         if (SWS.Utils.isUndefined(f))
724             $("body").hide().show(400, function () {
725                 $("body").css("display","block");
726                 if (!SWS.Utils.isUndefined(f))
727                     f();
728             });
729     };
730     self.changeAspect = function() {
731         if (_print_mode) return;
732         var newClass = $("#sws-aspect-select").val();
733         var args = newClass.split("-");
734         var targetRatio = (args[2] - 0) / (args[3] - 0);
735         var realRatio = window.innerWidth / window.innerHeight;
736         var byClass = (targetRatio > realRatio ) ? "sws-by-height" : "sws-by-width";
737         if ($("html").hasClass(newClass)
738             && $("html").hasClass(byClass))
739             return;
740
741         $("html").removeClass("sws-aspect-4-3")
742             .removeClass("sws-aspect-16-9")
743             .removeClass("sws-aspect-16-10")
744             .removeClass("sws-by-width")
745             .removeClass("sws-by-height")
746             .addClass(newClass).addClass(byClass);
747         self.redraw();
748     };
749
750     self.getCurrentTheme = function () {
751         var l = $("link.sws-theme[rel='stylesheet']")[0];
752
753         if (l) {
754             return  l.title;
755         } else
756             return ""
757     };
758
759     self.changeTheme = function (name) {
760         var theme_name;
761         if (typeof name === 'undefined')
762             theme_name = $("#sws-theme-select").val()
763         else
764             theme_name = name;
765
766         _current_theme = theme_name;
767         $("link.sws-theme").each (function (i) {
768             var e = this;
769             var title =  e.title;
770             if (title == theme_name) {
771                 e.rel = "stylesheet";
772                 e.disabled = false;
773                 e.media="all";
774             } else {
775                 e.rel = "alternate stylesheet";
776                 e.disabled = true;
777                 e.media="all";
778             };
779         });
780         self.redraw();
781
782     };
783
784     self.openPrint = function () {
785         window.open ("?mode=print&theme=" + self.getCurrentTheme());
786     }
787     var _fullscreen_icon_on = "&#xe746;";
788     var _fullscreen_icon_off = "&#xe744;";
789
790     self.toggleFullScreen = function () {
791         if (SWS.Fullscreen.status()) {
792             SWS.Fullscreen.exit();
793             $("a#sws-control-panel-fullscreen")
794                 .html(_fullscreen_icon_off);
795
796
797
798         } else {
799             SWS.Fullscreen.enter($("body")[0]);
800             $("a#sws-control-panel-fullscreen")
801                 .html(_fullscreen_icon_on);
802         };
803     };
804     function _update_ui() {
805         var nav = $('#sws-control-panel-navigation-bar');
806         nav.val(SWS.Presentation.getCurrentSlide() + 1);
807         $('#sws-control-panel-slide-input').val(nav.val());
808     }
809     self.navigate = function () {
810         self.goToSlide($("#sws-control-panel-navigation-bar").val()-1);
811         _update_ui();
812     };
813
814
815     self.init = function () {
816
817
818         $("html").addClass("sws-display");
819         //$(window).resize(self.redraw);
820
821         var slides = $(".sws-slide");
822         var h1s = $("body").children("h1");
823         var slide_num = slides.add(h1s).length - 1;
824
825         _total_slides = $(".sws-slide").add($("body").children("h1")).length;
826
827         var cur = self.getCurrentSlide();
828         var toc = self.buildFullTOC();
829         var common_html = "<div class='sws-slide sws-toc'><h1>Plan</h1><ul style='list-style-type:none'>";
830         var i;
831         for (i= 0; i < toc.length - 1; i++)
832             common_html += "<li class='done'>" + (i+1) +
833             ' ' + toc[i].title + "</li>";
834
835         common_html += "<li>" + toc.length + ' ' + toc[toc.length - 1].title;
836         common_html += "<ul style='list-style-type:none' >";
837         var sections = toc[toc.length - 1].sections;
838         $("body").children("h1").each(function (i) {
839             var this_html = common_html;
840             var j;
841             var secnum = toc.length + '.';
842             for (j = 0; j < i; ++j)
843                 this_html += "<li class='done'>" + secnum + (j+1) + ' ' +
844                 sections[j].title + "</li>";
845             this_html += "<li class='hl'>" + secnum + (i+1) + ' ' +
846                 sections[i].title + "</li>";
847             for (j = i+1; j < sections.length; j++)
848                 this_html += "<li>" + secnum + (j+1) + ' '
849                 +sections[j].title + "</li>";
850             this_html += "</ul></li></ul></div>";
851             $(this).after(this_html);
852         });
853
854         $(".sws-slide").each(function (i) {
855
856             var par = $(this).parent();
857
858
859             $(this).remove();
860             var canvas = $('<div class="sws-canvas"/>');
861
862             if (!($(this).hasClass("sws-option-noheader"))) {
863                 canvas.append($('<div class="sws-header"/>'));
864             };
865
866             if (!$(this).hasClass("sws-cover")) {
867                 var title = $($(this).find("h1")[0]);
868                 var title_div = $('<div class="sws-title" />');
869                 title_div.append(title);
870                 canvas.append(title_div);
871             };
872
873             var inner = $('<div class="sws-inner-canvas"/>');
874             var content = $('<div class="sws-content"/>');
875             $(this).find('script[type="text/javascript"]').remove();
876             content.append($(this));
877             inner.append(content);
878             canvas.append(inner);
879             var that = this;
880             [ "sws-cover", "sws-toc" ].forEach(
881                 function(v) {
882                     if ($(that).hasClass(v)) {
883                         inner.addClass(v);
884                         $(that).removeClass(v);
885                     }
886                 });
887
888             if (!($(this).hasClass("sws-option-nofooter"))) {
889                 canvas.append($('<div class="sws-footer"/>'));
890             };
891
892             par.append(canvas);
893
894             if (i == cur) {
895                 canvas
896                     .addClass("sws-active-slide")
897                     .removeClass("sws-inactive-slide");
898             } else {
899                 canvas
900                     .addClass("sws-inactive-slide")
901                     .removeClass("sws-active-slide");
902             };
903             init_canvas(canvas,_slide_callbacks[i]);
904
905         });
906
907         // Initialize the control panel
908         $("body").append($(SWS.Templates.controlPanel));
909         $("body").append($(SWS.Templates.helpPanel));
910         // Fill the theme switcher
911         $("link.sws-theme").each (function (i) {
912             var e = $(this);
913             var opt = "<option value='";
914             opt += e.attr("title");
915             opt += "' ";
916             if (e.attr("rel") == "stylesheet") {
917                 opt+= "selected='selected'";
918             };
919             opt += ">" + e.attr("title") + "</option>";
920             $("#sws-theme-select").append($(opt));
921         });
922         // Set the fullscreen icon
923         if (SWS.Fullscreen.status()) {
924             $("a#sws-control-panel-fullscreen")
925                 .html(_fullscreen_icon_on);
926         } else {
927             $("a#sws-control-panel-fullscreen")
928                 .html(_fullscreen_icon_off);
929         };
930         // Put the navigation range at the correct position
931         var nav = $('#sws-control-panel-navigation-bar');
932         nav.attr("min", SWS.Presentation.firstSlide() + 1);
933         nav.attr("max", SWS.Presentation.lastSlide() + 1);
934         $('#sws-control-panel-total-slides').text('/' + SWS.Presentation.getNumSlides());
935         _update_ui();
936         _slide_callbacks = null; /* avoid a leak */
937         var passed_theme = SWS.Utils.getParameterByName("theme");
938
939
940         //workaround weird chrome CSS loading bug
941         var f =
942             function () {
943                 if (passed_theme == "")
944                     self.changeTheme();
945                 else
946                     self.changeTheme(passed_theme);
947                 if (SWS.Utils.getParameterByName("mode") == "print") {
948                     self.printMode();
949                 }
950                 else {
951                     if (self.showHelpAtStartup()) $("#sws-help-panel-canvas").show().delay(5000).hide();
952                     self.changeAspect();
953                     self.refresh();
954                 };
955                 $(document).keydown(self.inputHandler);
956                 document.body.addEventListener('touchstart',self.inputHandler, false);
957                 document.body.addEventListener('touchend',self.inputHandler, false);
958                 $(window).resize(self.changeAspect);
959                 _initialized = true;
960             };
961         //setTimeout(f, 100);
962         f();
963     };
964
965 };