<?xml version="1.0" encoding="UTF-8"?>
<!--
        高橋メソッドなプレゼンツール in XUL リターンズ
        made by Piro
        http://piro.sakura.ne.jp/

        based on
        高橋メソッドなプレゼン作成ツール ver.2 made by mala
        http://la.ma.la/blog/diary_200504080545.htm
        もんたメソッドなプレゼン作成ツール made by mala
        http://la.ma.la/blog/diary_200505310749.htm
-->

<!-- ***** BEGIN LICENSE BLOCK *****
   - Version: MPL 1.1
   -
   - The contents of this file are subject to the Mozilla Public License Version
   - 1.1 (the "License"); you may not use this file except in compliance with
   - the License. You may obtain a copy of the License at
   - http://www.mozilla.org/MPL/
   -
   - Software distributed under the License is distributed on an "AS IS" basis,
   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
   - for the specific language governing rights and limitations under the
   - License.
   -
   - The Original Code is the Takahashi-Method-based Presentation Tool
   - in XUL/Returns.
   -
   - The Initial Developer of the Original Code is SHIMODA Hiroshi.
   - Portions created by the Initial Developer are Copyright (C) 2005-2006
   - the Initial Developer. All Rights Reserved.
   -
   - Contributor(s): SHIMODA Hiroshi <piro@p.club.ne.jp>
   -                 dynamis <dynamis@mozilla-japan.org>
   -                 matobaa <matobaa@lily.freemail.ne.jp>
   -
   - ***** END LICENSE BLOCK ***** -->


<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="yapcasia2008.css" type="text/css"?>

<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        id="presentation"
        xmlns:html="http://www.w3.org/1999/xhtml"
        orient="vertical"
        onkeypress="Presentation.onKeyPress(event);">


<html:textarea id="builtinCode" style="visibility: collapse">
JavaScript::Writer
Gugod
YAPC::Asia 2008
----
Toy
----
Learning
Perl
----
You write Perl,
  Perl writes Javascript
----
Javascript
  Code Generator
----
例
----
Simple
Function Call
----
Perl
[[PRE:
js->alert("Nihao");

print js->as_string;
]]
Javascript
[[PRE:
alert("Nihao");
]]
----
Method
Invocation
----
Perl
[[PRE:
js->object("SomeObj")->show();
]]
Javascript
[[PRE:
SomeObj.show();
]]
----
Perl
[[PRE:
js->object("jQuery('#msg')")->show();
]]
Javascript
[[PRE:
jQuery('#msg').show();
]]
----
AUTOLOAD
----
Perl
[[PRE:
js->jQuery("#msg")->show();
]]
Javascript
[[PRE:
jQuery("#msg").show();
]]
----
I reallylike jQuery
----
call chain
----
Perl
[[PRE:
js->jQuery("#msg")
  ->show()
  ->animate({ right => 500, top => 500 });
]]
Javascript
[[PRE:
jQuery("#msg")
  .show()
  .animate({"right":500,"top":500});
]]

----
Callback
Functions
----
Perl
[[PRE:
js->jQuery("#msg")
  ->click(sub {
    js->alert(42);
  });
]]

Javascript
[[PRE:
jQuery("#msg").click(
  function(){
    alert(42);
  });
]]
----
Perl
[[PRE:
jQuery("#msg")
  ->click(sub {
    js->alert(42);
  });
]]

Javascript
[[PRE:
jQuery("#msg").click(
  function(){
    alert(42);
  });
]]
----
Conditions
----
Perl
[[PRE:
js("true")->do(sub {
    js->alert("I am in here")
});
]]

Javascript
[[PRE:
if(true) {
    alert("I am in here")
}
]]
----
Perl
[[PRE:
js("false")->do(sub {
    js->alert("I will not be in here")
});
]]

Javascript
[[PRE:
if(false) {
    alert("I will not be in here")
}
]]
----
Perl
[[PRE:
js("some.bare.words")->do(sub {
    js->alert(42)
});
]]

Javascript
[[PRE:
if(some.bare.words) {
    alert(42);
}
]]
----
Smalltalk
----
[[PRE:
if(...) {
  ...
}
else {
  ...
}
]]
----
Perl
[[PRE:
js("some.bare.words")->do(
  sub {
      js->alert(42)
  },
  sub {
      # otherwise
      js->alert(42);
  }
);
]]
----
Variable
----
Perl
[[PRE:
js->var(ans => 42);
]]

Javascript
[[PRE:
var ans = 42;
]]
----
Perl
[[PRE:
js->var(joke => "knock knock");
]]

Javascript
[[PRE:
var joke = "knock knock";
]]
----
Perl
[[PRE:
js->var(data => {
    species => 'Android',
    gender => 'Male'
});
]]

Javascript
[[PRE:
var data = {
    "species":"Android",
    "gender":"Male"
};
]]
----
JSON
(Of course)
----
Function
Declaration
----
Perl
[[PRE:
js->var(
  help => sub {
    js->alert("Help");
  }
);
]]

Javascript
[[PRE:
var help = function(){
  alert("Help");
};
]]
----
Arbitrary
Javscript Code
----
Perl
[[PRE:
js-&gt;append("foobar();");
]]
Javascript
[[PRE:
foobar();
]]
----
&lt;/例&gt;
----
Why ?
----
玩
----
動機
----
RJS
----
Ruby
JavaScript::Generator
----
Ruby
[[PRE:
page["foo"].visual_effect('hilight')
]]

JavaScript
[[PRE:
new Effect.Hilight("foo")
]]
----
Cool
----
Sexy
----
Prototype.js
----
☹
----
Javascript::Code
----
ALIGN::left
✔ 完備
✔ Objective
----
冗長な
----
[[PRE:
var b = 42;
]]
----
[[PRE:
my $var = JavaScript::Code::Variable
    ->new()
    ->name('b')
    ->value(42);
]]
----
☹
----
[[PRE:
js->var(b => 42);
]]
----
☺
----
A little bit
internals...
----
js-&gt;[[EM:foo()]]

foo();
----
AUTOLOAD
----
ALIGN::left
1. AUTOLOAD gets the sub name
2. also gets the arguments
3. append [[PRE:"foo()"]] to an internal buffer.
----
js-&gt;as_string()
----
Output the buffer
----
SYNOPSIS

[[PRE:
js-&gt;var(...);
js-&gt;foo(...);
js-&gt;...;

print js-&gt;as_string;
]]
----
js
----
Singlton object
----
Global variable
(笑)
----
ALIGN::left
package JavaScript::Writer;
my [[EM:$base]];
----
But...
----
ALIGN::left
js-&gt;dothis(sub {
    js-&gt;alert();
})

dothis(function() {
    alert();
});
----
ALIGN::left
[[EM:js]]-&gt;do(sub {
    js-&gt;alert();
})

[[EM:$base]];
----
ALIGN::left
js-&gt;dothis(sub {
    [[EM:js]]-&gt;alert();
})

[[PRE:
$_[0] in the sub
]]
----
@DB::args
----
self.pm
----
how to implement
"call chain"
----
JavaScript
[[PRE:
jQuery("#foo")
    .text(42)
    .show();
]]
----
Perl
[[PRE:
js-&gt;jQuery("#foo")
  -&gt;text(42)
  -&gt;show();
]]
----
ALIGN::left
js-&gt;[[EM:jQuery("#foo")]]
  -&gt;[[EM:text(42)]]
  -&gt;[[EM:show()]];

AUTOLOAD
----
ALIGN::left
js-&gt;[[EM:jQuery("#foo")]]
  -&gt;text(42)
  -&gt;show();

[[EM:jQuery("#foo")]]
    [[EM:.]]text(42)
    .show();
----
ALIGN::left
js-&gt;jQuery("#foo")
  -&gt;[[EM:text(42)]]
  -&gt;show();

jQuery("#foo")
    .[[EM:text(42)]]
    [[EM:.]]show();
----
ALIGN::left
js-&gt;jQuery("#foo")
  -&gt;text(42)
  -&gt;[[EM:show()]];

jQuery("#foo")
    .text(42)
    .[[EM:show();]]
----
Problem
----
ALIGN::left
js-&gt;jQuery("#foo")
  -&gt;text(42)
  -&gt;[[EM:show()]];

jQuery("#foo")
    .text(42)
    .[[EM:show().]]
----
.→;
----
ALIGN::left
Question:
    How do I append "[[EM:.]]" after jQuery(), text(),
    but "[[EM:;]]" after "show()" ?
----
ALIGN::left
Question:
    How do I tell if it is
    the end of a Perl statement ?
----
[[PRE:
wantarray
]]
----
ALIGN::left
wantarray
    Returns true if the context of the currently executing
    subroutine or "eval" is looking for a list value. Returns false
    if the context is looking for a scalar. [[EM:Returns]] the [[EM:undefined]]
    value if the context is looking [[EM:for]] no value ([[EM:void context]]).
----
ALIGN::left
true  : list
false : scalar
undef : void
----
Scalar Context
[[PRE:
my $out = js->alert(42);
]]
----
Void Context:
[[PRE:
js->alert(42);
]]
----
ALIGN::left
print js-&gt;alert(42);
----
alert("#foo")[[EM:.]]
----

(ouch)

----
Workaround
----
ALIGN::left
js-&gt;[[EM:show(42)]];
print js;

Outputs
jQuery("#foo").show()[[EM:;]]
----
print js-&gt;show();
----
Enforce
void context
----
void()
----
void( js-&gt;foo() )
----
print scalar(js-&gt;foo(), js-&gt;as_string);
----
Still
researching
----
TODO:
----
ALIGN::left
❏ variable scope
❏ context ([[EM:this]] variable)
❏ object property assignment
❏ helper based on specific js libraries
----
ALIGN::left
Goal:
❏ Completely writes a JSAN module
  with JavaScript::Writer
----
完
----
☺
</html:textarea>


<deck flex="1" id="deck">

<vbox flex="1"
        onmouseup="Presentation.handleEvent(event);"
        onmousedown="Presentation.handleEvent(event);"
        onmousemove="Presentation.handleEvent(event);">
        <toolbox id="canvasToolbar">
                <toolbar>
                        <toolbarbutton oncommand="Presentation.home()" label="|&lt;&lt;"
                                observes="canBack"/>
                        <toolbarbutton oncommand="Presentation.back()" label="&lt;"
                                observes="canBack"/>
                        <toolbarbutton oncommand="Presentation.forward()" label="&gt;"
                                observes="canForward"/>
                        <toolbarbutton oncommand="Presentation.end()" label="&gt;&gt;|"
                                observes="canForward"/>
                        <toolbarseparator/>
                        <hbox align="center">
                                <textbox id="current_page" size="4"
                                        oninput="if (this.value) Presentation.showPage(parseInt(this.value)-1);"/>
                                <toolbarbutton id="pages-list-button"
                                        type="menu"
                                        label="Select Page"
                                        tooltiptext="Select Page"
                                        class="dropmarker-button">
                                        <menupopup id="pages-list"
                                                onpopupshowing="if (event.target == this) Presentation.preventToShowHideToolbar = true;"
                                                onpopuphiding="if (event.target == this) Presentation.preventToShowHideToolbar = false;"
                                                oncommand="Presentation.showPage(parseInt(event.target.value));"/>
                                </toolbarbutton>
                                <description value="/"/>
                                <description id="max_page"/>
                        </hbox>
                        <toolbarseparator/>
                        <vbox flex="2">
                                <spacer flex="1"/>
                                <scrollbar id="scroller"
                                        align="center" orient="horizontal"
                                        oncommand="Presentation.showPage(parseInt(event.target.getAttribute('curpos')));"
                                        onclick="Presentation.showPage(parseInt(event.target.getAttribute('curpos')));"
                                        onmousedown="Presentation.onScrollerDragStart();"
                                        onmousemove="Presentation.onScrollerDragMove();"
                                        onmouseup="Presentation.onScrollerDragDrop();"/>
                                <spacer flex="1"/>
                        </vbox>
                        <toolbarseparator/>
                        <toolbarbutton label="Pen"
                                id="penButton"
                                type="checkbox"
                                autoCheck="false"
                                oncommand="StrokablePresentationService.toggleCheck();"/>
                        <spacer flex="1"/>
                        <toolbarbutton id="func-menu-button"
                                type="menu"
                                label="Function">
                                <menupopup
                                        onpopupshowing="Presentation.preventToShowHideToolbar = true;"
                                        onpopuphiding="Presentation.preventToShowHideToolbar = false;">
                                        <menuitem label="Set Timer"
                                                oncommand="Presentation.setTimer();" />
                                        <menuseparator/>
                                        <menuitem label="Start Auto-Cruise"
                                                id="autoButton"
                                                type="checkbox"
                                                autoCheck="false"
                                                oncommand="Presentation.toggleAutoCruiseMode();" />
                                        <menu id="auto-interval-button"
                                                label="Change Interval">
                                                <menupopup id="auto-interval-list"
                                                        onpopupshowing="(this.getElementsByAttribute('value', Presentation.autoCruiseInterval)[0] || this.lastChild).setAttribute('checked', true);"
                                                        oncommand="Presentation.changeAutoCruiseInterval(parseInt(event.target.value));">
                                                        <menuitem type="radio" radiogroup="autocruise-interval"
                                                                label="1 sec" value="1000"/>
                                                        <menuitem type="radio" radiogroup="autocruise-interval"
                                                                label="2 sec" value="2000"/>
                                                        <menuitem type="radio" radiogroup="autocruise-interval"
                                                                label="3 sec" value="3000"/>
                                                        <menuitem type="radio" radiogroup="autocruise-interval"
                                                                label="4 sec" value="4000"/>
                                                        <menuitem type="radio" radiogroup="autocruise-interval"
                                                                label="5 sec" value="5000"/>
                                                        <menuseparator/>
                                                        <menuitem type="radio" radiogroup="autocruise-interval"
                                                                label="1 min" value="60000"/>
                                                        <menuitem type="radio" radiogroup="autocruise-interval"
                                                                label="2 min" value="120000"/>
                                                        <menuitem type="radio" radiogroup="autocruise-interval"
                                                                label="3 min" value="180000"/>
                                                        <menuitem type="radio" radiogroup="autocruise-interval"
                                                                label="4 min" value="240000"/>
                                                        <menuitem type="radio" radiogroup="autocruise-interval"
                                                                label="5 min" value="300000"/>
                                                        <menuseparator/>
                                                        <menuitem type="radio" radiogroup="autocruise-interval"
                                                                label="Custom"
                                                                oncommand="
                                                                        var current = Presentation.autoCruiseInterval;
                                                                        var val = parseInt(prompt('input interval (sec)', parseInt(current/1000)));
                                                                        if (isNaN(val)) {
                                                                                event.preventBubble();
                                                                                return;
                                                                        }
                                                                        else
                                                                                val = val * 1000;
                                                                        this.value = val;
                                                                "/>
                                                </menupopup>
                                        </menu>
                                        <menuseparator/>
                                        <menuitem oncommand="Presentation.print();" label="Print"/>
                                        <menu id="auto-interval-button"
                                                label="Thumbnail Format">
                                                <menupopup
                                                        onpopupshowing="(this.getElementsByAttribute('value', Presentation.imageType)[0] || this.firstChild).setAttribute('checked', true);"
                                                        oncommand="Presentation.imageType = event.target.value;">
                                                        <menuitem type="radio" radiogroup="print-image-type"
                                                                label="PNG" value="png"/>
                                                        <menuitem type="radio" radiogroup="print-image-type"
                                                                label="JPEG (50%)" value="jpeg"/>
                                                </menupopup>
                                        </menu>
                                        <menuseparator/>
                                        <menuitem id="toggleEva" label="Eva Mode"
                                                type="checkbox"
                                                autoCheck="false"
                                                oncommand="Presentation.toggleEvaMode();"/>
                                </menupopup>
                        </toolbarbutton>
                        <toolbarseparator/>
                        <toolbarbutton label="Edit"
                                oncommand="Presentation.toggleEditMode();"/>
                        <toolbarbutton oncommand="Presentation.reload();" label="Reload"/>
                </toolbar>
        </toolbox>
        <vbox flex="1" id="canvas">
                <stack flex="1">
                        <vbox flex="1">
                                <hbox id="headerBox" flex="1">
                                        <label id="header"/>
                                        <spacer flex="1"/>
                                        <vbox>
                                                <image id="logo"/>
                                                <spacer flex="1"/>
                                        </vbox>
                                </hbox>
                                <spacer flex="19"/>
                                <hbox id="footerBox" flex="1">
                                        <spacer flex="1"/>
                                        <label id="footer"/>
                                </hbox>
                        </vbox>
                        <vbox flex="1"
                                onclick="Presentation.onPresentationClick(event);">
                                <spacer flex="1"/>
                                <hbox flex="1" id="contentBox">
                                        <spacer flex="1"/>
                                        <vbox id="content"/>
                                        <spacer flex="1"/>
                                </hbox>
                                <spacer flex="1"/>
                        </vbox>
                </stack>
        </vbox>
        <hbox id="indicatorBar"
                onclick="Presentation.onIndicatorBarClick(event);">
                <stack flex="1">
                        <vbox>
                                <progressmeter id="remainingPageIndicator"
                                        type="determined" value="0"
                                        flex="1"/>
                                <progressmeter id="remainingTimeIndicator"
                                        type="determined" value="0"
                                        flex="1"
                                        collapsed="true"/>
                        </vbox>
                        <hbox flex="1">
                                <label value="Next:"/>
                                <description id="nextPage" flex="1" crop="end"/>
                        </hbox>
                </stack>
        </hbox>
</vbox>


<vbox flex="1" id="edit">
        <toolbox>
                <toolbar>
                        <menubar flex="1">
                                <menu label="File">
                                        <menupopup>
                                                <menuitem label="Save"
                                                        key="key_save"
                                                        oncommand="Presentation.output()"/>
                                                <menuitem label="Reload"
                                                        key="key_reload"
                                                        oncommand="Presentation.reload()"/>
                                        </menupopup>
                                </menu>
                                <menu label="Insert">
                                        <menupopup>
                                                <menuitem label="New Page"
                                                        key="key_insert_newpage"
                                                        oncommand="Presentation.insert('page')"/>
                                                <menuseparator/>
                                                <menuitem label="Header"
                                                        oncommand="Presentation.insert('header')"/>
                                                <menuitem label="Footer"
                                                        oncommand="Presentation.insert('footer')"/>
                                                <menuseparator/>
                                                <menuitem label="Link"
                                                        oncommand="Presentation.insert('link')"/>
                                                <menuitem label="Emphasis"
                                                        oncommand="Presentation.insert('em')"/>
                                                <menuitem label="Preformatted"
                                                        oncommand="Presentation.insert('pre')"/>
                                                <menuitem label="Monta"
                                                        oncommand="Presentation.insert('monta')"/>
                                                <menuitem label="Image"
                                                        oncommand="Presentation.insert('img')"/>
                                        </menupopup>
                                </menu>
                        </menubar>
                        <toolbarseparator/>
                        <toolbarbutton label="View"
                                oncommand="Presentation.toggleEditMode();"/>
                        <toolbarbutton oncommand="Presentation.reload();" label="Reload"/>
                </toolbar>
        </toolbox>
        <textbox id="textField" flex="1" multiline="true"
                oninput="Presentation.onEdit()"/>
</vbox>

</deck>


<broadcasterset>
        <broadcaster id="canBack"/>
        <broadcaster id="canForward"/>
</broadcasterset>

<commandset>
        <command id="cmd_forward"
                oncommand="if (Presentation.isPresentationMode) Presentation.forward();"/>
        <command id="cmd_forwardStep"
                oncommand="if (Presentation.isPresentationMode) Presentation.forwardStep();"/>
        <command id="cmd_back"
                oncommand="if (Presentation.isPresentationMode) Presentation.back();"/>
        <command id="cmd_home"
                oncommand="if (Presentation.isPresentationMode) Presentation.home();"/>
        <command id="cmd_end"
                oncommand="if (Presentation.isPresentationMode) Presentation.end();"/>
</commandset>
<keyset>
        <key keycode="VK_ENTER"      command="cmd_forwardStep"/>
        <key keycode="32"      command="cmd_forwardStep"/>
        <key keycode="VK_RETURN"     command="cmd_forwardStep"/>
        <key keycode="VK_PAGE_DOWN"  command="cmd_forwardStep"/>
        <key keycode="VK_RIGHT"      command="cmd_forwardStep"/>
        <key keycode="VK_DOWN"       command="cmd_forwardStep"/>
        <!--key keycode="VK_BACK_SPACE" command="cmd_back"/-->
        <key keycode="VK_PAGE_UP"    command="cmd_back"/>
        <key keycode="VK_UP"         command="cmd_back"/>
        <key keycode="VK_LEFT"       command="cmd_back"/>
        <key keycode="VK_HOME"       command="cmd_home"/>
        <key keycode="VK_END"        command="cmd_end"/>

        <key id="key_insert_newpage"
                key="n" modifiers="accel" oncommand="Presentation.insert('page');"/>

        <key id="key_save"
                key="s" modifiers="accel" oncommand="Presentation.output();"/>
        <key id="key_reload"
                key="r" modifiers="accel" oncommand="Presentation.reload();"/>
        <key id="key_reload"
                key="p" modifiers="accel" oncommand="Presentation.print();"/>

        <key key="e" modifiers="accel" oncommand="Presentation.toggleEditMode();"/>
        <key key="e" modifiers="accel,shift" oncommand="Presentation.toggleEvaMode();"/>
</keyset>


<script type="application/x-javascript; e4x=1"><![CDATA[

const XULNS   = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
const XHTMLNS = 'http://www.w3.org/1999/xhtml';

var Presentation = {

        size            : 9,
        montaLabelImage : 'monta-label.png',

        phraseOpenParen  : '[',
        phraseCloseParen : ']',
        makePhraseRegExp : function(aPattern, aFlags)
        {
                return new RegExp(
                                aPattern.replace(/%o(pen)?/gi, '\\'+this.phraseOpenParen)
                                        .replace(/%c(lose)?/gi, '\\'+this.phraseCloseParen),
                                aFlags
                        );
        },

        dragStartDelta : 8,

        imageType : 'jpeg',


        initialized : false,

        preventToShowHideToolbar : false,

        showMontaKeywordTimeout : 100,
        autoCruiseInterval      : 2000,
        timerUpdatingInterval   : 30000,

        cachedContents : [],

        init : function(option){
                if (this.initialized) {
                        this.startPresentation();
                        return;
                }
                this.initialized = true;


                this._offset  = 0;
                this.canvas    = document.getElementById('canvas');
                this.content   = document.getElementById('content');
                this.header    = document.getElementById('header');
                this.footer    = document.getElementById('footer');
                this.logo      = document.getElementById('logo');
                this.next      = document.getElementById('nextPage');

                this.indicatorBar = document.getElementById('indicatorBar');
                this.remainder    = document.getElementById('remainingPageIndicator');
                this.timer        = document.getElementById('remainingTimeIndicator');

                this.list     = document.getElementById('pages-list');
                this.textbox  = document.getElementById('textField');
                this.deck     = document.getElementById('deck');
                this.scroller = document.getElementById('scroller');

                this.toolbar         = document.getElementById('canvasToolbar');
                this.toolbarHeight   = this.toolbar.boxObject.height;
                this.isToolbarHidden = true;
                this.toolbar.setAttribute('style', 'margin-top:'+(0-this.toolbarHeight)+'px;margin-bottom:0px;');

                this.preloadImage(this.montaLabelImage);

                window.addEventListener('resize', this, false);
                window.addEventListener('contextmenu', this, false);
                window.addEventListener('CanvasContentAdded', this, false);

                this.canvas.addEventListener('DOMMouseScroll', this, false);
                this.indicatorBar.addEventListener('DOMMouseScroll', this, false);

                if(option){
                        for(var i in option){this[i] = option[i]}
                }

                this.cachedContents = [];

                if (this.readParameter()) {
                        this.startPresentation();
                }

                document.documentElement.focus();
        },

        startPresentation : function()
        {
                if (this.data.length)
                        document.title = this.data[0].title || this.data[0].header || this.data[0].text.join(' ');

                this.takahashi();
        },

        takahashi : function() {
                this.isRendering = true;

                if(!this.data[this.offset]){
                        this.offset = this.data.length-1;
                }
                document.getElementById("current_page").value = this.offset+1;
                document.getElementById("max_page").value     = this.data.length;

                this.scroller.setAttribute('maxpos', this.data.length-1);
                this.scroller.setAttribute('curpos', this.offset);

                var broadcaster = document.getElementById('canBack');
                if (!this.offset)
                        broadcaster.setAttribute('disabled', true);
                else
                        broadcaster.removeAttribute('disabled');

                var broadcaster = document.getElementById('canForward');
                if (this.offset == this.data.length-1)
                        broadcaster.setAttribute('disabled', true);
                else
                        broadcaster.removeAttribute('disabled');

                this.canvas.setAttribute('rendering', true);


                this.header.removeAttribute('style');
                this.footer.removeAttribute('style');
                this.content.removeAttribute('style');

                this.clickableNodes     = [];


                if ('title' in this.data[this.offset])
                        document.title = this.data[this.offset].title;

                this.header.setAttribute('style', 'font-size:10px;');
                this.header.value = this.data[this.offset].header;
                this.footer.setAttribute('style', 'font-size:10px;');
                this.footer.value = this.data[this.offset].footer;

                var page = this.content.getAttribute('page');
                var range = document.createRange();
                range.selectNodeContents(this.content);
                this.cachedContents[page] = {
                        fragment     : range.extractContents(),
                        offsetWidth  : parseInt(this.content.getAttribute('offsetWidth')),
                        offsetHeight : parseInt(this.content.getAttribute('offsetHeight'))
                };
                range.detach();


                if (this.data[this.offset].load) {
                        this.isRendering = false;
                        location.href = location.href.split('?')[0] + '?'+this.data[this.offset].load;
                        return;
                }

                var content = (this.offset in this.cachedContents) ?
                                                        this.cachedContents[this.offset] :
                                                        this.createContent();

                this.content.setAttribute('style', 'font-size:10px;');
                this.content.setAttribute('page',         this.offset);
                this.content.setAttribute('offsetWidth',  content.offsetWidth);
                this.content.setAttribute('offsetHeight', content.offsetHeight);

                this.content.appendChild(content.fragment);

                this.clickableNodes.push(this.content);


                this.fitHeaderFooterToCanvas();
                this.fitMainContentToCanvas();


                try {
                        var checkedItems = this.list.getElementsByAttribute('checked', 'true');
                        max = checkedItems.length;
                        for (i = max-1; i > -1 ; i--)
                                checkedItems[i].removeAttribute('checked');
                }
                catch(e) {
                }

                this.list.getElementsByAttribute('value', this.offset)[0].setAttribute('checked', true);

                this.canvas.removeAttribute('rendering');
                this.isRendering = false;
                this.setHash('page', 'page'+(this.offset+1));

                this.next.value = (this.offset <= this.data.length-2) ? (this.data[this.offset+1].plain.join('') || this.data[this.offset+1].text.join('')).replace(/\s+/g, ' ') : '(no page)' ;
                this.remainder.setAttribute('value', this.offset == 0 ? 0 : parseInt(((this.offset)/(this.data.length-1))*100));

                var event = document.createEvent('Events');
                event.initEvent('PresentationRedraw', false, true);
                this.canvas.dispatchEvent(event);
        },
        createContent : function()
        {
                var retVal = {
                                offsetWidth  : 0,
                                offsetHeight : 0
                        };
                var text = this.data[this.offset].text;
                var line;
                var newLine;
                var uri;
                var image_width;
                var image_height;
                var image_src;

                var labelId = 0;
                var lineRegExp = this.makePhraseRegExp('^([^%O]+)?(%O%Oem:((.+?)(:em)?%C%C)?|%O%O(raw|encoded):((.+?)(:raw|:encoded)?%C%C)?|%O%Opre:((.+?)(:pre)?%C%C)?|%O%O\#[^:]+:((.+?)%C%C)?|%O%Oima?ge? +src="([^"]+)" +width="([0-9]+)" +height="([0-9]+)"[^%C]*%C%C|%O%O(([^\|]+)?\\||)([^%C]+)%C%C|%O([^%C]+)%C)(.+)?', 'i');

                var emRegExp         = this.makePhraseRegExp('^([^%O]+)?%O%Oem:(.+?)()?%C%C', 'i');
                var emStartRegExp    = this.makePhraseRegExp('^([^%O]+)?%O%Oem:(.*)', 'i');
                var emEndRegExp      = this.makePhraseRegExp('^(.*?)((:em)?%C%C)', 'i');

                var preRegExp        = this.makePhraseRegExp('^([^%O]+)?%O%Opre:(.+?)(:pre)?%C%C', 'i');
                var preStartRegExp   = this.makePhraseRegExp('^([^%O]+)?%O%Opre:(.*)', 'i');
                var preEndRegExp     = this.makePhraseRegExp('^(.*?)((:pre)?%C%C)', 'i');

                var rawRegExp        = this.makePhraseRegExp('^([^%O]+)?%O%O(raw|encoded):(.+?)(:raw|:encoded)?%C%C', 'i');
                var rawStartRegExp   = this.makePhraseRegExp('^([^%O]+)?%O%O(raw|encoded):(.*)', 'i');
                var rawEndRegExp     = this.makePhraseRegExp('^(.*?)((:raw|:encoded)?%C%C)', 'i');

                var styleRegExp      = this.makePhraseRegExp('^([^%O]+)?%O%O\\#([^:]+):(.+?)%C%C', '');
                var styleStartRegExp = this.makePhraseRegExp('^([^%O]+)?%O%O\\#([^:]+):(.*)', '');
                var styleEndRegExp   = this.makePhraseRegExp('^(.*?)(%C%C)', '');

                var imagesRegExp     = this.makePhraseRegExp('^([^%O]+)?%O%Oima?ge? +src="([^"]+)" +width="([0-9]+)" +height="([0-9]+)"[^%C]*%C%C', 'i');

                var linksRegExp      = this.makePhraseRegExp('^([^%O]+)?%O%O(([^|]+)?\\||)([^%C]+)%C%C', '');

                var montaRegExp      = this.makePhraseRegExp('^([^%O]+)?%O([^%C]+)%C', '');

                var inBlock       = false,
                        blockClass    = '',
                        blockContents = [];

                var inGrid       = false,
                        gridContents = null;

                var fragment = document.createDocumentFragment();

                var lineBox;
                for (var i = 0, max = text.length; i < max; i++)
                {
                        lineBox = document.createElement('hbox');
                        lineBox.setAttribute('align', 'center');
                        lineBox.setAttribute('pack', this.data[this.offset].align);

                        line = text[i];
                        image_width  = 0;
                        image_height = 0;
                        if (!line) line = ' ';

                        if (inBlock) {
                                if (blockClass == 'raw' &&
                                        rawEndRegExp.test(line)) {
                                        inBlock = false;
                                        blockContents.push(RegExp.$1);
                                        line = line.substring((RegExp.$1+RegExp.$2).length);

                                        eval('var xml = <hbox class="raw" onclick="event.stopPropagation();" onkeypress="event.stopPropagation();">'+blockContents.join('\n')+'</hbox>;');
                                        importNodeTreeWithDelay(importE4XNode(xml, document, XULNS), lineBox, XULNS);

                                        blockClass    = '';
                                        blockContents = [];
                                }
                                else if (blockClass == 'preformatted-text' &&
                                        preEndRegExp.test(line)) {
                                        inBlock = false;
                                        blockContents.push(RegExp.$1);
                                        line = line.substring((RegExp.$1+RegExp.$2).length);

                                        lineBox.appendChild(document.createElement('description'));
                                        lineBox.lastChild.setAttribute('class', 'preformatted-text block');
                                        lineBox.lastChild.appendChild(document.createTextNode(
                                                blockContents.join('\n')
                                                        .replace(/^[\r\n\s]+/, '')
                                                        .replace(/[\r\n\s]+$/, '')
                                                        .replace(/&amp;/g, '&')
                                                        .replace(/&quot;/g, '"')
                                                        .replace(/&gt;/g, '>')
                                                        .replace(/&lt;/g, '<')
                                        ));

                                        blockClass    = '';
                                        blockContents = [];
                                }
                                else if (emEndRegExp.test(line) || styleEndRegExp.test(line)) {
                                        inBlock = false;
                                        blockContents.push(RegExp.$1);
                                        line = line.substring((RegExp.$1+RegExp.$2).length);

                                        lineBox.appendChild(document.createElement('vbox'));
                                        lineBox.lastChild.setAttribute('class', blockClass+' block');
                                        lineBox.lastChild.setAttribute('align', this.data[this.offset].align);
                                        blockContents = blockContents.join('\n')
                                                .replace(/^[\r\n\s]+/, '')
                                                .replace(/[\r\n\s]+$/, '')
                                                .split('\n');
                                        for (var j = 0, jmax = blockContents.length; j < jmax; j++)
                                        {
                                                lineBox.lastChild.appendChild(document.createElement('description'));
                                                lineBox.lastChild.lastChild.setAttribute('value', blockContents[j]);
                                        }

                                        blockClass    = '';
                                        blockContents = [];
                                }
                                else {
                                        blockContents.push(line);
                                        continue;
                                }
                        }

                        if (line.indexOf('|') == 0) {
                                fragment.appendChild(lineBox);

                                if (fragment.childNodes.length == 1 ||
                                        !fragment.childNodes[fragment.childNodes.length-2] ||
                                        !fragment.childNodes[fragment.childNodes.length-2].lastChild ||
                                        fragment.childNodes[fragment.childNodes.length-2].lastChild.localName != 'grid') {
                                        fragment.lastChild.appendChild(document.createElement('grid'));
                                        fragment.lastChild.lastChild.appendChild(document.createElement('columns'));
                                        fragment.lastChild.lastChild.appendChild(document.createElement('rows'));
                                }
                                else {
                                        fragment.removeChild(fragment.lastChild);
                                }
                                fragment.lastChild.lastChild.lastChild.appendChild(document.createElement('row'));
                                fragment.lastChild.lastChild.lastChild.lastChild.setAttribute('flex', 1);

                                line = line.split('|');
                                for (var j = 1, jmax = line.length; j < jmax; j++)
                                {
                                        fragment.lastChild.lastChild.lastChild.lastChild.appendChild(document.createElement('vbox'));
                                        fragment.lastChild.lastChild.lastChild.lastChild.lastChild.setAttribute('align', this.data[this.offset].align);
                                        fragment.lastChild.lastChild.lastChild.lastChild.lastChild.setAttribute('pack', 'center');
                                        if (line[j].charAt(0) == '~') {
                                                fragment.lastChild.lastChild.lastChild.lastChild.lastChild.setAttribute('class', 'special');
                                                line[j] = line[j].substring(1);
                                        }
                                        line[j] = line[j].split(/<br\s*\/>/g);
                                        for (var k = 0, kmax = line[j].length; k < kmax; k++)
                                        {
                                                fragment.lastChild.lastChild.lastChild.lastChild.lastChild.appendChild(document.createElement('description'));
                                                fragment.lastChild.lastChild.lastChild.lastChild.lastChild.lastChild.appendChild(document.createTextNode(line[j][k].replace(/^\s+|\s+$/g, '')));
                                        }
                                        if (fragment.lastChild.lastChild.firstChild.childNodes.length < j) {
                                                fragment.lastChild.lastChild.firstChild.appendChild(document.createElement('column'));
                                                fragment.lastChild.lastChild.firstChild.lastChild.setAttribute('flex', 1);
                                        }
                                }
                                continue;
                        }


                        while (line.match(lineRegExp))
                        {
                                if (RegExp.$1) {
                                        lineBox.appendChild(document.createElement('description'));
                                        lineBox.lastChild.setAttribute('value', RegExp.$1);
                                }
                                newLine = line.substring((RegExp.$1+RegExp.$2).length);

                                // Raw Codes: Parsed as XML
                                if (rawRegExp.test(line)) {
                                        eval('var xml = <hbox class="raw" onclick="event.stopPropagation();" onkeypress="event.stopPropagation();">'+RegExp.$3+'</hbox>;');
                                        importNodeTreeWithDelay(importE4XNode(xml, document, XULNS), lineBox, XULNS);
                                }
                                else if (rawStartRegExp.test(line)) {
                                        inBlock       = true;
                                        blockClass    = 'raw';
                                        blockContents = [RegExp.$3];
                                        newLine       = '';
                                }

                                // Preformatted Text
                                if (preRegExp.test(line)) {
                                        lineBox.appendChild(document.createElement('description'));
                                        lineBox.lastChild.setAttribute('value', RegExp.$2);
                                        lineBox.lastChild.setAttribute('class', 'preformatted-text');
                                }
                                else if (preStartRegExp.test(line)) {
                                        inBlock       = true;
                                        blockClass    = 'preformatted-text';
                                        blockContents = [RegExp.$2];
                                        newLine       = '';
                                }

                                // Emphasis
                                else if (emRegExp.test(line)) {
                                        lineBox.appendChild(document.createElement('description'));
                                        lineBox.lastChild.setAttribute('value', RegExp.$2);
                                        lineBox.lastChild.setAttribute('class', 'em-text');
                                }
                                else if (emStartRegExp.test(line)) {
                                        inBlock       = true;
                                        blockClass    = 'em-text';
                                        blockContents = [RegExp.$2];
                                        newLine       = '';
                                }

                                // User-defined Styles
                                else if (styleRegExp.test(line)) {
                                        lineBox.appendChild(document.createElement('description'));
                                        lineBox.lastChild.setAttribute('value', RegExp.$3);
                                        lineBox.lastChild.setAttribute('class', RegExp.$2);
                                }
                                else if (styleStartRegExp.test(line)) {
                                        inBlock       = true;
                                        blockClass    = RegExp.$2;
                                        blockContents = [RegExp.$3];
                                        newLine       = '';
                                }

                                // Images
                                else if (imagesRegExp.test(line)) {
                                        lineBox.appendChild(document.createElement('image'));
                                        image_src = RegExp.$2;
                                        if (image_src.indexOf('http://') < 0 &&
                                                image_src.indexOf('https://') < 0)
                                                image_src = this.dataFolder+image_src;
                                        lineBox.lastChild.setAttribute('src', image_src);
                                        lineBox.lastChild.setAttribute('width', parseInt(RegExp.$3 || '0'));
                                        lineBox.lastChild.setAttribute('height', parseInt(RegExp.$4 || '0'));
                                        image_width  += parseInt(RegExp.$3 || '0');
                                        image_height = Math.max(image_height, parseInt(RegExp.$4 || '0'));
                                }

                                // Links
                                else if (linksRegExp.test(line)) {
                                        uri = RegExp.$4;
                                        if (uri.indexOf('://') < 0)
                                                uri = this.dataFolder+uri;
                                        lineBox.appendChild(document.createElement('description'));
                                        lineBox.lastChild.setAttribute('value', RegExp.$3 || RegExp.$4);
                                        lineBox.lastChild.setAttribute('href', uri);
                                        lineBox.lastChild.setAttribute('tooltiptext', uri);
                                        lineBox.lastChild.setAttribute('statustext', uri);
                                        lineBox.lastChild.setAttribute('class', 'link-text');

                                        this.clickableNodes.push(lineBox.lastChild);
                                }

                                // Monta
                                else if (montaRegExp.test(line)) {
                                        lineBox.appendChild(document.createElement('stack'));

                                        lineBox.lastChild.appendChild(document.createElement('description'));
                                        lineBox.lastChild.lastChild.setAttribute('value', RegExp.$2);
                                        lineBox.lastChild.lastChild.setAttribute('class', 'monta-text');

                                        lineBox.lastChild.appendChild(document.createElement('spacer'));
                                        lineBox.lastChild.lastChild.setAttribute('flex', 1);
                                        lineBox.lastChild.lastChild.setAttribute('class', 'monta-label');

                                        lineBox.lastChild.lastChild.setAttribute('label-id', 'label-' + (++labelId));

                                        lineBox.lastChild.lastChild.setAttribute('monta-hidden', 'true');

                                        this.clickableNodes.push(lineBox.lastChild.lastChild);
                                }

                                line = newLine;
                        }

                        if (line) {
                                lineBox.appendChild(document.createElement('description'));
                                lineBox.lastChild.setAttribute('value', line);
                        }

                        retVal.offsetWidth = Math.max(retVal.offsetWidth, image_width);
                        retVal.offsetHeight += image_height;

                        if (lineBox.hasChildNodes())
                                fragment.appendChild(lineBox);
                }

                retVal.fragment = fragment;
                return retVal;
        },

        fitToCanvas : function(aContent, aCanvas, aOffsetWidth, aOffsetHeight)
        {
                aContent.removeAttribute('style');
                aContent.setAttribute('style', 'font-size:10px;');

                var grids      = aContent.getElementsByTagName('grid');
                var gridsCount = grids.length;

                if (!aContent.boxObject.width) return;

                var canvas_w  = aCanvas.boxObject.width;
                var canvas_h  = aCanvas.boxObject.height-aOffsetHeight;

                var content_w = aContent.boxObject.width;
                var new_fs = Math.round((canvas_w/content_w) * this.size);
                aContent.setAttribute('style', 'font-size:'+ new_fs + "px");

                for (var i = 0; i < gridsCount; i++)
                {
                        grids[i].firstChild.lastChild.removeAttribute('flex', 1);
                        grids[i].firstChild.lastChild.setAttribute('flex', 1);
                }

                if (aContent.boxObject.width < aOffsetWidth) {
                        content_w = aOffsetWidth;
                        new_fs = Math.round((canvas_w/content_w) * this.size);
                        aContent.setAttribute('style', 'font-size:'+ new_fs + "px");

                        for (var i = 0; i < gridsCount; i++)
                        {
                                grids[i].firstChild.lastChild.removeAttribute('flex', 1);
                                grids[i].firstChild.lastChild.setAttribute('flex', 1);
                        }
                }

                var content_h = aContent.boxObject.height;
                if(content_h >= canvas_h){
                        state='height';
                        content_h = aContent.boxObject.height;
                        new_fs = Math.round((canvas_h/content_h) * new_fs);
                        aContent.setAttribute('style', 'font-size:'+ new_fs + "px");

                        for (var i = 0; i < gridsCount; i++)
                        {
                                grids[i].firstChild.lastChild.removeAttribute('flex', 1);
                                grids[i].firstChild.lastChild.setAttribute('flex', 1);
                        }
                }
        },
        fitMainContentToCanvas : function()
        {
                this.fitToCanvas(
                        this.content,
                        this.canvas,
                        parseInt(this.content.getAttribute('offsetWidth')),
                        parseInt(this.content.getAttribute('offsetHeight'))
                        +this.header.boxObject.height
                        +this.footer.boxObject.height
                );
        },
        fitHeaderFooterToCanvas : function()
        {
                this.fitToCanvas(this.header, this.header.parentNode, 0, 0);
                this.fitToCanvas(this.footer, this.footer.parentNode, 0, 0);
        },

        reload : function() {
                var file = String(location.href).replace(/#.+$/, '');
                if (this.dataPath != file) {
                        var path = this.dataPath;
                        var request = new XMLHttpRequest();
                        request.open('GET', path);
                        request.onload = function() {
                                Presentation.textbox.value = request.responseText;
                                Presentation.data = Presentation.textbox.value;
                                Presentation.init();

                                path = null;
                                request = null;
                        };
                        request.send(null);
                }
                else
                        window.location.reload();
        },

        forward : function(){
                if (!this.canForward) return;
                this.offset++;
                this.takahashi();
        },
        forwardStep : function(){
                if (!this.canForward) return;
                var monta = document.getElementsByAttribute('monta-hidden', 'true');
                if (monta && monta.length) {
                        this.showMontaKeyword(monta[0]);
                }
                else
                        this.forward();
        },
        back : function(){
                if (!this.canBack) return;
                this.offset--;
                if(this.offset < 0){this.offset = 0}
                this.takahashi();
        },
        home : function(){
                if (!this.canMove) return;
                this.offset = 0;
                this.takahashi();
        },
        end : function(){
                if (!this.canMove) return;
                this.offset = this.data.length-1;
                this.takahashi();
        },
        showPage : function(aPageOffset){
                if (!this.canMove) return;
                this.offset = aPageOffset ? aPageOffset : 0 ;
                this.takahashi();
        },

        get canMove()
        {
                return (
                                this.isRendering ||
                                importNodeTreeWithDelayTimers ||
                                this.montaAnimating
                        ) ? false : true ;
        },
        get canBack()
        {
                return this.canMove;
        },
        get canForward()
        {
                return this.canMove;
        },


        insert : function(aType) {
                switch (aType)
                {
                        case 'page':
                                this.insertTextFor('\n----\n', this.textbox, 6);
                                break;
                        case 'header':
                                this.insertTextFor('\nHEADER::\n', this.textbox, 9);
                                break;
                        case 'footer':
                                this.insertTextFor('\nFOOTER::\n', this.textbox, 9);
                                break;

                        case 'em':
                        case 'emphasis':
                                this.insertTextFor(this.phraseOpenParen+this.phraseOpenParen+'EM:'+this.phraseCloseParen+this.phraseCloseParen, this.textbox, 5);
                                break;
                        case 'pre':
                        case 'preformatted':
                                this.insertTextFor(this.phraseOpenParen+this.phraseOpenParen+'PRE:'+this.phraseCloseParen+this.phraseCloseParen, this.textbox, 6);
                                break;
                        case 'monta':
                                this.insertTextFor(this.phraseOpenParen+this.phraseCloseParen, this.textbox, 1);
                                break;
                        case 'link':
                                this.insertTextFor(this.phraseOpenParen+this.phraseOpenParen+'|http://'+this.phraseCloseParen+this.phraseCloseParen, this.textbox, 2);
                                break;
                        case 'img':
                        case 'image':
                                this.insertTextFor(this.phraseOpenParen+this.phraseOpenParen+'image src="" width="" height=""'+this.phraseCloseParen+this.phraseCloseParen, this.textbox, 13);
                                break;

                        default:
                                return;
                }
                this.onEdit();
        },
        insertTextFor : function(aString, aNode, aPosOffset)
        {
                var pos = aNode.selectionStart;
                var value = aNode.value;
                aNode.value = [value.substring(0, pos), aString, value.substring(pos, value.length)].join('');
                aNode.selectionEnd = aNode.selectionStart = pos + (aPosOffset || 0);
        },


        output : function()
        {
                location.href = 'data:application/octet-stream,'+encodeURIComponent(this.textbox.value);
        },


        toggleEditMode : function(){
                this.deck.selectedIndex = this.deck.selectedIndex == 0 ? 1 : 0 ;
                this.setHash('edit', this.deck.selectedIndex == 0 ? '' : 'edit' );
        },
        toggleEvaMode : function(){
                var check = document.getElementById('toggleEva');
                if (this.canvas.getAttribute('eva') == 'true') {
                        this.canvas.removeAttribute('eva');
                        this.logo.removeAttribute('eva');
                        check.checked = false;
                }
                else {
                        this.canvas.setAttribute('eva', true);
                        this.logo.setAttribute('eva',true);
                        check.checked = true;
                }
                this.setHash('eva', check.checked ? 'eva' : '' );
        },



        toggleAutoCruiseMode : function()
        {
                var autoCruise = document.getElementById('autoButton');
                if(!autoCruise.checked)
                        this.startAutoCruise();
                else
                        autoCruise.checked = false;
        },
        startAutoCruise : function()
        {
                var autoCruise = document.getElementById('autoButton');
                autoCruise.checked = true;

                if (this.autoCruiseTimer) {
                        window.clearTimeout(this.autoCruiseTimer);
                }
                this.autoCruiseTimer = window.setTimeout(this.autoCruise, this.autoCruiseInterval);
        },

        changeAutoCruiseInterval : function(aInterval)
        {
                this.autoCruiseInterval = aInterval;
                this.startAutoCruise();
        },

        autoCruise : function()
        {
                var autoCruise = document.getElementById('autoButton');
                if (!autoCruise.checked) return;

                if (Presentation.offset == Presentation.data.length-1) {
                        if (Presentation.canMove)
                                Presentation.home();
                }
                else {
                        if (Presentation.canForward)
                                Presentation.forwardStep();
                }
                Presentation.autoCruiseTimer = window.setTimeout(arguments.callee, Presentation.autoCruiseInterval);
        },
        autoCruiseTimer : null,



        setHash : function(aKey, aValue)
        {
                aKey = String(aKey).toLowerCase();
                var hashArray = String(location.hash).replace(/^#/, '').toLowerCase().split(',');

                for (var i = hashArray.length-1; i > -1; i--)
                        if (!hashArray[i] || hashArray[i].indexOf(aKey) == 0)
                                hashArray.splice(i, 1);

                if (aValue) hashArray.push(aValue);
                hashArray.sort();

                location.hash = hashArray.length ? hashArray.join(',') : '' ;
        },


        showMontaKeyword : function(aNode, aWithoutAnimation) {
                if (aNode.getAttribute('monta-hidden') != 'true') return;

                if (aWithoutAnimation) {
                        aNode.setAttribute('monta-hidden', 'false');
                        return;
                }

                aNode.setAttribute('monta-hidden', 'progress');

                this.montaAnimating = true;

                window.setTimeout(this.showMontaKeywordCallback, 0, {
                        position : -100,
                        node     : aNode,
                        interval : this.showMontaKeywordTimeout/10
                });
        },
        showMontaKeywordCallback : function(aInfo) {
                if (aInfo.position >= aInfo.node.boxObject.width) {
                        aInfo.node.setAttribute('monta-hidden', 'false');
                        Presentation.montaAnimating = false;
                        return;
                }

                aInfo.position += (aInfo.node.boxObject.width/10);
                aInfo.node.setAttribute('style', 'background-position: '+aInfo.position+'px 0 !important;');
                window.setTimeout(arguments.callee, aInfo.interval, aInfo);
        },
        montaAnimating : false,



        onPresentationClick : function(aEvent)
        {
                if (this.isPrinting) {
                        if (confirm('Do you want printing operation to be stopped?')) {
                                this.stopPrint();
                        }
                        return;
                }

                if (!this.isToolbarHidden)
                        this.showHideToolbar();

                switch(aEvent.button)
                {
                        case 0:
                                switch (aEvent.target.getAttribute('class'))
                                {
                                        case 'link-text':
                                                var uri = aEvent.target.getAttribute('href');
                                                if (uri) {
                                                        window.open(uri);
                                                        return;
                                                }
                                                break;

                                        case 'monta-label':
                                                if (aEvent.target.getAttribute('monta-hidden') == 'true') {
                                                        this.showMontaKeyword(aEvent.target);
                                                        aEvent.preventBubble();
                                                        return;
                                                }

                                        default:
                                                break;
                                }
                                this.forward();
                                document.documentElement.focus();
                                break;
                        case 2:
                                this.back();
                                document.documentElement.focus();
                                break;
                        default:
                                break;
                }
        },

        onScrollerDragStart : function(){
                if (this.isPrinting) return;

                this.scroller.dragging = true;
        },
        onScrollerDragMove : function(){
                if (this.isPrinting) return;

                if (this.scroller.dragging)
                        this.showPage(parseInt(this.scroller.getAttribute('curpos')));
        },
        onScrollerDragDrop : function(){
                if (this.isPrinting) return;

                this.onScrollerDragMove();
                this.scroller.dragging = false;
        },

        onIndicatorBarClick : function(aEvent)
        {
                if (this.isPrinting) return;

                var bar = this.indicatorBar;
                this.showPage(Math.round((aEvent.screenX - bar.boxObject.screenX) / bar.boxObject.width * this.data.length));
        },
        onIndicatorBarDragStart : function()
        {
                if (this.isPrinting) return;

                this.indicatorBar.dragging = true;
        },
        onIndicatorBarDragMove : function(aEvent)
        {
                if (this.isPrinting) return;

                var bar = this.indicatorBar;
                this.showPage(Math.round((aEvent.screenX - bar.boxObject.screenX) / bar.boxObject.width * this.data.length));
        },
        onIndicatorBarDragEnd : function(aEvent)
        {
                if (this.isPrinting) return;

                this.onIndicatorBarDragMove(aEvent);
                this.indicatorBar.dragging = false;
        },

        onEdit : function() {
                if (this.isPrinting) return;

                this.data = this.textbox.value;
                this.init();
        },

        onKeyPress : function(aEvent) {
                if (this.isPrinting) return;

                switch(aEvent.keyCode)
                {
                        case aEvent.DOM_VK_BACK_SPACE:
                                if (this.isPresentationMode) {
                                        aEvent.preventBubble();
                                        aEvent.preventDefault();
                                        Presentation.back();
                                }
                                break;
                        default:
                                break;
                }
        },



        handleEvent : function(aEvent)
        {
                if (this.isPrinting) return;

                var node = aEvent.target;
                var inRawContents = false;
                do {
                        if (node.nodeType == Node.ELEMENT_NODE &&
                                /\braw\b/i.test(node.getAttribute('class'))) {
                                inRawContents = true;
                                break;
                        }

                        node = node.parentNode;
                }
                while (node.parentNode)


                switch (aEvent.type)
                {
                        default:
                                break;

                        case 'resize':
                                this.takahashi(); // redrwa
                                break;

                        case 'contextmenu':
                                aEvent.stopPropagation();
                                aEvent.preventCapture();
                                aEvent.preventDefault();
                                aEvent.preventBubble();
                                break;


                        case 'mouseup':
                                if (inRawContents) return;
                                this.dragStartX = -1;
                                this.dragStartY = -1;
                                if (this.indicatorBar.dragging)
                                        this.onIndicatorBarDragEnd(aEvent);
                                break;

                        case 'mousedown':
                                if (inRawContents) return;
                                if (this.dragStartX < 0) {
                                        this.dragStartX = aEvent.clientX;
                                        this.dragStartY = aEvent.clientY;
                                }
                                var box = this.indicatorBar.boxObject;
                                if (!(aEvent.screenX < box.screenX ||
                                        aEvent.screenY < box.screenY ||
                                        aEvent.screenX > box.screenX+box.width ||
                                        aEvent.screenY > box.screenY+box.height))
                                        this.onIndicatorBarDragStart();
                                break;

                        case 'mousemove':
                                if (inRawContents) return;
                                this.checkShowHideToolbar(aEvent);
                                if (this.indicatorBar.dragging) {
                                        this.onIndicatorBarDragMove(aEvent);
                                        return;
                                }
                                if (this.dragStartX > -1) {
                                        if (Math.abs(this.dragStartX-aEvent.clientX) > Math.abs(this.dragStartDelta) ||
                                                Math.abs(this.dragStartY-aEvent.clientY) > Math.abs(this.dragStartDelta)) {
                                                var event = document.createEvent('Events');
                                                event.initEvent('StartDragOnCanvas', false, true);
                                                this.canvas.dispatchEvent(event);
                                        }
                                }
                                break;

                        case 'CanvasContentAdded':
                                if (this.fitToCanvasTimer) {
                                        window.clearTimeout(this.fitToCanvasTimer);
                                        this.fitToCanvasTimer = null;
                                }
                                this.fitToCanvasTimer = window.setTimeout('Presentation.fitMainContentToCanvas()', 100);
                                break;

                        case 'DOMMouseScroll':
                                if (
                                        (aEvent.detail > 0 && this.scrollCounter < 0) ||
                                        (aEvent.detail < 0 && this.scrollCounter > 0)
                                        )
                                        this.scrollCounter = 0;

                                this.scrollCounter += aEvent.detail;
                                if (Math.abs(this.scrollCounter) >= this.scrollThreshold) {
                                        if (aEvent.detail > 0)
                                                Presentation.forwardStep();
                                        else
                                                Presentation.back();

                                        this.scrollCounter = 0;
                                }
                                break;
                }
        },
        dragStartX : -1,
        dragStartY : -1,
        scrollCounter : 0,
        scrollThreshold : 10,



        onToolbarArea   : false,
        toolbarHeight   : 0,
        toolbarDelay    : 300,
        toolbarTimer    : null,
        isToolbarHidden : false,
        checkShowHideToolbar : function(aEvent) {
                if (!this.scroller || this.scroller.dragging || this.preventToShowHideToolbar) return;

                this.onToolbarArea = (aEvent.clientY < this.toolbarHeight);

                if (this.isToolbarHidden == this.onToolbarArea) {
                        if (this.toolbarTimer) window.clearTimeout(this.toolbarTimer);
                        this.toolbarTimer = window.setTimeout('Presentation.checkShowHideToolbarCallback()', this.toolbarDelay);
                }
        },
        checkShowHideToolbarCallback : function() {
                if (this.isToolbarHidden == this.onToolbarArea)
                        this.showHideToolbar();
        },

        toolbarAnimationDelay : 100,
        toolbarAnimationSteps : 5,
        toolbarAnimationInfo  : null,
        toolbarAnimationTimer : null,
        showHideToolbar : function(aWithoutAnimation)
        {
                if (this.isPrinting) return;

                if (this.toolbarAnimationTimer) window.clearTimeout(this.toolbarAnimationTimer);

                this.toolbarAnimationInfo = { count : 0 };
                if (this.isToolbarHidden) {
                        this.toolbarAnimationInfo.start = 0;
                        this.toolbarAnimationInfo.end   = this.toolbarHeight;
                }
                else {
                        this.toolbarAnimationInfo.start = this.toolbarHeight;
                        this.toolbarAnimationInfo.end   = 0;
                }
                this.toolbarAnimationInfo.current = 0;

                this.toolbar.setAttribute('style', 'margin-top:'+(0-(this.toolbarHeight-this.toolbarAnimationInfo.start))+'px; margin-bottom:'+(0-this.toolbarAnimationInfo.start)+'px;');

                if (aWithoutAnimation) {
                        this.toolbarAnimationInfo.current = this.toolbarHeight;
                        Presentation.animateToolbar();
                }
                else {
                        this.toolbarAnimationTimer = window.setTimeout('Presentation.animateToolbar()', this.toolbarAnimationDelay/this.toolbarAnimationSteps);
                }
        },
        animateToolbar : function()
        {
                this.toolbarAnimationInfo.current += parseInt(this.toolbarHeight/this.toolbarAnimationSteps);

                var top, bottom;
                if (this.toolbarAnimationInfo.start < this.toolbarAnimationInfo.end) {
                        top    = this.toolbarHeight-this.toolbarAnimationInfo.current;
                        bottom = this.toolbarAnimationInfo.current;
                }
                else {
                        top    = this.toolbarAnimationInfo.current;
                        bottom = this.toolbarHeight-this.toolbarAnimationInfo.current;
                }

                top    = Math.min(Math.max(top, 0), this.toolbarHeight);
                bottom = Math.min(Math.max(bottom, 0), this.toolbarHeight);

                this.toolbar.setAttribute('style', 'margin-top:'+(0-top)+'px; margin-bottom:'+(0-bottom)+'px');

                if (this.toolbarAnimationInfo.count < this.toolbarAnimationSteps) {
                        this.toolbarAnimationInfo.count++;
                        this.toolbarAnimationTimer = window.setTimeout('Presentation.animateToolbar()', this.toolbarAnimationDelay/this.toolbarAnimationSteps);
                }
                else
                        this.isToolbarHidden = !this.isToolbarHidden;
        },



        get offset(){
                return this._offset;
        },
        set offset(aValue){
                this._offset = parseInt(aValue || 0);
                document.documentElement.setAttribute('lastoffset', this.offset);
                return this.offset;
        },

        get data(){
                var codes = document.getElementById('builtinCode');
                if (!this._data) {
                        // mozilla splits very long text node into multiple text nodes whose length is less than 4096 bytes.
                        // so, we must concat all the text nodes.
                        this.textbox.value = "";
                        for (var i = 0; i < codes.childNodes.length; i++) {
                                this.textbox.value += codes.childNodes[i].nodeValue;
                        }

                        this._data = this.textbox.value.split(/----+/);
                        this.initData();
                }
                if (codes)
                        codes.parentNode.removeChild(codes);

                return this._data;
        },
        set data(aValue){
                this._data = aValue.split(/----+/);
                this.initData();
                return aValue;
        },
        initData : function()
        {
                var range = document.createRange();
                range.selectNodeContents(this.list);
                range.deleteContents();


                var regexp = [
                                /^[\r\n\s]+/g,
                                /[\r\n\s]+$/g,
                                /(\r\n|[\r\n])/g
                        ];

                var title;
                var titleRegExp   = /^(TITLE::)([^\n]*)\n?/im;
                var header        = '';
                var headerRegExp  = /^(HEADER::)([^\n]*)\n?/im;
                var footer        = '';
                var footerRegExp  = /^(FOOTER::)([^\n]*)\n?/im;
                var chapter       = '';
                var chapterRegExp = /^(CHAPTER::)([^\n]*)\n?/im;
                var lastChapter;
                var alignGlobal   = 'center';
                var align;
                var alignRegExp   = /^((GLOBAL-)?ALIGN::)(left|right|center|start|end)?\n?/im;

                var imageMatchResults;
                var imagesRegExp  = this.makePhraseRegExp('%O%Oima?ge? +src="[^"]+" +width="[0-9]+" +height="[0-9]+"[^%C]*%C%C', 'gi');
                var imagesRegExp2 = this.makePhraseRegExp('%O%Oima?ge? +src="([^"]+)"', 'i');
                var image_src;

                var plainTextRegExp = this.makePhraseRegExp('(%O%O\#[^:]+:(.+)%C%C|%O%OEM:(.+)(:EM)?%C%C|%O%OPRE:(.+)(:PRE)?%C%C|%O%Oima?ge? +src="[^"]*"[^%C]+%C%C|%O%O([^\\|%C]+)(\\|[^%C]+)?%C%C|%O([^%O]+)%C)', 'gi');

                var hiddenRegExp = /^(HIDDEN|IGNORE)::true\n?/im;

                var loadRegExp = /^LOAD::(.+)\n?/im;

                var dataObj;
                var i, j,
                        max = this._data.length;
                var fragment = document.createDocumentFragment();
                var popup;
                var dataPath;
                for (i = 0; i < max; i++)
                {
                        image_src = null;
                        align     = null;
                        dataPath  = '';

                        this._data[i] = this._data[i]
                                .replace(regexp[0], '')
                                .replace(regexp[1], '')
                                .replace(regexp[2], '\n');

                        if (loadRegExp.test(this._data[i])) {
                                this._data[i] = this._data[i].replace(loadRegExp, '');
                                dataPath = RegExp.$1;
                        }

                        if (hiddenRegExp.test(this._data[i])) {
                                this._data.splice(i, 1);
                                max--;
                                i--;
                                continue;
                        }

                        while (titleRegExp.test(this._data[i])) {
                                this._data[i] = this._data[i].replace(titleRegExp, '');
                                if (String(RegExp.$1).toUpperCase() == 'TITLE::')
                                        title = RegExp.$2 || '' ;
                        }

                        while (headerRegExp.test(this._data[i])) {
                                this._data[i] = this._data[i].replace(headerRegExp, '');
                                if (String(RegExp.$1).toUpperCase() == 'HEADER::')
                                        header = RegExp.$2 || '' ;
                        }

                        while (footerRegExp.test(this._data[i])) {
                                this._data[i] = this._data[i].replace(footerRegExp, '');
                                if (String(RegExp.$1).toUpperCase() == 'FOOTER::')
                                        footer = RegExp.$2 || '' ;
                        }

                        while (chapterRegExp.test(this._data[i])) {
                                this._data[i] = this._data[i].replace(chapterRegExp, '');
                                if (String(RegExp.$1).toUpperCase() == 'CHAPTER::')
                                        chapter = RegExp.$2 || '' ;
                        }

                        while (alignRegExp.test(this._data[i])) {
                                this._data[i] = this._data[i].replace(alignRegExp, '');

                                align = (RegExp.$3 || '').toLowerCase();
                                if (align == 'left')
                                        align = 'start';
                                else if (align == 'right')
                                        align = 'end';

                                if (String(RegExp.$1).toUpperCase() == 'GLOBAL-ALIGN::') {
                                        alignGlobal = align;
                                        align = null;
                                }
                        }

                        imageMatchResults = this._data[i].match(imagesRegExp);
                        if (imageMatchResults) {
                                for (j = imageMatchResults.length-1; j > -1; j--)
                                        image_src = this.preloadImage(imageMatchResults[j].match(imagesRegExp2)[1]);
                        }

                        this._data[i] = {
                                load   : dataPath,
                                header : header,
                                footer : footer,
                                text   : this._data[i].split('\n'),
                                image  : image_src,
                                align  : align || alignGlobal
                        };
                        this._data[i].plain = this._data[i].text
                                                        .join('\n')
                                                        .replace(plainTextRegExp, '$2$3$5$7$9')
                                                        .split('\n');
                        if (title !== void(0))
                                this._data[i].title = title;

                        this._data[i].chapter = chapter || title || '';
                        if (lastChapter === void(0) ||
                                lastChapter != this._data[i].chapter) {
                                lastChapter = this._data[i].chapter;

                                if (popup && popup.childNodes.length == 1) {
                                        fragment.removeChild(fragment.lastChild);
                                        fragment.appendChild(popup.removeChild(popup.lastChild));
                                }

                                popup = document.createElement('menupopup');
                                fragment.appendChild(document.createElement('menu'));
                                fragment.lastChild.setAttribute('label', this._data[i].chapter);
                                fragment.lastChild.appendChild(popup);
                        }

                        popup.appendChild(document.createElement('menuitem'));
                        popup.lastChild.setAttribute('type', 'radio');
                        popup.lastChild.setAttribute('radiogroup', 'pages');
                        popup.lastChild.setAttribute('label', (i+1)+': '+(
                                (this._data[i].plain.join('') || this._data[i].text.join(' ')).replace(/\s+/g, ' ')
                        ));
                        popup.lastChild.setAttribute('value', i);

//                      if (image_src) {
//                              popup.lastChild.setAttribute('image', image_src);
//                              popup.lastChild.setAttribute('class', 'menuitem-iconic');
//                      }
                }

                if (fragment.childNodes.length == 1) {
                        range.selectNodeContents(fragment.firstChild.firstChild);
                        fragment = range.extractContents();
                }
                this.list.appendChild(fragment);

                range.detach();


                this.shownMontaLabels = [];
        },

        preloadImage : function(aURI)
        {
                if (aURI in this.imageRequests) return;

                if (aURI.indexOf('http://') < 0 &&
                        aURI.indexOf('https://') < 0)
                        aURI = this.dataFolder+aURI;

                this.imageRequests[aURI] = new XMLHttpRequest();
                try {
                        this.imageRequests[aURI].open('GET', aURI);
                        this.imageRequests[aURI].onload = function() {
                                Presentation.imageRequests[aURI] = null;
                        };
                        this.imageRequests[aURI].send(null);
                }
                catch(e) {
                        this.imageRequests[aURI] = null;
                }
                return aURI;
        },
        imageRequests : {},


        get isPresentationMode(){
                return (this.deck.selectedIndex == 0);
        },


        get dataPath(){
                if (!this._dataPath)
                        this.dataPath = String(location.href).replace(/#.+$/, '');
                return this._dataPath;
        },
        set dataPath(aValue){
                var oldDataPath = this._dataPath;
                this._dataPath = aValue;
                if (oldDataPath != aValue) {
                        this._dataFolder = this._dataPath.split('?')[0].replace(/[^\/]+$/, '');
                }
                return this._dataPath;
        },

        get dataFolder(){
                if (!this._dataFolder)
                        this.dataPath = this.dataPath;
                return this._dataFolder;
        },
        set dataFolder(aValue){
                this._dataFolder = aValue;
                return this._dataFolder;
        },

        readParameter : function() {
                if (location.search || location.hash) {
                        var param = location.search.replace(/^\?/, '');

                        if (location.hash.match(/page([0-9]+)/i) ||
                                param.match(/page=([0-9]+)/i))
                                this.offset = parseInt(RegExp.$1)-1;

                        if (location.hash.match(/edit/i) ||
                                param.match(/edit=(1|true|yes)/i))
                                this.toggleEditMode();

                        if (location.hash.match(/eva/i) ||
                                param.match(/eva=(1|true|yes)/i))
                                this.toggleEvaMode();

                        if (location.hash.match(/timer(\d+)\-(\d+)/i))
                                this.setTimer(RegExp.$1, RegExp.$2);

                        if (param.match(/(style|css)=([^&;]+)/i)) {
                                var style = unescape(RegExp.$2);
                                var pi = document.createProcessingInstruction('xml-stylesheet', 'href="'+style+'" type="text/css"');
                                document.insertBefore(pi, document.documentElement);
                        }

                        if (param.match(/data=([^&;]+)/i)) {
                                this.loadData(RegExp.$1);
                                return false;
                        }
                }
                return true;
        },
        loadData : function(aPath)
        {
                this.dataPath = aPath;
                var request = new XMLHttpRequest();
                request.open('GET', aPath);
                request.onload = function() {
                        Presentation.textbox.value = request.responseText;
                        Presentation.data = Presentation.textbox.value;
                        Presentation.init();
                };
                request.send(null);
        },



        resetTimer : function()
        {
                if (this.timerTimer) {
                        window.clearInterval(this.timerTimer);
                        this.timerTimer = null;
                }
                this.timer.setAttribute('value', 0);
                this.timer.setAttribute('collapsed', true);
                this.setHash('timer', '');
        },
        setTimer : function(aStart, aEnd)
        {
                var now = (new Date()).getTime();
                if (aStart === void(0) || aEnd === void(0)) {
                        var rest = prompt('Remaining Time (minits)');
                        if (rest == '') {
                                this.resetTimer();
                                return;
                        }
                        else {
                                rest = Number(rest);
                                if (!rest || isNaN(rest)) return;
                        }

                        rest = Math.abs(rest);
                        this.timerStart = now;
                        this.timerEnd   = this.timerStart + (rest * 60000);
                }
                else {
                        aStart = Number(aStart);
                        aEnd   = Number(aEnd);
                        if (isNaN(aStart) || isNaN(aEnd)) return;

                        this.timerStart = Math.min(aStart, aEnd);
                        this.timerEnd   = Math.max(aStart, aEnd);

                        if (this.timerStart >= now || this.timerEnd <= now) return;
                }

                this.resetTimer();

                this.timer.removeAttribute('collapsed');
                this.setHash('timer', 'timer'+this.timerStart+'-'+this.timerEnd);

                if (now != this.timerStart)
                        this.updateTimer(this);

                window.setInterval(this.updateTimer, Math.min(this.timerUpdatingInterval, (this.timerEnd-this.timerStart)/(this.data.length*2)), this);
        },
        updateTimer : function(aThis)
        {
                var now = (new Date()).getTime();
                if (now >= aThis.timerEnd) {
                        aThis.resetTimer();
                        aThis.timer.setAttribute('value', 100);
                        aThis.timer.removeAttribute('collapsed');
                        aThis.setHash('timer', '');
                }
                else {
                        var value = parseInt(((now - aThis.timerStart) / (aThis.timerEnd - aThis.timerStart)) * 100);
                        aThis.timer.setAttribute('value', value);
                }
        },
        timerStart : 0,
        timerEnd   : 0,
        timerTimer : null,



        print : function()
        {
                if (!this.canMove) {
                        alert('Please wait for a while, and retry later.');
                        return;
                }

                this.stopPrint();
                if (this.printWindow) {
                        this.printWindow.close();
                        this.printWindow = null;
                }

                if (!this.isToolbarHidden)
                        this.showHideToolbar(true);

                this.printWindow = window.open('output.htm', 'PresentationPrint', 'dependent=yes,hotkeys=yes,location=yes,menubar=yes,personalbar=yes,scrollbars=yes,status=yes,toolbar=yes');
                if (!this.printWindow) return;

                this.isPrinting = true;

                if (!this.printCanvas)
                        this.printCanvas = document.createElementNS(XHTMLNS, 'canvas');

                this.printWindow.document.write('<html><head><title>'+document.title+'</title></head><body></body></html>');
                this.home();
                this.printTimer = window.setInterval(this.printCallback, 0, this);
        },
        printCallback : function(aThis)
        {
                if (
                        !aThis.canMove
                        )
                        return;

                var monta = document.getElementsByAttribute('monta-hidden', 'true');
                if (monta && monta.length) {
                        for (var i = monta.length-1; i > -1; i--)
                                aThis.showMontaKeyword(monta[i], true);
                }

                var doc  = aThis.printWindow.document;
                var body = doc.getElementsByTagName('body')[0];
                var img  = doc.createElement('img');

                if ((aThis.offset+1) % 2 == 1) {
                        body.appendChild(doc.createElement('div'));
//                      body.lastChild.style.clear = 'both';
                }
                var box = doc.createElement('div');
                box.appendChild(doc.createElement('div'));
                box.lastChild.appendChild(document.createTextNode(aThis.offset+1));
                body.lastChild.appendChild(box);

                var w = window.innerWidth;
                var h = window.innerHeight;
                var canvasW = parseInt(w * aThis.printSize);
                var canvasH = parseInt(h * aThis.printSize);

                aThis.printCanvas.width  = canvasW;
                aThis.printCanvas.height = canvasH;
                aThis.printCanvas.style.border = 'black solid medium';

                img.style.border = 'black solid medium';
                img.style.width  = canvasW+'px';
                img.style.height = canvasH+'px';

                box.style.margin = '1em';
                box.style.width  = parseInt(w * aThis.printSize)+'px';
                box.style.cssFloat  = ((aThis.offset+1) % 2 == 1) ? 'left' : 'right' ;

                try {
                        netscape.security.PrivilegeManager.enablePrivilege('UniversalBrowserRead');

                        var ctx = aThis.printCanvas.getContext('2d');
                        ctx.clearRect(0, 0, canvasW, canvasH);
                        ctx.save();
                        ctx.scale(aThis.printSize, aThis.printSize);
                        ctx.drawWindow(window, 0, 0, w, h, 'rgb(255,255,255)');
                        ctx.restore();
                        try {
                                if (aThis.imageType == 'jpeg')
                                        img.src = aThis.printCanvas.toDataURL('image/jpeg', 'quality=50');
                                else
                                        img.src = aThis.printCanvas.toDataURL('image/png', 'transparency=none');

                                box.appendChild(img);
                        }
                        catch(e) {
                                box.appendChild(aThis.printCanvas.cloneNode(true));
                                ctx = box.lastChild.getContext('2d');
                                ctx.clearRect(0, 0, canvasW, canvasH);
                                ctx.save();
                                ctx.scale(aThis.printSize, aThis.printSize);
                                ctx.drawWindow(window, 0, 0, w, h, 'rgb(255,255,255)');
                                ctx.restore();
                        }
                }
                catch(e) {
                        alert('Error: Failed to create a document for printing.\n\n------\n'+e);
                        aThis.stopPrint();
                        return;
                }

                if (aThis.offset == aThis.data.length-1) {
                        aThis.stopPrint();
                        aThis.printWindow.focus();
                }
                else {
                        aThis.forward();
                }
        },
        stopPrint : function()
        {
                window.clearInterval(this.printTimer);
                this.printTimer = null;
                this.isPrinting = false;
        },
        printSize   : 0.4,
        printTimer  : null,
        printWindow : null,
        printCanvas : null

};





var StrokeService = {
        className      : 'stroke-dot',
        dragStartDelta : 8,
        lineColor      : 'red',
        lineWidth      : 3,

        initialized : false,


        mode          : null,
        canvas        : null,
        canvasContext : null,
        startX        : -1,
        startY        : -1,

        init : function(aCanvas)
        {
                this.initialized = true;

                this.canvas = aCanvas;

                var canvas = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
                this.canvas.appendChild(canvas);
                if (!('getContext' in canvas) || !canvas.getContext) {
                        this.canvas.removeChild(canvas);
                        this.mode = 'box';
                }
                else {
                        this.canvas        = canvas;
                        this.canvasContext = this.canvas.getContext('2d');
                        this.mode          = 'canvas';
                }

                document.documentElement.addEventListener('PresentationRedraw', this, false);
                window.addEventListener('resize', this, false);
                this.canvas.addEventListener('mouseup',   this, false);
                this.canvas.addEventListener('mousedown', this, false);
                this.canvas.addEventListener('mousemove', this, false);

                this.canvas.addEventListener('click', this, false);
                this.canvas.addEventListener('dblclick', this, false);

                this.clear();
        },

        destroy : function()
        {
                document.documentElement.removeEventListener('PresentationRedraw', this, false);
                window.removeEventListener('resize', this, false);
                this.canvas.removeEventListener('mouseup', this, false);
                this.canvas.removeEventListener('mousedown', this, false);
                this.canvas.removeEventListener('mousemove', this, false);
                this.canvas.removeEventListener('click', this, false);

                this.cliclableNodesManager = null;
                this.canvas = null;

                this.initialized = false;
        },



        handleEvent : function(aEvent)
        {
                switch(aEvent.type)
                {
                        default:
                                break;

                        case 'mouseup':
                                this.finish(aEvent);
                                this.startX = -1;
                                this.startY = -1;
                                window.setTimeout('StrokeService.preventToSendClickEvent = false', 10);
                                break;

                        case 'mousedown':
                                if (this.startX < 0) {
                                        this.startX = aEvent.clientX;
                                        this.startY = aEvent.clientY;
                                }
                                break;

                        case 'mousemove':
                                if (this.startX > -1 && !this.active) {
                                        if (Math.abs(this.startX-aEvent.clientX) > Math.abs(this.dragStartDelta) ||
                                                Math.abs(this.startY-aEvent.clientY) > Math.abs(this.dragStartDelta)) {
                                                this.start(aEvent, this.startX, this.startY);
                                                this.preventToSendClickEvent = true;
                                        }
                                }
                                else
                                        this.trace(aEvent);

                                break;

                        case 'PresentationRedraw':
                        case 'resize':
                                this.clear();
                                break;

                        case 'click':
                                if (this.preventToSendClickEvent) {
                                        aEvent.stopPropagation();
                                        aEvent.preventCapture();
                                        aEvent.preventDefault();
                                        aEvent.preventBubble();
                                        this.preventToSendClickEvent = false;
                                }
                                else if (this.cliclableNodesManager && this.cliclableNodesManager.clickableNodes) {
                                        var nodes = this.cliclableNodesManager.clickableNodes;
                                        var max = nodes.length;
                                        var x, y, width, height
                                        for (var i = 0; i < max; i++)
                                        {
                                                if (nodes[i].boxObject) {
                                                        x      = nodes[i].boxObject.x;
                                                        y      = nodes[i].boxObject.y;
                                                        width  = nodes[i].boxObject.width;
                                                        height = nodes[i].boxObject.height;
                                                }
                                                else {
                                                        x      = nodes[i].offsetLeft;
                                                        y      = nodes[i].offsetTop;
                                                        width  = nodes[i].offsetWidth;
                                                        height = nodes[i].offsetHeight;
                                                }
                                                if (aEvent.clientX < x ||
                                                        aEvent.clientX > x+width ||
                                                        aEvent.clientY < y ||
                                                        aEvent.clientY > y+height)
                                                        continue;

                                                var event = document.createEvent('MouseEvents');
                                                event.initMouseEvent(
                                                        aEvent.type, aEvent.canBubble, aEvent.cancelable, aEvent.view,
                                                        aEvent.detail,
                                                        aEvent.screenX, aEvent.screenY, aEvent.clientX, aEvent.clientY,
                                                        aEvent.ctrlKey, aEvent.altKey, aEvent.shiftKey, aEvent.metaKey,
                                                        aEvent.button,
                                                        aEvent.relatedTarget
                                                );
                                                nodes[i].dispatchEvent(event);
                                                break;
                                        }
                                }
                                break;
                }
        },
        preventToSendClickEvent : false,



        start : function(aEvent, aX, aY)
        {
                this.active = true;
                this.trace(aEvent, aX, aY);
        },

        finish : function(aEvent)
        {
                if (!this.active) return;
                this.trace(aEvent);
                this.finishStroke();
        },

        trace : function(aEvent, aX, aY)
        {
                if (!this.active) return;
                this.addPoint((aX === void(0) ? aEvent.clientX : aX ), (aY === void(0) ? aEvent.clientY : aY ));
        },


        finishStroke : function()
        {
                this.active = false;
                this.lastX = -1;
                this.lastY = -1;
        },


        addPoint : function(aX, aY)
        {
                if (this.lastX != -1)
                        this.drawLine(this.lastX, this.lastY, aX, aY);
                else
                        this.drawDot(aX, aY);

                this.lastX = aX;
                this.lastY = aY;
        },



        clear : function()
        {
                this.active = false;
                this.lastX = -1;
                this.lastY = -1;

                if (this.mode == 'canvas') {
                        if (this.canvas.lastWindowWidth != window.innerWidth ||
                                this.canvas.lastWindowHeight != window.innerHeight) {
                                this.canvas.width  = this.canvas.parentNode.boxObject.width-2;
                                this.canvas.height = this.canvas.parentNode.boxObject.height-2;

                                this.canvas.lastWindowWidth  = window.innerWidth;
                                this.canvas.lastWindowHeight = window.innerHeight;
                        }
                        this.canvasContext.clearRect(0, 0, this.canvas.width, this.canvas.height);
                        this.canvasContext.strokeStyle = this.lineColor;
                        this.canvasContext.lineWidth   = this.lineWidth;
                }
                else {
                        var dotes = this.canvas.getElementsByAttribute('class', this.className);
                        if (!dotes.length) return;

                        var range = document.createRange();
                        range.selectNodeContents(this.canvas);
                        range.setStartBefore(dotes[0]);
                        range.setEndAfter(dotes[dotes.length-1]);
                        range.deleteContents();
                        range.detach();
                }
        },

        drawDot : function(aX, aY, aParent)
        {
                if (this.mode == 'canvas') {
                        this.canvasContext.strokeRect(aX, aY, 0, 0);
                        this.canvasContext.stroke();
                }
                else {
                        var dot = document.createElement('spacer');
                        dot.setAttribute('style', 'left:'+aX+'px; top:'+aY+'px');
                        dot.setAttribute('class', this.className);
                        (aParent || this.canvas).appendChild(dot);
                }
        },
        drawLine : function(aX1, aY1, aX2, aY2)
        {
                if (aX1 == aX2 && aY1 == aY2) return;


                if (this.mode == 'canvas') {
                        this.canvasContext.beginPath();
                        this.canvasContext.moveTo(aX1, aY1);
                        this.canvasContext.lineTo(aX2, aY2);
/*
                        this.canvasContext.bezierCurveTo(
                                parseInt(aX1+((aX2-this.lastX)*0.3)), parseInt(aY1+((aY2-this.lastY)*0.3)),
                                parseInt(aX1+((aX2-this.lastX)*0.6)), parseInt(aY1+((aY2-this.lastY)*0.6)),
                                aX2, aY2
                        );
*/
                        this.canvasContext.closePath();
                        this.canvasContext.stroke();
                }
                else {
                        var x_move = aX2 - aX1;
                        var y_move = aY2 - aY1;
                        var x_diff = x_move < 0 ? 1 : -1;
                        var y_diff = y_move < 0 ? 1 : -1;

                        var fragment = document.createDocumentFragment();
                        if (Math.abs(x_move) >= Math.abs(y_move)) {
                                for (var i = x_move; i != 0; i += x_diff)
                                        this.drawDot(aX2 - i, aY2 - Math.round(y_move * i / x_move), fragment);
                        }
                        else {
                                for (var i = y_move; i != 0; i += y_diff)
                                        this.drawDot(aX2 - Math.round(x_move * i / y_move), aY2 - i, fragment);
                        }
                        this.canvas.appendChild(fragment);
                }
        }
};





var StrokablePresentationService = {

        id : 'stroke-canvas-box',

        strokeService         : null,
        cliclableNodesManager : null,
        canvasContainer       : null,
        canvas                : null,

        autoStart : false,

        init : function(aPresentation, aStrokeService)
        {
                this.cliclableNodesManager = aPresentation;
                this.strokeService         = aStrokeService;
                this.canvasContainer       = document.getElementById('canvas').firstChild;
                this.check = document.getElementById('penButton');

                document.documentElement.addEventListener('StartDragOnCanvas', this, false);
                document.documentElement.addEventListener('PresentationRedraw', this, false);
        },

        toggle : function(aEnable)
        {
                if (aEnable)
                        this.start();
                else
                        this.end();
        },

        start : function()
        {
                if (!this.strokeService || !this.canvasContainer) return;

                this.strokeService.cliclableNodesManager = this.cliclableNodesManager;
                var box = document.createElement('vbox');
                box.setAttribute('flex', 1);
                box.setAttribute('id', this.id);
                this.canvas = this.canvasContainer.appendChild(box);
                this.strokeService.init(this.canvas);

                this.canvas.addEventListener('dblclick', this, false);
        },

        end : function()
        {
                this.canvas.removeEventListener('dblclick', this, false);

                this.strokeService.destroy();
                this.canvasContainer.removeChild(this.canvas);
                this.canvas = null;
        },

        handleEvent : function(aEvent)
        {
                switch (aEvent.type)
                {
                        default:
                                break;

                        case 'StartDragOnCanvas':
                                if (!this.check.checked) {
                                        this.toggleCheck();
                                        this.strokeService.startX = Presentation.dragStartX;
                                        this.strokeService.startY = Presentation.dragStartY;

                                        this.autoStart = true;
                                }
                                break;

                        case 'PresentationRedraw':
                                if (this.autoStart && this.check.checked) {
                                        this.autoStart = false;
                                        this.toggleCheck();
                                }
                                break;

                        case 'dblclick':
                                if (this.canvas)
                                        this.end();
                                break;
                }
        },

        toggleCheck : function()
        {
                var enable = !this.check.checked;
                this.toggle(enable);
                this.check.checked = enable;

                this.autoStart = false;
        }

};




function init()
{
        window.removeEventListener('load', init, false);

        Presentation.init();
        StrokablePresentationService.init(Presentation, StrokeService);
}
window.addEventListener('load', init, false);




// http://ecmanaut.blogspot.com/2006/03/e4x-and-dom.html


function importE4XNode( e4x, doc, aDefaultNS )
{
  aDefaultNS = aDefaultNS || XHTMLNS;
  var root, domTree, importMe;
  this.Const = this.Const || { mimeType: 'text/xml' };
  this.Static = this.Static || {};
  this.Static.parser = this.Static.parser || new DOMParser;
  eval('root = <testing xmlns="'+aDefaultNS+'" />;');
  root.test = e4x;
  domTree = this.Static.parser.parseFromString( root.toXMLString(),
           this.Const.mimeType );
  importMe = domTree.documentElement.firstChild;
  while( importMe && importMe.nodeType != 1 )
    importMe = importMe.nextSibling;
  if( !doc ) doc = document;
  return importMe ? doc.importNode( importMe, true ) : null;
}

function appendE4XTo( e4x, node, doc, aDefaultNS )
{
  return node.appendChild( importE4XNode( e4x, (doc || node.ownerDocument), aDefaultNS ) );
}

function setE4XContent( e4x, node, aDefaultNS )
{
  while( node.firstChild )
    node.removeChild( node.firstChild );
  appendE4XTo( e4x, node, aDefaultNS );
}

// importE4XNodeで得たノードツリーを埋め込むと、XULでバインディングが適用されないことがある。
// 遅延処理でこの問題を一部避けることができる（が、これでもまだダメな場合がある。menuとか。）
// とりあえずXULとSVGとXHTMLはいけた。MathMLはダメだった。
function importNodeTreeWithDelay(aNode, aParent, aDefaultNS, aFromTimeout)
{
        if (aFromTimeout) {
                importNodeTreeWithDelayTimers--;
        }

        var node;
        var delay = 1;
        switch (aNode.nodeType)
        {
                case Node.ELEMENT_NODE:
                        var ns = (aNode.namespaceURI || aDefaultNS);
                        node = document.createElementNS(ns, aNode.localName);
                        aParent.appendChild(node);

                        var attr = aNode.attributes;
                        for (var i = 0, maxi = attr.length; i < maxi; i++)
                                node.setAttribute(attr[i].name, attr[i].value);

                        if (ns == XULNS) delay = 1; else delay = 0;

                        var children = aNode.childNodes;
                        for (var i = 0, maxi = children.length; i < maxi; i++)
                                if (delay) {
                                        importNodeTreeWithDelayTimers++;
                                        window.setTimeout(importNodeTreeWithDelay, delay, children[i], node, aDefaultNS, true);
                                }
                                else
                                        importNodeTreeWithDelay(children[i], node, aDefaultNS);
                        break;

                default:
                        if (
                                aNode.nodeType == Node.TEXT_NODE &&
                                /^\s*$/.test(aNode.nodeValue) &&
                                (aNode.parentNode.namespaceURI || aDefaultNS) != XHTMLNS
                                )
                                return;
                        node = aParent.appendChild(aNode.cloneNode(true));
                        break;
        }

        var event = document.createEvent('Events');
        event.initEvent('CanvasContentAdded', true, true);
        node.dispatchEvent(event);

        return node;
}
var importNodeTreeWithDelayTimers = 0;

]]></script>

</page>
