Index: trunk/FACT++/www/viewer/index.css
===================================================================
--- trunk/FACT++/www/viewer/index.css	(revision 17700)
+++ trunk/FACT++/www/viewer/index.css	(revision 17700)
@@ -0,0 +1,31 @@
+.CodeMirror
+{
+    border: 0;
+    font-size:9pt
+}
+
+.cm-s-blackboard.CodeMirror
+{
+    background: #000000;
+    color: #F8F8F8;
+}
+
+.myaccordion .ui-accordion-header
+{
+    background-color: #ccc;  
+    margin: 0px;
+    font-size:10pt;
+    padding-left: 25px;
+    padding-right: 25px;
+    padding-top: 0px;
+    padding-bottom: 0px;
+}
+
+.myaccordion .ui-accordion-content
+{
+    color: #000;
+    font-size: 10pt; 
+    padding-top: 1ex;
+    padding-bottom: 1em;
+    padding-left: 2ex;
+}
Index: trunk/FACT++/www/viewer/index.html
===================================================================
--- trunk/FACT++/www/viewer/index.html	(revision 17700)
+++ trunk/FACT++/www/viewer/index.html	(revision 17700)
@@ -0,0 +1,392 @@
+<!DOCTYLE HTML>
+<html>
+<head>
+    <link rel="stylesheet" href="jquery-ui-1.10.4.custom/css/smoothness/jquery-ui-1.10.4.custom.min.css"/>
+    <script src="jquery-2.1.0.min.js"></script>
+    <script src="jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>
+    <!--<link rel="stylesheet" href="jquery-ui-1.10.4/css/ui-lightness/jquery-ui-1.10.4.min.css"-->
+    <!--<script src="zoomooz-1.1.9/jquery.zoomooz.min.js"></script>-->
+    <script src="codemirror-4.1/lib/codemirror.js"></script>
+    <link rel="stylesheet" href="codemirror-4.1/lib/codemirror.css"/>
+    <link rel="stylesheet" href="codemirror-4.1/theme/myblackboard.css"/>
+    <!--<link rel="stylesheet" href="codemirror-4.1/theme/blackboard.css">-->
+    <!--<link rel="stylesheet" href="codemirror-4.1/theme/3024-night.css">-->
+    <link rel="stylesheet" href="codemirror-4.1/addon/display/fullscreen.css"/>
+    <link rel="stylesheet" href="codemirror-4.1/addon/dialog/dialog.css"/>
+    <link rel="stylesheet" href="codemirror-4.1/addon/fold/foldgutter.css"/>
+    <script src="codemirror-4.1/addon/selection/active-line.js"></script>
+    <script src="codemirror-4.1/addon/display/fullscreen.js"></script>
+    <script src="codemirror-4.1/addon/edit/matchbrackets.js"></script>
+    <script src="codemirror-4.1/addon/search/search.js"></script>
+    <script src="codemirror-4.1/addon/search/searchcursor.js"></script>
+    <script src="codemirror-4.1/addon/dialog/dialog.js"></script>
+    <script src="codemirror-4.1/addon/hint/show-hint.js"></script>
+    <script src="codemirror-4.1/addon/hint/javascript-hint.js"></script>
+    <script src="codemirror-4.1/addon/fold/foldcode.js"></script>
+    <script src="codemirror-4.1/addon/fold/foldgutter.js"></script>
+    <script src="codemirror-4.1/addon/fold/brace-fold.js"></script>
+    <script src="codemirror-4.1/addon/fold/comment-fold.js"></script>
+    <script src="codemirror-4.1/mode/javascript/javascript.js"></script>
+    <script src="flot-0.8.3/jquery.flot.min.js"></script>
+<!--    <script src="flot-0.8.3/jquery.flot.navigate.js"></script>-->
+    <script src="flot-0.8.3/jquery.flot.selection.min.js"></script>
+    <script src="flot-0.8.3/jquery.flot.symbol.min.js"></script>
+    <script src="flot-0.8.3/jquery.flot.resize.js"></script>
+<!--    <script type="text/javascript" src="jqplot-1.0.8/jquery.min.js"></script>
+    <script type="text/javascript" src="jqplot-1.0.8/jquery.jqplot.min.js"></script>
+    <script type="text/javascript" src="jqplot-1.0.8/plugins/jqplot.canvasTextRenderer.min.js"></script>
+    <script type="text/javascript" src="jqplot-1.0.8/plugins/jqplot.canvasAxisLabelRenderer.min.js"></script>
+    <script type="text/javascript" src="jqplot-1.0.8/plugins/jqplot.highlighter.min.js"></script>
+    <script type="text/javascript" src="jqplot-1.0.8/plugins/jqplot.cursor.min.js"></script>
+    <script type="text/javascript" src="jqplot-1.0.8/plugins/jqplot.dateAxisRenderer.min.js"></script>
+    <link rel="stylesheet" type="text/css" href="jqplot-1.0.8/jquery.jqplot.css" />-->
+    <script src="index.js"></script>
+    <link rel="stylesheet" href="index.css"/>
+</head>
+
+<body>
+
+<div class="myaccordion" id="accordion5">
+   <h3><a href="#">Editor 1 (proc)</a></h3>
+   <div id="editorcontainer1fake" style="position:absolute;border:0;opacity:0"></div>
+</div>
+<div id="editorcontainer1" style="z-index:600;">
+   <form action="getevent.php" method="post" style="margin-bottom:0px">
+      <div id="textcontainer1">
+         <textarea id="editor1" name="editor1" type="textarea">
+return $.data[pixel];
+         </textarea>
+      </div>
+      <div class="ui-widget-content" style="background:#333;color:#eef;border-top-width:0;padding-top:2px;">
+         <input type="submit" value="Save"></input>
+         <input type="file" name="files[]" id="selectfile1"></input><output id="file1"></output>
+      </div>
+    </form>
+</div>
+
+<div class="myaccordion" id="accordion1">
+   <h3><a href="#">Editor 2 (main)</a></h3>
+   <div id="editorcontainer2fake" style="position:absolute;border:0;opacity:0"></div>
+</div>
+<div id="editorcontainer2">
+   <form action="getevent.php" method="post" style="margin-bottom:0px">
+      <div id="textcontainer2"> 
+         <textarea id="editor2" name="editor2" type="textarea">
+print("test\n2nd line\n");
+
+// The four first values of pixel 1
+var arr = [ $.data[1][0], $.data[1][1], $.data[1][2], $.data[1][3] ];
+print("return [ "+arr+" ]\n");
+
+// Get the maximum sample from each pixel
+var rc = [[],[]];
+for (var p=0; p<$.event.numPix; p++)
+{
+    var max = 0;
+    var idx = 0;
+    for (var s=5; s<$.event.numRoi-50; s++)
+    {
+        // spike suppression
+        var h = ($.data[p][s]-$.data[p][s-1]) + ($.data[p][s]-$.data[p][s+1])
+
+        if ($.data[p][s]>max && h<20)
+        {
+            max = $.data[p][s];
+            idx = s;
+        }
+    }
+    rc[0][p]=max;
+    rc[1][p]=idx;
+}
+
+return rc;
+         </textarea>
+      </div>
+      <div class="ui-widget-content" style="background:#333;color:white;border-top-width:0;padding-top:2px;">
+         <input type="submit" value="Save"></input>
+         <input type="file" name="files[]" id="selectfile2"></input><output id="file2"></output>
+      </div>
+   </form>
+</div>
+
+<div class="myaccordion" id="accordion" style="margin-bottom:1ex">
+   <h3><a  style="color:red" href="#">Runtime error</a></h3>
+   <div>
+      <pre id="error" style="color:red"></pre>
+   </div>
+   <h3><a href="#">Console</a></h3>
+   <div>
+      <pre style="color:green" id="console"></pre>
+   </div>
+   <h3><a href="#">Debug</a></h3>
+   <div id="debug">
+   </div>
+</div>
+
+<div class="ui-widget-content" style="background:#eee;margin-top:-10px">
+   <input id="submit" type="button" onclick="onSubmit();" value="Submit"></input>
+   <span style="float:right">
+      &nbsp;Run file: 
+      <input id="file"></input>
+   </span>
+   &nbsp;Event: 
+   <input id="event" style="text-align:right" type="number" onchange="onEvent();"  value="0" min="0" max="0"    step="1"></input>/<span id="numevents">---</span>
+   &nbsp;Pixel: 
+   <input id="pixel" style="text-align:right;width:55px;" type="number" onchange="onPixel();"  value="0" min="0" max="1439" step="1"></input>
+   &nbsp;CBPX:
+   <input id="cbpx-c" style="text-align:right;width:35px;" type="number" onchange="onCBPX();"  value="1" min="0" max="3" step="1"></input>
+   <input id="cbpx-b" style="text-align:right;width:35px;" type="number" onchange="onCBPX();"  value="0" min="0" max="9" step="1"></input>
+   <input id="cbpx-p" style="text-align:right;width:35px;" type="number" onchange="onCBPX();"  value="3" min="0" max="3" step="1"></input>
+   <input id="cbpx-x" style="text-align:right;width:35px;" type="number" onchange="onCBPX();"  value="6" min="0" max="8" step="1"></input>
+   =
+   <input id="cbpx"   style="text-align:right;width:55px;" type="number" onchange="onHW();"  value="393" min="0" max="1439" step="1"></input>
+</div>
+
+<div class="myaccordion" id="accordion2">
+   <h3><a href="#">Camera display</a></h3>
+</div>
+<div id="cameracontainer" class="ui-widget-content">
+
+   <table width="100%">
+      <colgroup>
+         <col style="width:50%;">
+         <col style="width:50%;">
+      </colgroup>
+      <tr>
+         <td>
+            Min: 
+            <input id="cameramin1"   style="text-align:right;width:100px;" type="number"   onchange="onCameraMinMax(1);"  value="0" disabled="true"></input>
+            <input id="cameraminon1" type="checkbox" onclick="onCameraMinMaxOn(1);" checked="true"></input>
+            Max: 
+            <input id="cameramax1"   style="text-align:right;width:100px;" type="number"   onchange="onCameraMinMax(1);"  value="0" disabled="true"></input>
+            <input id="cameramaxon1" type="checkbox" onclick="onCameraMinMaxOn(1);" checked="true"></input>
+         </td>
+         <td style="text-align:right">
+            Min: 
+            <input id="cameramin2"   style="text-align:right;width:100px;" type="number"   onchange="onCameraMinMax(2);"  value="0" disabled="true"></input>
+            <input id="cameraminon2" type="checkbox" onclick="onCameraMinMaxOn(2);" checked="true"></input>
+            Max: 
+            <input id="cameramax2"   style="text-align:right;width:100px;" type="number"   onchange="onCameraMinMax(2);"  value="0" disabled="true"></input>
+            <input id="cameramaxon2" type="checkbox" onclick="onCameraMinMaxOn(2);" checked="true"></input>
+         </td>
+         <td>
+         </td>
+      </tr>
+   </table>
+
+   <table id="table" width="100%" border="0" style="border:0;margin:0;padding:0;">
+      <tr style="margin:0;padding:0;">
+         <td style="margin:0;padding:0;">
+	    <table id="col1" border="0" style="margin:0;padding:0;">
+	       <tr style="margin:0;padding:0;"><td style="margin:0;padding:0;" id="cont1"></tr>
+	       <tr style="margin:0;padding:0;"><td style="margin:0;padding:0;" id="cont3"><canvas id="camera3" width="1" height="1"></canvas></td></tr>
+            </table>
+	 </td>
+	 <td style="margin:0;padding:0;" id="center"><canvas id="camera1" width="1" height="1"></canvas></td>
+	 <td style="margin:0;padding:0;">
+	    <table id="col3" border="0" style="margin:0;padding:0;">
+	       <tr style="margin:0;padding:0;"><td style="margin:0;padding:0;" id="cont2"><canvas id="camera2" width="1" height="1"></canvas></td></tr>
+	       <tr style="margin:0;padding:0;"><td style="margin:0;padding:0;" id="cont4"><canvas id="camera4" width="1" height="1"></canvas></td></tr>
+	    </table>
+	 </td>
+      </tr>
+   </table>
+
+   <table width="100%">
+      <colgroup>
+         <col style="width:40%;">
+         <col style="width:20%;">
+         <col style="width:40%;">
+      </colgroup>
+      <tr>
+         <td>
+            Min: 
+            <input id="cameramin3"   style="text-align:right;width:100px;" type="number"   onchange="onCameraMinMax(3);"  value="0" disabled="true"></input>
+            <input id="cameraminon3" type="checkbox" onclick="onCameraMinMaxOn(3);" checked="true"></input>
+            Max: 
+            <input id="cameramax3"   style="text-align:right;width:100px;" type="number"   onchange="onCameraMinMax(3);"  value="0" disabled="true"></input>
+            <input id="cameramaxon3" type="checkbox" onclick="onCameraMinMaxOn(3);" checked="true"></input>
+         </td>
+         <td style="text-align:center;">
+            Pixel value: 
+            <input id="value" type="text" readonly="true"  value="0" style="text-align:right;width:100px"></input>
+         </td>
+         <td style="text-align:right">
+            Min: 
+            <input id="cameramin4"   style="text-align:right;width:100px;" type="number"   onchange="onCameraMinMax(4);"  value="0" disabled="true"></input>
+            <input id="cameraminon4" type="checkbox" onclick="onCameraMinMaxOn(4);" checked="true"></input>
+            Max: 
+            <input id="cameramax4"   style="text-align:right;width:100px;" type="number"   onchange="onCameraMinMax(4);"  value="0" disabled="true"></input>
+            <input id="cameramaxon4" type="checkbox" onclick="onCameraMinMaxOn(4);" checked="true"></input>
+         </td>
+      </tr>
+   </table>
+</div>
+
+<div class="myaccordion" id="accordion3">
+   <h3><a href="#">Waveform</a></h3>
+</div>
+<div id="waveformcontainer"  class="ui-widget-content">
+   <div id="waveform" style="width:100%;height:300px;"></div>
+   <div style="text-align:center;margin-bottom:2px">
+      Min: 
+      <input id="waveformmin"   style="text-align:right"type="number"   onchange="onWaveformMinMax();"  value="0" disabled="true"></input>
+      <input id="waveformminon" type="checkbox" onclick="onWaveformMinMaxOn();" checked="true"></input>
+      Max: 
+      <input id="waveformmax"   style="text-align:right"type="number"   onchange="onWaveformMinMax();"  value="0" disabled="true"></input>
+      <input id="waveformmaxon" type="checkbox" onclick="onWaveformMinMaxOn();" checked="true"></input>
+   </div>
+</div>
+
+<div id='tooltip' style="position:absolute;display:none;border:1px solid #fdd;padding;2px;background-color:#fee;opacity:0.8"></div>
+
+<div class="myaccordion" id="accordion4">
+   <h3><a href="#">Help</a></h3>
+</div>
+<div id="helpcontainer" class="ui-widget-content" style="padding-left:40px;padding-right:40px;padding-bottom:40px;">
+<H1>HELP</H1>
+
+<H3>How does it work?</H3>
+When you submit a javascript to the server, it will be executed
+on a sandbox on the server. Before the event is loaded from a file
+and made available within the sandbox. Generally, the sandbox
+can easily be enhanced, e.g. with algorithms available in php.
+This can be done on request. After execution, the result returned
+by the script is then displayed in the camera display.
+
+<H3>Why Javascript?</H3>
+Simply for security reasons. To avoid tranferring the event to the client's
+browser, the script must be executed on the server side. Also Python
+and PHP offer the possibility to execute scripts within a program,
+they do not have any security feature to avoid for exmple access to the
+local disk. The V8 Javascript engine however, is very limited in
+functionality and therefore ideally suited for a sandboxed and
+therefore safe excution on the server. Any other solution is welcome.
+
+<H3>Javascript hints!</H3>
+Please note that in Javascript only basic data typed (number, etc)
+are copied in an assignment. In all other cases, only a reference
+is copied. For example, the following code snippet does not return 2
+as you might expect but 7!
+<pre><!--<textarea id="code0" style="height:auto;overflow-x:auto;overflow-y:hidden;">-->
+var arr = [ 0, 1, 2, 3 ];
+var cpy = arr;
+cpy[2] = 7;
+print(arr[2]);
+</pre><!--</textarea>-->
+To avoid that, the global namespace implements a clone function.
+For exmaple, the following code snippet will return the expected output:
+<pre><!--<textarea id="code1">-->
+var arr = [ 0, 1, 2, 3 ];
+var cpy = $.clone(arr);
+cpy[2] = 7;
+print(arr[2]);
+</pre><!--</textarea>-->
+
+<H3>The global object $</H3>
+
+The environment provides a global object (namesapce) called $ with the following members:
+<table>
+<tr><td>$.event</td><td>The event header information as obtained from the file</td></tr>
+<tr><td>$.event.numEvents</td><td>Number of events in the file</td></tr>
+<tr><td>$.event.numRoi</td><td>Number of samples per pixel</td></tr>
+<tr><td>$.event.numPix</td><td>Number of pixels</td></tr>
+<tr><td>$.event.eventNumber</td><td>Event number</td></tr>
+<tr><td>$.event.triggerNumber</td><td>Trigger number</td></tr>
+<tr><td>$.event.triggerType</td><td>Trigger type</td></tr>
+<tr><td>$.event.unixTime[2]</td><td>Timestamp when the event arrived at the event builder</td></tr>
+<tr><td>$.data[numPix][numRoi]</td><td>Theevent data</td></tr>
+</table>
+
+<H3>Editor 1 (proc)</H3>
+
+<I>proc</I> can return an array with numRoi entries or an array with up to 
+four sub-arrays each of nRoi entries. They will be displayed in the graph.
+The chosel pixel is available as <I>pixel</I>.
+The most simple is to just return the selected pixel data unprocssed:
+<pre>
+return $.data[pixel];
+</pre>
+
+<H3>Editor 2 (main)</H3>
+<I>main</I> should return extracted data per pixel. As <I>proc</I> it can
+return a single array or an array with up to four sub-arrays each with
+numPix entries. They are displayed in the camera displays. The function
+implemented as <I>proc</I> is accessible as <I>proc(i)</I> with <I>i</I> 
+being available in <I>proc</I> as <I>pixel</I>. A very simple extractor
+could be to return just the sample at the trigger position
+<pre>
+var rc = [];
+for (var i=0; i<$.event.numPix; i++)
+    rc[i] = $.data[i][60];
+return rc;
+</pre>
+or taking the data pre-procesed by the code of the <I>proc</I>-function:
+<pre>
+var rc = [];
+for (var i=0; i<$.event.numPix; i++)
+    rc[i] = proc(i)[60];
+return rc;
+</pre>
+
+Note that the precise access to the result of proc might depend on what
+exactly is retruned by proc (an array, or an array with sub-arrays).
+
+<H3>The editor</H3>
+Key bindings of the editor can be found at 
+<A HREF="http://codemirror.net/doc/manual.html#commands">Codemirror</A>.
+In addition the following binding are defined: Tab - Auto indent;
+F11 - Switch to fullscreen; Esc - In fullscreen mode to 
+leave fullscreen; Ctrl-r - replace; Ctrl-y - Delete the line under the cursor;
+Ctrl-. (dot) - Fold code; Ctrl-Down (cursor down) - Autocomplete.
+
+<H3>How to change the file?</H3>
+Start typing the date in the filename field. The available files 
+will be filtered as you type. To select a file you need to select
+it fom the pull down.
+
+<H3>How to change the displayed pixel?</H3>
+Enter the pixel number on the corresponding field or its hwardware 
+address. As soon as you acknowledge your change (e.g. remove focus 
+by clicking somewhere else) the pixel contents gets displayed.
+
+<H3>How to Save/Load a script?</H3>
+To load a file choose a file fro your local hard drive via the
+file selection dialog. To save the contents of the editor, press
+Save.
+
+<H3>Submit</H3>
+If you have changed the script and you want to run it on the current event
+press Submit. The Javascript will be executed for this event on
+the server.
+
+<!--<H3>Autosubmit</H3>
+If Autosumit is enabled, the script will be submitted each time
+the file name or the event number is changed and executed.-->
+
+<H3>Pixel value</H3>
+To display the value returned for a pixel just click on the pixel.
+The value will be displayed in the Pixel field. This will also
+display the corresponding waveform in the graph.
+
+<H3>Min/max values</H3>
+Min and max values for the plot and the graph can be determined
+automatically or manully. To set the to a fixed value, enable the
+min and/or max field and enter the value of your choice. It will
+survive changing files, events or pixel.
+
+<H3>Why is loading the page so slow?</H3>
+The total contents of the javascript libraries is about 1MB which
+all have to be treasferred to the browser. In addition the loading
+and processing of the event takes up to 1s so that the loading
+time of the page might be untypically slow. Once finished,
+the amaount of code to load can be decreased.
+
+<H3>How to unzoom the plot?</H3>
+Double click on the plot.
+
+</div>
+
+
+</body>
+</html>
Index: trunk/FACT++/www/viewer/index.js
===================================================================
--- trunk/FACT++/www/viewer/index.js	(revision 17700)
+++ trunk/FACT++/www/viewer/index.js	(revision 17700)
@@ -0,0 +1,1058 @@
+'use strict';
+
+// display 'Wait'
+
+var editor1;
+var editor2;
+var plot;
+
+function debug(txt)
+{
+    var dbg = document.getElementById("debug");
+    dbg.appendChild(document.createTextNode(txt));
+    dbg.appendChild(document.createElement("br"));
+}
+
+function setupAccordion(accordion, container, inactive)
+{
+    function onAccordion(event, ui)
+    {
+        if (ui.oldHeader.length==0)
+            $(container).slideDown(400);
+        else
+            $(container).slideUp(400);
+    }
+
+    var config = { collapsible: true };
+
+    if (inactive)
+    {
+        config.active = false;
+        $(container).hide();
+    }
+
+    var acc = $(accordion);
+
+    acc.accordion(config);
+    acc.on("accordionbeforeactivate", onAccordion);
+}
+
+function onResize(event, ui)
+{
+    if (!ui.size)
+        return;
+
+    $(event.target.id).css({marginRight:'2px'});
+
+    var editor = event.target.id=="textcontainer1" ? editor1 : editor2;
+
+    editor.setSize("100%", ui.size.height);
+    editor.refresh();
+}
+
+function onResizeCenter(event, ui)
+{
+    var w = document.getElementById("cameracontainer").clientWidth/4;
+
+    var offy = 0;
+    var offx = 5;
+
+    var cont = document.getElementById("center").childNodes[0];
+
+    var nn;
+    if (cont)
+    {
+        nn = parseInt(cont.id[cont.id.length-1]);
+
+        document.getElementById("camera"+nn).height=parseInt(w*2);
+        document.getElementById("camera"+nn).width=parseInt(w*2);
+    }
+
+    if (nn!=1)
+    {
+        document.getElementById("camera1").height=parseInt(w)-offy;
+        document.getElementById("camera1").width=parseInt(w)-offx;
+    }
+
+    if (nn!=2)
+    {
+        document.getElementById("camera2").height=parseInt(w)-offy;
+        document.getElementById("camera2").width=parseInt(w)-offx;
+    }
+
+    if (nn!=3)
+    {
+        document.getElementById("camera3").height=parseInt(w)-offy;
+        document.getElementById("camera3").width=parseInt(w)-offx;
+    }
+
+    if (nn!=4)
+    {
+        document.getElementById("camera4").height=parseInt(w)-offy;
+        document.getElementById("camera4").width=parseInt(w)-offx;
+    }
+
+    document.getElementById("center").width=parseInt(w*2);
+
+    document.getElementById("cont1").width=parseInt(w);
+    document.getElementById("cont2").width=parseInt(w);
+    document.getElementById("cont3").width=parseInt(w);
+    document.getElementById("cont4").width=parseInt(w);
+
+    document.getElementById("cont1").height=parseInt(w);
+    document.getElementById("cont2").height=parseInt(w);
+    document.getElementById("cont3").height=parseInt(w);
+    document.getElementById("cont4").height=parseInt(w);
+
+    drawFullCam("camera1");
+    drawFullCam("camera2");
+    drawFullCam("camera3");
+    drawFullCam("camera4");
+}
+
+function createEditor(textarea)
+{
+    var editor;
+
+    var config =
+    {
+        //value: "function myScript(){return 100;}\n",
+        mode:  { name: "text/typescript", globalVars: true },
+        lineNumbers: true,
+        styleActiveLine: true,
+        matchBrackets: true,
+        indentUnit: 4,
+        foldGutter: true,
+        gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
+        extraKeys: {
+            //"Ctrl-D": "duplicateLine",
+            //"Alt--": "goToBracket",
+            //"Ctrl-H": "findPrev",
+            "Ctrl-Down": "autocomplete",
+            "Tab": "indentAuto",
+            "Ctrl-Y": "deleteLine",
+            "Ctrl-.": function(cm) {
+                cm.foldCode(cm.getCursor());
+            },
+            "F11": function(cm) {
+                editor.setOption("fullScreen", !editor.getOption("fullScreen"));
+            },
+            "Ctrl-R": function(cm) {
+                editor.execCommand("replace");
+            },
+            "Esc": function(cm) {
+                if (editor.getOption("fullScreen")) editor.setOption("fullScreen", false);
+            },
+            "Enter": function(cm) {
+                editor.execCommand("indentAuto");
+                editor.execCommand("newlineAndIndent");
+            },
+        }
+
+    };
+
+    editor = CodeMirror.fromTextArea(document.getElementById(textarea), config);
+    editor.setOption("theme", "blackboard");
+
+    return editor;
+}
+
+function colorizeHTML(textarea)
+{
+    var config =
+    {
+        //value: "function myScript(){return 100;}\n",
+        mode:  { name: "text/typescript", globalVars: true },
+        readOnly: true,
+    };
+
+    CodeMirror.fromTextArea(document.getElementById(textarea), config);
+}
+
+function disableControls(disabled)
+{
+    $('#event').prop('disabled', disabled);
+    $('#pixel').prop('disabled', disabled);
+    $('#cbpx').prop('disabled', disabled);
+    $('#cbpx-c').prop('disabled', disabled);
+    $('#cbpx-b').prop('disabled', disabled);
+    $('#cbpx-p').prop('disabled', disabled);
+    $('#cbpx-x').prop('disabled', disabled);
+    $('#file').prop('disabled', disabled);
+}
+
+function onReady()
+{
+    //$('input,select').keypress(function(event) { return event.keyCode != 13; });
+
+    //colorizeHTML("code0");
+    //colorizeHTML("code1");
+
+    $("#accordion").accordion({collapsible:true,active:false,heightStyle:'content'});
+    $("#accordion").find('h3').filter(':contains(Runtime)').hide();
+    if (location.href.search('debug')==-1)
+        $("#accordion").find('h3').filter(':contains(Debug)').hide();
+
+    $("#textcontainer1").resizable({handles:"s",autoHide:true,});
+    $("#textcontainer1").on("resize", onResize);
+
+    $("#textcontainer2").resizable({handles:"s",autoHide:true,});
+    $("#textcontainer2").on("resize", onResize);
+
+    $("#cameracontainer").on("resize", onResizeCenter);
+    onResizeCenter();
+
+    $("#cont1").click(onClickNew);
+    $("#cont2").click(onClickNew);
+    $("#cont3").click(onClickNew);
+    $("#cont4").click(onClickNew);
+
+    $("#camera1").click(onClick);
+    $("#camera2").click(onClick);
+    $("#camera3").click(onClick);
+    $("#camera4").click(onClick);
+
+    editor1 = createEditor("editor1");
+    editor2 = createEditor("editor2");
+
+    setupAccordion('#accordion5', '#editorcontainer1', true);
+    setupAccordion('#accordion1', '#editorcontainer2');
+
+    $('#accordion5').on("accordionactivate", function() { $('#editorcontainer1fake').hide(); editor1.refresh();  });
+    $('#accordion1').on("accordionactivate", function() { $('#editorcontainer2fake').hide(); editor2.refresh();  });
+
+    setupAccordion('#accordion2', '#cameracontainer');
+    setupAccordion('#accordion3', '#waveformcontainer');
+    setupAccordion('#accordion4', '#helpcontainer', true);
+
+    $("#selectfile1").on('change', onFile);
+    $("#selectfile2").on('change', onFile);
+
+    $(document).ajaxStart(function() { disableControls(true) }).ajaxStop(function() { disableControls(false); });
+
+
+    $.ajax({
+        type:    "POST",
+        cache:   false,
+        url:     "index.php",
+        success: onFilelistReceived,
+        error:   function(xhr) { if (xhr.status==0) alert("ERROR[0] - Request failed!"); else alert("ERROR[0] - "+xhr.statusText+" ["+xhr.status+"]"); }
+    });
+}
+
+function onFileSelect(event, ui)
+{
+    onSubmit(ui.item.value);
+}
+
+function onFilelistReceived(result)
+{
+    var dbg = document.getElementById("debug");
+
+    var pre = document.createElement("pre");
+    pre.appendChild(document.createTextNode(rc));
+    dbg.appendChild(pre);
+
+    var rc;
+    try
+    {
+        rc = JSON.parse(result);
+    }
+    catch (e)
+    {
+        alert("ERROR[0] - Decoding answer:\n"+e);
+        return;
+    }
+
+    $("#file").autocomplete({source: rc, select: onFileSelect} );
+    document.getElementById("file").value = "2014/04/17-181";
+
+    onSubmit("2014/04/17-181");
+}
+
+function setZoom(xfrom, xto, yfrom, yto)
+{
+    var xaxis = plot.getXAxes()[0];
+    var yaxis = plot.getYAxes()[0];
+
+    if (xfrom!==undefined)
+        xaxis.options.min = xfrom;
+    if (xto!==undefined)
+        xaxis.options.max = xto;
+
+    if (yfrom!==undefined)
+        yaxis.options.min = yfrom;
+    if (yto!==undefined)
+        yaxis.options.max = yto;
+
+    plot.setupGrid();
+    plot.draw();
+    plot.clearSelection();
+}
+
+function processCameraData(id, data)
+{
+    var canv = document.getElementById(id);
+
+    canv.dataAbs = [ ];
+    for (var i=0; i<1440; i++)
+        if (data[i]!==undefined && data[i]!==null)
+            canv.dataAbs[i] = data[i];
+
+    canv.min = Math.min.apply(Math, canv.dataAbs.filter(function(e){return !isNaN(e)}));
+    canv.max = Math.max.apply(Math, canv.dataAbs.filter(function(e){return !isNaN(e)}));
+
+    canv.dataRel = [ ];
+    for (var i=0; i<1440; i++)
+        if (data[i]!==undefined && data[i]!==null)
+            canv.dataRel[i] = (data[i]-canv.min)/canv.max;
+
+    var n = id[id.length-1];
+
+    if (document.getElementById("cameraminon"+n).checked)
+        document.getElementById("cameramin"+n).value = canv.min;
+    if (document.getElementById("cameramaxon"+n).checked)
+        document.getElementById("cameramax"+n).value = canv.max;
+
+    onCameraMinMax(n);
+}
+
+
+function onDataReceived(rc)
+{
+    var dbg = document.getElementById("debug");
+
+    var pre = document.createElement("pre");
+    pre.appendChild(document.createTextNode(rc));
+    dbg.appendChild(pre);
+
+    try
+    {
+      rc = JSON.parse(rc);
+    }
+    catch (e)
+    {
+        alert("ERROR[1] - Decoding answer:\n"+e);
+        return;
+    }
+
+    document.getElementById("event").max = rc.event.numEvents;
+    var el = document.getElementById("numevents");
+    if (el.firstChild)
+        el.removeChild(el.firstChild);
+    el.appendChild(document.createTextNode(rc.event.numEvents));
+
+    if (rc.timeJs)
+    {
+        var err = document.getElementById("error");
+        while (err.lastChild)
+            err.removeChild(err.lastChild);
+
+        $("#accordion").accordion("option", "active", false);
+        $("#accordion").find('h3').filter(':contains(Runtime)').hide();
+
+        var con = document.getElementById("console");
+        while (con.lastChild)
+            con.removeChild(con.lastChild);
+    }
+
+
+    if (rc.err)
+    {
+        err.appendChild(document.createTextNode("Javascript runtime exception: "+rc.err.file+":"+rc.err.lineNumber));
+        err.appendChild(document.createTextNode("\n"));
+        err.appendChild(document.createTextNode(rc.err.sourceLine));
+        err.appendChild(document.createTextNode("\n"));
+        err.appendChild(document.createTextNode(rc.err.trace));
+
+        var editor = rc.err.file=="main" ? editor2 : editor1;
+        editor.setCursor(rc.err.lineNumber-1, 1);
+
+        $("#accordion").find('h3').filter(':contains(Runtime)').show();
+        $("#accordion").accordion("option", "active", 0);
+    }
+
+    var canv = document.getElementById("camera1");
+
+    if (rc.debug!==undefined)
+    {
+        con.appendChild(document.createTextNode(rc.debug));
+
+        debug("return "+JSON.stringify(rc.ret));
+        debug("");
+        debug("Execution times:");
+        debug("TimeJS="+(rc.timeJs*1000).toFixed(2)+"ms");
+    }
+
+    if (rc.ret!==undefined && Array.isArray(rc.ret))
+    {
+        if (!Array.isArray(rc.ret[0]))
+            processCameraData("camera1", rc.ret);
+        else
+            processCameraData("camera1", rc.ret[0]);
+
+        if (rc.ret.length>1)
+            processCameraData("camera2", rc.ret[1]);
+
+        if (rc.ret.length>2)
+            processCameraData("camera3", rc.ret[2]);
+
+        if (rc.ret.length>3)
+            processCameraData("camera4", rc.ret[3]);
+    }
+
+    if (canv.dataAbs && rc.event)
+        document.getElementById("value").value = canv.dataAbs[rc.event.pixel];
+
+    debug("TimePhp="+(rc.timePhp*1000).toFixed(1)+"ms");
+
+    if (Array.isArray(rc.waveform))
+    {
+        var waveform = document.getElementById("waveform");
+
+        var data = [[],[],[],[]];
+
+        var min = [];
+        var max = [];
+        if (Array.isArray(rc.waveform) && rc.waveform.length==rc.event.numRoi)
+        {
+            min.push(Math.min.apply(Math, rc.waveform));
+            max.push(Math.max.apply(Math, rc.waveform));
+
+            for (var i=0; i<rc.event.numRoi; i++)
+                data[0][i] = [ i, rc.waveform[i] ];
+        }
+
+        for (var j=0; j<4; j++)
+        {
+            var ref = rc.waveform[j];
+
+            if (Array.isArray(ref) && ref.length==rc.event.numRoi)
+            {
+                min.push(Math.min.apply(Math, ref));
+                max.push(Math.max.apply(Math, ref));
+
+                for (var i=0; i<rc.event.numRoi; i++)
+                    data[j][i] = [ i, ref ];
+
+            }
+        }
+
+        waveform.ymin = Math.min.apply(Math, min);
+        waveform.ymax = Math.max.apply(Math, max);
+        waveform.xmin = 0;
+        waveform.xmax = rc.event.numRoi;
+
+        if (document.getElementById("waveformminon").checked)
+            document.getElementById("waveformmin").value = waveform.ymin;
+        if (document.getElementById("waveformmaxon").checked)
+            document.getElementById("waveformmax").value = waveform.ymax;
+
+        var ymin = document.getElementById("waveformminon").checked ? waveform.ymin : parseInt(document.getElementById("waveformmin").value);
+        var ymax = document.getElementById("waveformmaxon").checked ? waveform.ymax : parseInt(document.getElementById("waveformmax").value);
+
+        var opts =
+        {
+           xaxis: {
+               min: waveform.xmin-1,
+               max: waveform.xmax+1,
+           },
+           yaxis: {
+               min: ymin-5,
+               max: ymax+5,
+           },
+           series: {
+               lines: {
+                   show: true
+               },
+               points: {
+                   show: true,
+                   symbol: 'cross',
+               }
+           },
+           selection: {
+               mode: "xy"
+           },
+           grid: {
+               hoverable: true,
+           }
+        };
+
+        plot = $.plot("#waveform", data, opts);
+
+        waveform = $('#waveform');
+        waveform.bind("plotselected", function (event, ranges)
+                      {
+                          setZoom(ranges.xaxis.from, ranges.xaxis.to,
+                                  ranges.yaxis.from, ranges.yaxis.to);
+                      });
+
+        waveform.dblclick(function ()
+                          {
+                              var waveform = document.getElementById("waveform");
+                              setZoom(waveform.xmin-1, waveform.xmax+1, waveform.ymin-5, waveform.ymax+5);
+                          });
+        waveform.bind("plothover", function (event, pos, item)
+                      {
+                          if (!item)
+                          {
+                              $("#tooltip").fadeOut(100);
+                              return;
+                          }
+
+                          var x = item.datapoint[0].toFixed(2);
+                          var y = item.datapoint[1].toFixed(2);
+                          //item.series.label
+
+                          var tooltip = $("#tooltip");
+                          tooltip.html(parseInt(x) + " / " + y);
+                          tooltip.css({top: item.pageY-20, left: item.pageX+5});
+                          tooltip.fadeIn(200);
+                      });
+    }
+
+    //$("#accordion").accordion("refresh");
+}
+
+function onSubmit(file, pixelOnly)
+{
+    if (!file)
+        file = document.getElementById("file").value;
+
+    var dbg = document.getElementById("debug");
+    while (dbg.lastChild)
+        dbg.removeChild(dbg.lastChild);
+
+    var event   = document.getElementById("event").value;
+    var pixel   = document.getElementById("pixel").value;
+    var source1 = editor1.getValue();
+    var source2 = editor2.getValue();
+
+    var uri = "file="+file+"&event="+event+"&pixel="+map[pixel];
+    uri += "&source1="+encodeURIComponent(source1);
+    uri += "&source2="+encodeURIComponent(source2);
+
+    $.ajax({
+        type:    "POST",
+        cache:   false,
+        url:     "index.php",
+        data:    uri,
+        success: onDataReceived,
+        error:   function(xhr) { if (xhr.status==0) alert("ERROR[1] - Request failed!"); else alert("ERROR[1] - "+xhr.statusText+" ["+xhr.status+"]"); }
+    });
+}
+
+function onFile(event, ui)
+{
+    var f = event.target.files[0];
+    if (!f)
+        return;
+
+    if (!f.type.match('text/plain') && !f.type.match('application/javascript'))
+    {
+        alert("ERROR - Unknown file type.");
+        return;
+    }
+
+    var id     = event.target.id;
+    var editor = id[id.length-1]=='1' ? editor1 : editor2;
+
+    var reader = new FileReader();
+
+    // Closure to capture the file information.
+    reader.onload = (function(theFile) { return function(e) { editor.setValue(e.target.result); }; })(f);
+    // onloadstart
+    // onloadend
+    // onprogress
+
+    // Read in the text file
+    reader.readAsText(f);
+}
+
+function onEvent()
+{
+    onSubmit();
+}
+
+function onPixel()
+{
+    var p = parseInt(document.getElementById("pixel").value);
+
+    var cbpx = map[p];
+
+    document.getElementById("cbpx-c").value = parseInt((cbpx/360));
+    document.getElementById("cbpx-b").value = parseInt((cbpx/36)%10);
+    document.getElementById("cbpx-p").value = parseInt((cbpx/9)%4);
+    document.getElementById("cbpx-x").value = parseInt((cbpx%9));
+    document.getElementById("cbpx").value = parseInt(cbpx);
+
+    onSubmit("", true);
+}
+
+function onCBPX()
+{
+    var c = parseInt(document.getElementById("cbpx-c").value);
+    var b = parseInt(document.getElementById("cbpx-b").value);
+    var p = parseInt(document.getElementById("cbpx-p").value);
+    var x = parseInt(document.getElementById("cbpx-x").value);
+
+    var cbpx = c*360 + b*36 + p*9 + x;
+
+    document.getElementById("cbpx").value = parseInt(cbpx);
+    document.getElementById("pixel").value = map.indexOf(cbpx);
+
+    onSubmit("", true);
+}
+
+function onHW()
+{
+    var cbpx = parseInt(document.getElementById("cbpx").value);;
+
+    document.getElementById("cbpx-c").value = parseInt((cbpx/360));
+    document.getElementById("cbpx-b").value = parseInt((cbpx/36)%10);
+    document.getElementById("cbpx-p").value = parseInt((cbpx/9)%4);
+    document.getElementById("cbpx-x").value = parseInt((cbpx%9));
+
+    document.getElementById("pixel").value = map.indexOf(cbpx);
+
+    onSubmit("", true);
+}
+
+function isInside(x, y, mouse)
+{
+    var dist = Math.sqrt((mouse.x-x)*(mouse.x-x)+(mouse.y-y)*(mouse.y-y));
+    return dist<0.5;
+
+    /*
+    ctx.translate(x, y);
+    ctx.scale(1/2, 1/3);
+
+    ctx.beginPath();
+    ctx.moveTo( 1,  1);
+    ctx.lineTo( 0,  2);
+    ctx.lineTo(-1,  1);
+    ctx.lineTo(-1, -1);
+    ctx.lineTo( 0, -2);
+    ctx.lineTo( 1, -1);
+    ctx.fill();
+
+    ctx.restore();
+    */
+}
+
+function getIndex(id, mouse)
+{
+    var canv = document.getElementById(id);
+
+    var scale = 83;
+
+    var w = Math.min(canv.width/scale, canv.height/scale);
+
+    //ctx.translate(canv.width/2, canv.height/2);
+    //ctx.scale(w*2, w*2);
+    //ctx.scale(1, Math.sqrt(3)/2);
+    //ctx.translate(-0.5, 0);
+
+    mouse.x -= canv.width/2;
+    mouse.y -= canv.height/2;
+    mouse.x /= w*2;
+    mouse.y /= w*2;
+    mouse.y /= Math.sqrt(3)/2;
+    mouse.x -= -0.5;
+
+    if (isInside(0, 0, mouse))
+        return 0;
+
+    var cnt = 1;
+    for (var ring=1; ring<24; ring++)
+    {
+        for (var s=0; s<6; s++)
+        {
+            for (var i=1; i<=ring; i++)
+            {
+                var pos = new Position(s, ring, i);
+                if (pos.d() - pos.x > 395.75)
+                    continue;
+
+                if (isInside(pos.x, pos.y, mouse))
+                    return cnt;
+                cnt++;
+            }
+        }
+    }
+
+    if (isInside(7, -22, mouse))
+        return 1438;
+    if (isInside(7, 22, mouse))
+        return 1439;
+
+    return -1;
+}
+
+var inprogress = 0;
+function moveAnimate(n, target)
+{
+    if (inprogress==n || inprogress<0)
+        return;
+
+    inprogress = target ? -n : n;
+
+    var element   = $("#camera"+n); //Allow passing in either a JQuery object or selector
+    var newParent = $(target ? target : "#cont"+n); //Allow passing in either a JQuery object or selector
+
+    var oldOffset = element.offset();
+
+    var newOffset = newParent.offset();
+
+    var w = newParent.width();
+    var h = newParent.height();
+
+    var temp = element.appendTo('body');
+    temp.css('position', 'absolute')
+        .css('left', oldOffset.left)
+        .css('top',  oldOffset.top)
+        .css('zIndex', 400);
+
+    temp.animate( {'top': newOffset.top, 'left':newOffset.left, 'width':w, 'height': h},
+    'slow', function()
+    {
+        temp = temp.appendTo(newParent);
+        temp.css('position', 'default');
+        temp.css('width', 'default');
+        temp.css('height', 'default');
+        temp.css('left', '0');
+        temp.css('top', '0');
+
+        var canv = document.getElementById("camera"+n);
+
+        canv.width = w;
+        canv.height = h;
+
+        drawFullCam("camera"+n);
+
+        inprogress = 0;
+    });
+}
+
+function onClickNew(event)
+{
+    var id = event.target.id;
+    var n = parseInt(id[id.length-1]);
+
+    if (id.substr(0, 3)=="cam")
+    {
+        var cont = document.getElementById("center").childNodes[0];
+        if (cont)
+        {
+            var nn = parseInt(cont.id[cont.id.length-1]);
+            moveAnimate(nn);
+        }
+        moveAnimate(n, "#center");
+
+    }
+    else
+        moveAnimate(n);
+}
+
+function onClick(event)
+{
+    var cont = document.getElementById("center").childNodes[0];
+    if (!cont)
+        return;
+
+    if (cont.id!=event.target.id)
+        return;
+
+    var mouse = getClickPosition();
+    var index = getIndex(event.target.id, mouse);
+
+    if (index<0)
+        return;
+
+    document.getElementById("pixel").value = index;
+
+    onPixel();
+}
+
+function getClickPosition(event)
+{
+    var event = window.event;
+
+    if (event.offsetX !== undefined && event.offsetY !== undefined)
+        return { x: event.offsetX, y: event.offsetY };
+
+    return { x: event.layerX, y: event.layerY };
+}
+
+function onCameraMinMax(n)
+{
+    var canv = document.getElementById("camera"+n);
+
+    canv.zmin = document.getElementById("cameramin"+n).value;
+    canv.zmax = document.getElementById("cameramax"+n).value;
+
+    drawFullCam("camera"+n);
+}
+
+function onCameraMinMaxOn(n)
+{
+    var canv = document.getElementById("camera"+n);
+
+    var redraw;
+    if (document.getElementById("cameraminon"+n).checked)
+    {   
+        document.getElementById("cameramin"+n).setAttribute("disabled", "true");
+        document.getElementById("cameramin"+n).value = canv.min;
+        redraw = true;
+    }
+    else
+        document.getElementById("cameramin"+n).removeAttribute("disabled");
+
+    if (document.getElementById("cameramaxon"+n).checked)
+    {
+        document.getElementById("cameramax"+n).setAttribute("disabled", "true");
+        document.getElementById("cameramax"+n).value = canv.max;
+        redraw = true;
+    }
+    else
+        document.getElementById("cameramax"+n).removeAttribute("disabled");
+
+    if (redraw)
+        onCameraMinMax(n);
+}
+
+function onWaveformMinMax()
+{
+    var wf = document.getElementById("waveform");
+
+    var xmin, xmax, ymin, ymax;
+
+    var redraw;
+    if (!document.getElementById("waveformminon").checked)
+        ymin = document.getElementById("waveformmin").value;
+    if (!document.getElementById("waveformmaxon").checked)
+        ymax = document.getElementById("waveformmax").value;
+
+    setZoom(xmin, xmax, ymin, ymax);
+
+}
+
+function onWaveformMinMaxOn()
+{
+    var wf = document.getElementById("waveform");
+
+    var xmin, xmax, ymin, ymax;
+
+    var redraw;
+    if (document.getElementById("waveformminon").checked)
+    {
+        document.getElementById("waveformmin").setAttribute("disabled", "true");
+        document.getElementById("waveformmin").value = wf.ymin;
+        ymin = wf.ymin-5;
+    }
+    else
+        document.getElementById("waveformmin").removeAttribute("disabled");
+
+    if (document.getElementById("waveformmaxon").checked)
+    {
+        document.getElementById("waveformmax").setAttribute("disabled", "true");
+        document.getElementById("waveformmax").value = wf.ymax;
+        ymax = wf.ymax+5;
+    }
+    else
+        document.getElementById("waveformmax").removeAttribute("disabled");
+
+    setZoom(xmin, xmax, ymin, ymax);
+}
+
+//document.addEventListener("click", getClickPosition, false);
+
+$(document).ready(onReady);
+
+// ================================== Pixel mapping =================================================
+
+var codedMap = "966676:6:A;68656364626Y?\\?;A=A<AGADAN4K4i5g5h5o506W?Z?]?_?>A@A?AJAIAFACAM4J4H4f5d5e5l5m5n516X?[?^?N?P?AA1ABAVAUAKAHAEAO4L4I4G4E4c5a5b5M6j5k5V6Y6\\6_6G?J?O?Q?S?2A4A3AYAXAWAbA_AnAkAhA3404F4D4B4`5^5_5J6K6L6S6T6W6Z6]6E?H?K?M?R?T?V?5A7A6A\\A[AZAeAdAaA^AmAjAgA24o3m3C4A4?4]5[5\\5G6H6I6P6Q6R6U6X6[6^6F?I?L?<?>?U?;@=@8Ah@9AMALA]ABCACfAcA`AoAlAiA4414n3l3<4@4>425Z5X5Y5D6E6F698N6O608E8H8K8H>K>N>?>B>=???A?<@>@@@i@k@j@PAOANAECDCCC<C9CZCWCTC=3:3734313=4;4943515o4W5U5V5A6B6C6687888m7n7C8F8I8F>I>L>=>@>C>E>@?B?D??@A@C@l@n@m@SARAQAHCGCFC?C>C;C8CYCVCSC<393633303n2:4846405n4l4T5R5S5>6?6@6384858j7k7l7o7D8G8J8G>J>M>>>A>D>4>6>C?3?5?B@2@4@o@_@0ALBKBTA0CoBIC8D7D@C=C:C[CXCUC>3;3835323o2m2k27454j3m4k4i4Q5O5P5C7<6=6g71828o8h7i7S9<8?8B8Q>T>W>@=C=F=e<h<5>7>9>4?6?8?3@5@7@`@b@a@OBNBMB3C2C1C;D:D9D_D\\DQCNCKCF3C3@3>2;282Z1W1l2j2h2k3i3g3j4h4f4N5L5M5@7A7B7d7e7f7l8m8n8P9Q9:8=8@8O>R>U>>=A=D=c<f<i<k<8>:><>7?9?;?6@8@:@c@e@d@RBQBPB6C5C4C>D=D<DbDaD^D[DPCMCJCE3B3?3=2:272Y1V1T1i2g2e2h3f3d3g4e4c4K5I5J5=7>7?7a7b7c7i8j8k8M9N9O9R9;8>8A8P>S>V>?=B=E=d<g<j<Z<\\<;>k=m=:?j>l>9@i?k?f@V@g@CBBBSBgBfB7CoCnC?DSDRDcD`D]DRCOCLCG3D3A3?2<292[1X1U1S1Q1f2d2b2e3c3a3d4b4`4H5F5G5:7;7<7^7_7`7f8g8h8J9K9L97:::=:@:C:F:I:I=L=O=7=:===n<1=[<]<_<l=n=0>k>m>o>j?l?n?W@Y@X@FBEBDBjBiBhB2D1D0DVDUDTDCE@EOELEIEXEUERE5222o1l1i1f1c1`1R1P1N1c2a2_2b3`3^3a4_4]4<5:5;5778797[7\\7]7c8d8e8G9H9I94:5:8:;:>:A:D:G:G=J=M=5=8=;=l<o<2=4=^<`<b<o=1>3>n>0?2?m?o?1@Z@\\@[@IBHBGBmBlBkB5D4D3DYDXDWDFEEEBE?ENEKEHEWETEQE4212n1k1h1e1b1_1]1O1M1K1`2^2\\2_3]3[3^4\\4Z4957585475767X7Y7Z7`8a8b8D9E9F91:2:3:6:9:<:?:B:E:H:H=K=N=6=9=<=m<0=3=d;f;a<H<J<2>b=d=1?a>c>0@`?b?]@D@^@:B9BJB^B]BnBfCeC6DJDIDZDnDmDGEDEAEPEMEJEYEVESE623202m1j1g1d1a1^1\\1[0L1J1?1]2[2Y2\\3Z3X3[4Y4W4654555172737U7V7W7]8^8_8A9B9C9e9o90:n9g:j:m:L:O:R:U:X:[:];`;c;T;W;Z;7<9<e;g;i;I<K<M<c=e=g=b>d>f>a?c?e?E@G@F@=B<B;BaB`B_BiChCgCMDLDKD1E0EoD9E7E<F9F6FaE^E[EjEgEdER0O0L0I0F0C0n0l0\\0Z0X0@1>1<1Z2X2V2Y3W3U3X4V4T4E5C5D5n6o607R7S7T7Z8[8\\8>9?9@9b9c9d9l9m9e:h:k:J:M:P:S:V:Y:[;^;a;R;U;X;6<8<:<;<h;j;l;L<N<P<f=h=j=e>g>i>d?f?h?N@Q@H@@B?B>BdBcBbBlCkCjCPDODND4E3E2E;E:E8E6E;F8F5F`E]EZEiEfEcEQ0N0K0H0E0B0m0k0j0Y0W0U0=1;191W2U2S2V3T3R3U4S4Q4B5@5A5k6l6m6O7P7Q7W8X8Y8;9<9=9_9`9a9j9k9\\:_:f:i:l:K:N:Q:T:W:Z:\\;_;b;S;V;Y;C<E<G<<<m;k;1<2<O<Q<S<i=[=P=h>X>Z>g?M@O@R@U@S@J@I@AB1B0BeBTB\\CmCAD@DQDiDhD5EdD<E4F2F0F=F:F7FbE_E\\EkEhEeES0P0M0J0G0D011o0h0g0e0V0T0`0:181Q2T2R2H2S3Q3P3R4P4K3?5=5>5c6i6j6h6M7N7L7U8V8T899:9W9]9^9\\9g9h9i9]:`:c:5;2;o:>;;;8;P;M;J;G;D;A;?<A<D<F<=<><n;o;3<5<R<T<Y=Z=Q=R=Y>[>\\>^>P@T@L@K@5B4B3B2BVBUB^C]CCDBDkDjDfDeD>E=E3F1FnElE@FCFFFIFLFOF;0>0A0205080513101i0f0d0c0b0_0I1H1D1P2O2G2F2D2O3N3M3J3H3a6b6e6f6g6I7J7K7R8S8397989V9Y9Z9[9f9^:a:d:4;1;n:=;:;7;O;L;I;F;C;@;@<B<0<4<U<V<X<`=]=\\=S=U=W=]>_>`>8B7B6BZBXBWB_CaC`CFDEDDDlDgDoEmE>FAFDFGFJFMF90<0?0003060714121a0^0]0G1C1B1N2L2E2C2B2@2L3I3`6d6E7F7G7H7O8Q8192969T9U9X96;3;0;?;<;9;Q;N;K;H;E;B;W<Y<^=_=a=T=V=X=\\B[BYBdCcCbCHDGD?FBFEFHFKFNF:0=0@0104070F1E1A1M2K2J2I2A2D7L8M8N8P809495961b:";
+var map = new Array(1440);
+
+// first: decode the pixel mapping!
+var sum = 1036080;
+for (var i=0; i<1440; i++)
+{
+    var d0 = codedMap.charCodeAt(i*2)  -48;
+    var d1 = codedMap.charCodeAt(i*2+1)-48;
+
+    map[i] = d0 | (d1<<6);
+    sum -= map[i];
+}
+if (sum!=0)
+    alert("Pixel mapping table corrupted!");
+
+// ================================== Camera Display ================================================
+
+function hueToRGB(hue)
+{
+    hue /= 3;
+    hue %= 6;
+
+    if (hue<1) return parseInt(255*hue,     10);
+    if (hue<3) return parseInt(255,         10);
+    if (hue<4) return parseInt(255*(4-hue), 10);
+
+    return 0.
+}
+
+function hueToHex(flt)
+{
+    var s = hueToRGB(flt).toString(16);
+    return s.length==2 ? s : "0"+s;
+}
+
+function HLStoRGB(hue)
+{
+    if (isNaN(hue))
+        return "fff";
+
+    if (hue<0)
+        return "eef"; // 555
+
+    if (hue>1)
+        return "700"; // 666
+
+    hue *= 14;
+
+    var sr = hueToHex(20-hue);
+    var sg = hueToHex(14-hue);
+    var sb = hueToHex(26-hue);
+
+    return sr+sg+sb;
+}
+
+function drawHex(ctx, x, y, col, min, max)
+{
+    if (col===undefined || col===null)
+        return;
+
+    var lvl = (col-min)/(max-min);
+
+    ctx.fillStyle = "#"+HLStoRGB(lvl);
+
+    ctx.save();
+
+    ctx.translate(x, y);
+    ctx.scale(1/2, 1/3);
+
+    ctx.beginPath();
+    ctx.moveTo( 1,  1);
+    ctx.lineTo( 0,  2);
+    ctx.lineTo(-1,  1);
+    ctx.lineTo(-1, -1);
+    ctx.lineTo( 0, -2);
+    ctx.lineTo( 1, -1);
+    ctx.fill();
+
+    ctx.restore();
+}
+
+function Position(s, ring, i)
+{
+    switch (s)
+    {
+    case 1: this.x =  ring     - i*0.5;  this.y =       + i; break;
+    case 2: this.x =  ring*0.5 - i;      this.y =  ring    ; break;
+    case 3: this.x = -ring*0.5 - i*0.5;  this.y =  ring - i; break;
+    case 4: this.x = -ring     + i*0.5;  this.y =       - i; break;
+    case 5: this.x = -ring*0.5 + i;      this.y = -ring    ; break;
+    case 0: this.x =  ring*0.5 + i*0.5;  this.y = -ring + i; break;
+    }
+    this.d = (function () { return this.x*this.x + this.y*this.y*3/4; });
+}
+
+function drawFullCam(id)
+{
+    var canv = document.getElementById(id);
+    if (!canv)
+        return;
+
+    var ctx = canv.getContext("2d");
+
+    ctx.clearRect(0, 0, canv.width, canv.height);
+
+    // ======================= Draw Graphics ======================
+
+    var data = canv.dataRel;
+    if (!data)
+        return;
+
+    var min = (canv.zmin-canv.min)/canv.max;
+    var max = (canv.zmax-canv.min)/canv.max;
+
+    var scale = 83;
+
+    var w = Math.min(canv.width/scale, canv.height/scale);
+
+    ctx.save();
+    ctx.translate(canv.width/2, canv.height/2);
+    ctx.scale(w*2, w*2);
+    // ctx.rotate(Math.PI/3);
+
+    ctx.scale(1, Math.sqrt(3)/2);
+    ctx.translate(-0.5, 0);
+
+    drawHex(ctx, 0, 0, data[map[0]], min, max);
+
+    var cnt = 1;
+    for (var ring=1; ring<24; ring++)
+    {
+        for (var s=0; s<6; s++)
+        {
+            for (var i=1; i<=ring; i++)
+            {
+                var pos = new Position(s, ring, i);
+                if (pos.d() - pos.x > 395.75)
+                    continue;
+
+                drawHex(ctx, pos.x, pos.y, data[map[cnt]], min, max);
+                cnt++;
+            }
+        }
+    }
+
+    drawHex(ctx, 7, -22, data[map[1438]], min, max);
+    drawHex(ctx, 7,  22, data[map[1439]], min, max);
+
+    ctx.restore();
+
+    // ======================= Draw Legend ======================
+
+    var lmin = parseFloat(canv.min).toFixed(1);
+    var lmax = parseFloat(canv.max).toFixed(1);
+
+    var v0 = parseFloat(canv.zmin);
+    var v1 = parseFloat(canv.zmax);
+
+    var diff = v1-v0;
+
+    var cw = canv.width;
+    //var ch = canv.height;
+
+    ctx.font         = "8pt Arial";
+    ctx.textAlign    = "right";
+    ctx.textBaseline = "top";
+
+    for (var i=0; i<11; i++)
+    {
+        ctx.strokeStyle = "#"+HLStoRGB(i/10);
+        ctx.strokeText((v0+diff*i/10).toPrecision(3), cw-5, 125-i*12);
+    }
+
+    var mw = Math.max(ctx.measureText(lmin).width,
+                      ctx.measureText(lmax).width);
+
+    ctx.textBaseline = "top";
+    ctx.strokeStyle  = "#000";
+
+    //ctx.strokeText(min, 5+mw, 5+24);
+    ctx.strokeText(lmin, 5+mw, 5+12);
+    ctx.strokeText(lmax, 5+mw, 5);
+}
+
Index: trunk/FACT++/www/viewer/index.php
===================================================================
--- trunk/FACT++/www/viewer/index.php	(revision 17700)
+++ trunk/FACT++/www/viewer/index.php	(revision 17700)
@@ -0,0 +1,277 @@
+<?php
+
+if (!extension_loaded('v8js'))
+    die("V8Js missing");
+
+if (isset($_POST['editor1']) || isset($_POST['editor2']))
+{
+    $isOne = isset($_POST['editor1']);
+
+    $source = $isOne ? $_POST['editor1'] : $_POST['editor2'];
+    if (!isset($_POST['files']))
+        $name = $isOne ? "proc.js" : "main.js";
+    else
+        $name = $_POST['files'][0];
+
+    header($_SERVER["SERVER_PROTOCOL"] . " 200 OK");
+    header("Cache-Control: public"); // needed for i.e.
+    header("Content-Type: text/plain");
+    header("Content-Transfer-Encoding: Text");
+    header("Content-Disposition: attachment; filename=\"".$name."\"");
+    print($source);
+    return;
+}
+
+if (!isset($_POST['file']) || !isset($_POST['event']) || !isset($_POST['pixel']))
+{
+   /*
+    function getList($path)
+    {
+        $hasdir = false;
+
+        $list = array();
+        foreach (new DirectoryIterator($path) as $file)
+        {
+           if ($file->isDot())
+              continue;
+
+           $name = $file->getFilename();
+
+
+           if ($file->isDir())
+           {
+               $list[$name] = getList($path."/".$name);
+               $hasdir = true;
+           }
+
+           if ($file->isFile() && $file->isReadable())
+           {
+               if (substr($name, 12)!=".fits.fz")
+                   continue;
+
+               array_push($list, substr($name, 9, 3));
+           }
+        }
+
+        if (!$hasdir)
+            sort($list);
+        return $list;
+    }
+    */
+
+    function getList($path, $sub)
+    {
+        $hasdir = false;
+
+        $list = array();
+        foreach (new DirectoryIterator($path."/".$sub) as $file)
+        {
+           if ($file->isDot())
+              continue;
+
+           $name = $file->getFilename();
+
+           if ($file->isDir())
+               $list = array_merge($list, getList($path, $sub."/".$name));
+
+           if ($file->isFile() && $file->isReadable())
+           {
+               if (substr($name, 12)!=".fits.fz")
+                   continue;
+
+               $rc = substr($name, 0, 4)."/".substr($name, 4, 2)."/".substr($name, 6, 2)."-".substr($name, 9, 3);
+
+               array_push($list, $rc);
+           }
+        }
+
+        return $list;
+    }
+
+    try
+    {
+        $list = getList("/daq/raw", "");
+        sort($list);
+    }
+    catch (Exception $e)
+    {
+        return header('HTTP/1.0 400 '.$e->getMessage());
+    }
+
+    print(json_encode($list));
+    return;
+}
+
+$event = intval($_POST['event']);
+$pixel = intval($_POST['pixel']);
+
+function get($handle, $format, $count = 1)
+{
+    $size = 0;
+    switch ($format)
+    {
+    case 'L': $size = 4; break;
+    case 'S': $size = 2; break;
+    case 's': $size = 2; break;
+    }
+
+    if ($size==0)
+        return;
+
+    $data = fread($handle, $size*$count);
+    $data = unpack($format.$count, $data);
+
+    return $count==1 ? $data[1] : $data;
+}
+
+//define('E_FATAL',  E_ERROR | E_USER_ERROR | E_PARSE | E_CORE_ERROR |
+//        E_COMPILE_ERROR | E_RECOVERABLE_ERROR);
+
+$rc = array();
+$rc['startPhp'] = microtime(true);
+
+// ============================ Read data from file ==========================
+$file = $_POST['file'];
+
+$y = substr($file,  0, 4);
+$m = substr($file,  5, 2);
+$d = substr($file,  8, 2);
+$r = substr($file, 11, 3);
+
+$filename = "/daq/raw/".$y."/".$m."/".$d."/".$y.$m.$d."_".$r.".fits.fz";
+
+$file = popen("/home/fact/FACT++/getevent ".$filename." ".$event." 2> /dev/null", "r");
+if (!$file)
+    return header('HTTP/1.0 400 Could not open file.');
+
+$evt = array();
+
+$evt['numEvents']     = get($file, "L");
+$evt['numRoi']        = get($file, "L");
+$evt['numPix']        = get($file, "L");
+$evt['eventNumber']   = get($file, "L");
+$evt['triggerNumber'] = get($file, "L");
+$evt['triggerType']   = get($file, "S");
+$evt['unixTime']      = get($file, "L", 2);
+
+if (isset($_POST['source1']))
+{
+    // Read the data and copy it from an associative array to an Array
+    // (this is just nicer and seems more logical)
+    $data = array();
+    for ($i=0; $i<$evt['numPix']; $i++)
+    {
+        //$data[$i] = get($file, "s", $evt['numRoi']);
+        $var = get($file, "s", $evt['numRoi']);
+
+        $data[$i] = array();
+        for ($j=0; $j<$evt['numRoi']; $j++)
+            $data[$i][$j] = $var[$j+1]*0.48828125; // dac -> mV
+    }
+}
+
+//if (feof($file))
+//   return;
+
+pclose($file);
+
+if ($evt['numEvents']==0)
+    return header('HTTP/1.0 400 Could not read event.');
+
+// =============================== Copy waveform =============================
+
+$rc['event'] = $evt;
+$rc['event']['index'] = $event;
+$rc['event']['pixel'] = $pixel;
+//$rc['waveform'] = $data[$pixel];
+
+// Get execution times
+$now = microtime(true);
+
+if (isset($_POST['source1']))
+{
+    // =============================== Run Javascript ============================
+
+/*
+$JS = <<< EOT
+require("path/to/module1");
+EOT;
+
+$v8 = new V8Js();
+$v8->setModuleLoader(function($module) {
+  switch ($module) {
+    case 'path/to/module1':
+      return 'print(' . json_encode($module . PHP_EOL) . ');require("./module2");';
+
+    case 'path/to/module2':
+      return 'print(' . json_encode($module . PHP_EOL) . ');require("../../module3");';
+
+    default:
+      return 'print(' . json_encode($module . PHP_EOL) . ');';
+  }
+});
+*/
+
+    // V8Js global methods:  exit, print, sleep, var_dump, require
+    //$v8 = new V8Js("$", array("data"=>"data"), extensions, flags, millisecond, bytes);
+    $v8 = new V8Js("$"/*, array("data"=>"data")*/);
+
+    //$v8 = new V8Js("$", array("data"=>"data"));
+
+    //V8Js::registerExtension("exit", "1");
+    //$v8 = new V8Js("$", array(), array("exit"));
+
+    //$v8->func = function($a) { echo "Closure with param $a\n"; };
+    //$v8->greeting = "From PHP with love!";
+
+    // This is much faster than the variables option in the constructor
+    $v8->data  = $data;
+    $v8->event = $evt;
+    $v8->clone = function($data) { return $data; };
+
+    $rc['startJs'] = microtime(true);
+
+    // Buffer output from javascript
+    ob_start();
+
+    try
+    {
+        $JS = "(function proc(pixel){\n".$_POST['source1']."\n})(".$pixel.");";
+        $rc['waveform'] = $v8->executeString($JS, 'proc');
+
+        if (isset($_POST['source2']))
+        {
+            $JS = "(function main(){\n".$_POST['source2']."\n})();";
+            $rc['ret'] = $v8->executeString($JS, 'main');
+        }
+
+        // This is supposed to work, but it seems it does not...
+        //$v8->proc = $rc['ret']->proc;
+        //$rc['ret'] = $v8->executeString('PHP.proc();', 'proc');
+    }
+    catch (V8JsException $e)
+    {
+        $rc['err'] = array();
+        $rc['err']['message']    = $e->getMessage();
+        $rc['err']['file']       = $e->getJsFileName();
+        $rc['err']['sourceLine'] = $e->getJsSourceLine();
+        $rc['err']['lineNumber'] = $e->getJsLineNumber()-1;
+        $rc['err']['trace']      = $e->getJsTrace();
+    }
+
+    // Copy output buffer and clean it
+    $rc['debug'] = ob_get_contents();
+    ob_end_clean();
+
+    // Get execution times
+    $now = microtime(true);
+
+    $rc['timeJs']  = ($now - $rc['startJs']);
+}
+
+$rc['timePhp'] = ($now - $rc['startPhp']);
+
+// Output result as JSON object
+print(json_encode($rc));
+
+?>
