Last active
October 1, 2015 01:58
-
-
Save karino2/1898759 to your computer and use it in GitHub Desktop.
EquationPad
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<meta charset="UTF-8"> | |
<style type="text/css"> | |
.selectedTarget { background-color: red} | |
#halo input { | |
background: #88ffff; | |
border-radius: 8px; | |
margin: 0; | |
width: 3em; | |
height: 2em; | |
} | |
#gesturearea { | |
width: 40em; | |
height: 40em; | |
background: #ff88ff; | |
} | |
.halobutton{ | |
font-size: xx-large; | |
} | |
#halo td { | |
padding: 0; | |
} | |
#mathcanvas { | |
position: relative; | |
z-index: 5; | |
} | |
</style> | |
</head> | |
<body onload="onload()"> | |
<input value="Export" type="button" onclick="Export();"> | |
<input value="Import" type="button" onclick="Import();"> | |
<input type="file" id="load_file"> | |
<input value="Load" type="button" onclick="Load()"> | |
<input value="Save" type="button" onclick="Save()"> | |
<input id="import_data_hidden" type="hidden" | |
value="[['%5B%22=%22,%0A%20%20%22Q%22,%0A%20%20%5B%22pow%22,%0A%20%20%20%20%5B%22*%22,%22K%22,%22L%22%5D,%0A%20%20%20%20%5B%22/%22,%221%22,%22a%22%5D%0A%20%20%5D%0A%5D','%22Q%E3%81%AF%E7%94%A3%E5%87%BA%E9%87%8F%E3%80%81K%E3%81%AF%E8%B3%87%E6%9C%AC%E3%81%A7%E7%9F%AD%E6%9C%9F%E3%81%A7%E3%81%AF%E5%9B%BA%E5%AE%9A%E3%80%81L%E3%81%AF%E5%8A%B4%E5%83%8D%22',],[]]"> | |
<div id="history" contenteditable="true"></div> | |
<script language="javascript"> | |
var g_DEFAULT_IMPORT = ""; | |
var g_VERVOSE = false; | |
function assertFail(msg) { | |
if(g_VERVOSE) | |
alert("fail! "+ msg); | |
throw msg; | |
} | |
function assertFalse(condition, msg) { | |
if(condition) | |
assertFail("assert false fail:" + msg); | |
} | |
function assertEq(exp, act) { | |
if(exp != act) | |
assertFail("not equal [" + exp + "] and [" + act + "]"); | |
} | |
function assertNeq(exp, act) { | |
if(exp == act) | |
assertFail("not equal [" + exp + "] and [" + act + "]"); | |
} | |
function assertNStrictEq(exp, act) { | |
if(exp === act) | |
assertFail("not equal [" + exp + "] and [" + act + "]"); | |
} | |
function $(id) { | |
return document.getElementById(id); | |
} | |
function CE(tag){ | |
return document.createElement(tag); | |
} | |
function QueryInput(title, initVal) { | |
return prompt(title, initVal); | |
} | |
/* | |
------------------------------------------------------------------------------ | |
utility DONE, parser Start | |
------------------------------------------------------------------------------ | |
*/ | |
var g_id = 1; | |
function IdGen() { | |
return g_id++; | |
} | |
function Mo(id, val) { | |
return "<mo id=\"" + id + "\">" + val + "</mo>"; | |
} | |
function Mi(id, val) { | |
return "<mi id=\"" + id + "\">" + val + "</mi>"; | |
} | |
function MnOrMi(term) { | |
if(term instanceof Number){ | |
return "<mn id=\"" + term.objId + "\">" + term + "</mn>"; | |
} | |
if(term instanceof String){ | |
if(term[0] == '&') { | |
return Mi(term.objId, term); | |
} | |
return Mi(term.objId, term); | |
} | |
throw "Unknown terminal"; | |
} | |
function EvalTerm(builder, term, nofence){ | |
if(term instanceof Array){ | |
if(!nofence) { | |
builder.push("<mfenced id=\""); | |
builder.push(term.objId); | |
builder.push("\"><mrow>"); | |
} | |
builder.push(JsonToMathML(term)); | |
if(!nofence) | |
builder.push("</mrow></mfenced>"); | |
}else{ | |
builder.push(MnOrMi(term)); | |
} | |
} | |
function PushMo(builder, tagName, id) { | |
if(id) | |
builder.push('<mo id="' + id + '">'); | |
else | |
builder.push("<mo>"); | |
builder.push(tagName); | |
builder.push("</mo>"); | |
} | |
function PushMi(builder, name, id) { | |
if(id) | |
builder.push('<mi id="' + id + '">'); | |
else | |
builder.push("<mi>"); | |
builder.push(name); | |
builder.push("</mi>"); | |
} | |
function IsMinus(term) { | |
return term[0] == "-"; | |
} | |
function IsSingleTerm(term) { | |
return !(term[0] == "+"); | |
} | |
function PushMinusTerm(builder, term) { | |
assertEq(true, IsMinus(term)); | |
PushMrow(builder, term.objId); | |
PushMo(builder, "-", term[0].objId); | |
EvalTerm(builder,term[1], IsSingleTerm(term[1])); | |
builder.push('</mrow>'); | |
} | |
function FirstHeadIsNotMinus(sexp) { | |
if(sexp[0] == "-") | |
return false; | |
if(sexp[0] != "*") | |
return true; | |
return FirstHeadIsNotMinus(sexp[1]); | |
} | |
function EvalPlus(json) { | |
var builder = []; | |
var len = json.length; | |
PushMrow(builder, json.objId); | |
if(IsMinus(json[1])){ | |
PushMinusTerm(builder, json[1]); | |
} | |
else | |
EvalTerm(builder, json[1], IsSingleTerm(json[1])); | |
for(var i = 2; i < len; i++) { | |
if(IsMinus(json[i])){ | |
PushMinusTerm(builder, json[i]); | |
} | |
else { | |
PushMo(builder, "+"); | |
EvalTerm(builder,json[i], IsSingleTerm(json[i]) && FirstHeadIsNotMinus(json[i])); | |
} | |
} | |
builder.push('</mrow>'); | |
return builder.join(""); | |
} | |
function PushMrow(builder, id) { | |
builder.push('<mrow id="'+ id + '">'); | |
} | |
function EvalTimes(json) { | |
var builder = []; | |
var len = json.length; | |
PushMrow(builder, json.objId); | |
EvalTerm(builder, json[1], IsSingleTerm(json[1])); | |
for(var i = 2; i < len; i++) { | |
if(json[i-1] instanceof Number && json[i] instanceof Number) | |
PushMo(builder, "×"); | |
EvalTerm(builder,json[i], IsSingleTerm(json[i]) && !IsMinus(json[i])); | |
} | |
builder.push('</mrow>'); | |
return builder.join(""); | |
} | |
function EvalEquation(json, strOp) { | |
var builder = []; | |
PushMrow(builder, json.objId); | |
EvalTerm(builder, json[1], true); | |
PushMo(builder, strOp, json[0].objId); | |
EvalTerm(builder,json[2], true); | |
builder.push("</mrow>"); | |
return builder.join(""); | |
} | |
function EvalPower(json) { | |
var builder = []; | |
builder.push('<msup id="' + json.objId + '">'); | |
EvalTerm(builder, json[1]); | |
EvalTerm(builder, json[2], true); | |
builder.push("</msup>"); | |
return builder.join(""); | |
} | |
function EvalSubIndex(json) { | |
var builder = []; | |
builder.push('<msub id="' + json.objId + '">'); | |
EvalTerm(builder, json[1], true); | |
EvalTerm(builder, json[2], true); | |
builder.push("</msub>"); | |
return builder.join(""); | |
} | |
function EvalFunction(json) { | |
var builder = []; | |
PushMrow(builder, json.objId); | |
EvalTerm(builder,json[1], true); | |
builder.push("<mfenced>"); | |
EvalTerm(builder,json[2], true); | |
builder.push("</mfenced>"); | |
builder.push('</mrow>'); | |
return builder.join(""); | |
} | |
function EvalComma(json) { | |
var builder = []; | |
for(var i = 1; i < json.length; i++) { | |
EvalTerm(builder,json[i], true); | |
} | |
return builder.join(""); | |
} | |
function EvalPartial(json) { | |
var builder = []; | |
builder.push('<mfrac id="' +json.objId + '">'); | |
builder.push("<mrow>"); | |
PushMo(builder, "∂"); | |
EvalTerm(builder, json[1]); | |
builder.push("</mrow>"); | |
builder.push("<mrow>"); | |
PushMo(builder, "∂"); | |
EvalTerm(builder, json[2]); | |
builder.push("</mrow>"); | |
builder.push("</mfrac>"); | |
return builder.join(""); | |
} | |
function EvalFrac(json) { | |
var builder = []; | |
builder.push('<mfrac id="' +json.objId + '"><mrow>'); | |
EvalTerm(builder, json[1], true); | |
builder.push('</mrow><mrow>'); | |
EvalTerm(builder, json[2], true); | |
builder.push('</mrow></mfrac>'); | |
return builder.join(""); | |
} | |
function EvalDotOver(json) { | |
var builder = []; | |
builder.push('<mover id="' + json.objId + '">'); | |
EvalTerm(builder, json[1]); | |
builder.push("<mo>.</mo>"); | |
builder.push("</mover>"); | |
return builder.join(""); | |
} | |
function EvalIntegral(json) { | |
var builder = []; | |
PushMrow(builder, json.objId); | |
if(json.length == 3) { | |
PushMo(builder, "∫", json[0].objId); | |
EvalTerm(builder, json[1], true); | |
// currently, json[2] does'nt support ρ, etc. | |
builder.push(Mi(json[2].objId, "d" + json[2] )); | |
} else if(json.length == 5) { | |
builder.push("<msubsup>"); | |
builder.push(Mo(json[0].objId, "∫")); | |
EvalTerm(builder, json[3], true); | |
EvalTerm(builder, json[4], true); | |
builder.push("</msubsup>"); | |
EvalTerm(builder, json[1], true); | |
// currently, json[2] does'nt support ρ, etc. | |
builder.push(Mi(json[2].objId, "d" + json[2] )); | |
} else { | |
throw "unknown arg number of integral. ( " + json.length + ")"; | |
} | |
builder.push("</mrow>"); | |
return builder.join(""); | |
} | |
function EvalMaxMin(json, maxmin) { | |
var builder = []; | |
PushMrow(builder, json.objId); | |
builder.push("<munder>"); | |
PushMi(builder, maxmin, json[0].objId); | |
EvalTerm(builder, json[2], true); | |
builder.push("</munder>"); | |
builder.push('<mfenced open="{" close="}">'); | |
EvalTerm(builder, json[1], true); | |
builder.push('</mfenced>'); | |
builder.push("</mrow>"); | |
return builder.join(""); | |
} | |
function EvalMax(json) { | |
return EvalMaxMin(json, "max"); | |
} | |
function EvalMin(json) { | |
return EvalMaxMin(json, "min"); | |
} | |
function JsonToMathML(json) { | |
var tag = json[0]; | |
if(tag == "-") | |
{ | |
var builder = []; | |
PushMinusTerm(builder, json); | |
return builder.join(""); | |
} | |
if(tag == "+") | |
{ | |
return EvalPlus(json); | |
} | |
if(tag == "*") | |
{ | |
return EvalTimes(json); | |
} | |
if(tag == "pow") | |
{ | |
return EvalPower(json); | |
} | |
if(tag == "_") | |
{ | |
return EvalSubIndex(json); | |
} | |
if(tag == "part") | |
{ | |
return EvalPartial(json); | |
} | |
if(tag == "fun") | |
{ | |
return EvalFunction(json); | |
} | |
if(tag == "/") | |
{ | |
return EvalFrac(json); | |
} | |
if(tag == "dotover") | |
{ | |
return EvalDotOver(json); | |
} | |
if(tag == "int") | |
{ | |
return EvalIntegral(json); | |
} | |
if(tag == "max") | |
{ | |
return EvalMax(json); | |
} | |
if(tag == "min") | |
{ | |
return EvalMin(json); | |
} | |
if(tag == "=") | |
{ | |
return EvalEquation(json, "="); | |
} | |
if(tag == "<") | |
{ | |
return EvalEquation(json, "<"); | |
} | |
if(tag == "<=") | |
{ | |
return EvalEquation(json, "≤"); | |
} | |
if(tag == ">") | |
{ | |
return EvalEquation(json, ">"); | |
} | |
if(tag == ">=") | |
{ | |
return EvalEquation(json, "≥"); | |
} | |
if(tag == ",") | |
{ | |
return EvalComma(json); | |
} | |
if((json instanceof String) || (json instanceof Number)) | |
{ | |
var builder = []; | |
EvalTerm(builder, json); | |
return builder.join(""); | |
} | |
if(json.length == 1) | |
{ | |
var builder = []; | |
EvalTerm(builder, json[0]); | |
return builder.join(""); | |
} | |
throw "unknown tag:" + tag; | |
} | |
var g_hash = {}; | |
function RegisterObjectToHash(obj) { | |
obj.objId = IdGen(); | |
g_hash[obj.objId] = obj; | |
} | |
function ParseSingleItem(item) { | |
var obj; | |
if(typeof(item) == "number") | |
obj = new Number(item); | |
else if(typeof(item) == "string") | |
obj = new String(item); | |
else if(item instanceof Array) | |
obj = JsonParser(item); | |
else | |
obj = item; | |
RegisterObjectToHash(obj); | |
return obj; | |
} | |
function JsonParser(str) { | |
var sexp = eval(str); | |
var res = []; | |
res.objId = IdGen(); | |
g_hash[res.objId] = res; | |
if(sexp instanceof Array) { | |
for(var i = 0; i < sexp.length; i++) { | |
var obj = ParseSingleItem(sexp[i]); | |
res.push(obj); | |
obj.parent = res; | |
} | |
} else { | |
var obj = ParseSingleItem(sexp); | |
res = obj; // return atom, not array! | |
} | |
assertNStrictEq(res, sexp); | |
return res; | |
} | |
/* | |
----------------------------------------------------------------------------- | |
HTML element related | |
----------------------------------------------------------------------------- | |
*/ | |
function dp(st) { | |
var con = $("console"); | |
con.value += st + "\n"; | |
} | |
function EncloseMath(str,isBlock){ | |
var header ="<math " + (isBlock?"display=\"block\"":"") + "xmlns=\"http://www.w3.org/1998/Math/MathML\">" | |
var footer ="</math>" | |
return header + str + footer; | |
} | |
function showMath(str) { | |
var math = $("mathcanvas"); | |
math.innerHTML = EncloseMath(str,true); | |
} | |
function onClear() { | |
var con = $("console"); | |
con.value = ''; | |
} | |
function ClearMathCanvas(){ | |
var input = $('input'); | |
input.value = ""; | |
var canvas = $('mathcanvas'); | |
canvas.innerHTML = ""; | |
} | |
/* | |
---------------------------------------------------------------------------- | |
HTML related done. Update related start. | |
---------------------------------------------------------------------------- | |
*/ | |
var g_sexp; | |
function UpdateInputToCanvas() { | |
g_selected = undefined; | |
g_sexp = JsonParser($("input").value); | |
var result = JsonToMathML(g_sexp); | |
dp(result); | |
showMath(result); | |
} | |
function UpdateSelection() { | |
if(g_selected) { | |
var elem = $(g_selected.obj.objId); | |
elem.setAttribute("class", "selectedTarget"); | |
g_selected.elem = elem; | |
} | |
} | |
function Update(sexp) { | |
var result = JsonToMathML(sexp); | |
dp(result); | |
showMath(result); | |
UpdateSelection(); | |
$("input").value = SexpSerializer(sexp); | |
} | |
/* | |
---------------------------------------------------------------- | |
Update related done. Prity Print related start. | |
---------------------------------------------------------------- | |
*/ | |
function HasArrayChild(sexp) { | |
for(var i = 0; i < sexp.length; i++) { | |
if(sexp[i].length > 1) { | |
return true; | |
} | |
} | |
return false; | |
} | |
function PushIndent(builder, level) { | |
for(var i = 0; i < level; i++) { | |
builder.push(" "); | |
} | |
} | |
function InnerSexpSerializer(sexp, builder, level) { | |
if(sexp instanceof Number) { | |
PushIndent(builder, level); | |
builder.push(sexp.toString()); | |
return; | |
} | |
if(sexp instanceof String) { | |
PushIndent(builder, level); | |
builder.push('"'); | |
builder.push(sexp); | |
builder.push('"'); | |
return; | |
} | |
if(sexp instanceof Array) { | |
PushIndent(builder, level); | |
builder.push("["); | |
assertFalse(sexp[0] instanceof Array, "head of array must not array."); | |
InnerSexpSerializer(sexp[0], builder, 0); | |
if(HasArrayChild(sexp)) { | |
for(var i = 1; i < sexp.length; i++) { | |
builder.push(",\n"); | |
InnerSexpSerializer(sexp[i], builder, level+1); | |
} | |
builder.push("\n"); | |
PushIndent(builder, level); | |
builder.push("]"); | |
} | |
else { | |
for(var i = 1; i < sexp.length; i++) { | |
builder.push(","); | |
InnerSexpSerializer(sexp[i], builder, 0); | |
} | |
builder.push("]"); | |
} | |
return; | |
} | |
alert(typeof(sexp)); | |
throw "unknown atom type inside S Expression Serializer"; | |
} | |
function SexpSerializer(sexp) { | |
var builder = []; | |
var level = 0; | |
InnerSexpSerializer(sexp, builder, level); | |
return builder.join(""); | |
} | |
/* | |
---------------------------------------------------------------- | |
Prity Print related done. | |
---------------------------------------------------------------- | |
*/ | |
/* | |
---------------------------------------------------------------- | |
Command related start. | |
---------------------------------------------------------------- | |
*/ | |
// -------- CutBuffer related Command. --------------------- | |
function CopyRawExpression(raw) { | |
var par = $('cutbuffer'); | |
var entry = CE("li"); | |
SaveExpression(entry, raw); | |
if(par.firstChild) { | |
par.insertBefore(entry, par.firstChild); | |
} else { | |
par.appendChild(entry); | |
} | |
} | |
function CutBufferMoveForward() { | |
var cutbuf = $('cutbuffer'); | |
var li = cutbuf.firstChild; | |
// move to last | |
if(li) | |
cutbuf.appendChild(li); | |
} | |
function CutBufferMoveBackword() { | |
var cutbuf = $('cutbuffer'); | |
var li = cutbuf.lastChild; | |
// move to first | |
if(li) | |
cutbuf.insertBefore(li, cutbuf.firstChild); | |
} | |
function CutBufferRotate(isBackward) { | |
if(isBackward) { | |
CutBufferMoveBackword(); | |
}else{ | |
CutBufferMoveForward(); | |
} | |
} | |
function Copy(sexp) { | |
var raw = SexpSerializer(sexp); | |
CopyRawExpression(raw); | |
} | |
function MoveUpExpression(targetExp) { | |
var parent = targetExp.parent; | |
if(!parent.parent) | |
g_sexp = targetExp; | |
else { | |
Replace(parent.parent, parent, targetExp); | |
} | |
g_selected = {obj: targetExp}; | |
} | |
function Cut(sexp) { | |
Copy(sexp); | |
var parent = sexp.parent; | |
if(!parent) | |
return ; | |
if((parent[0] != "+") && (parent[0] != "*") && (parent[0] != ",")) | |
return; | |
if(g_selected.obj = sexp) | |
g_selected = undefined; | |
var i = FindIndex(parent, sexp); | |
parent.splice(i, 1); | |
if(parent[0] == ",") | |
return; | |
// only one term remains. go up! | |
if(parent.length == 2) { | |
var single = parent[1]; | |
MoveUpExpression(single); | |
} | |
} | |
// ---------------- Replace Expression related --------- | |
function ReplaceExpression(targetSexp, newExp) { | |
if(g_selected && g_selected.obj == targetSexp) | |
g_selected = {obj: newExp}; // caution! no elem until UpdateSelection! | |
if(!targetSexp.parent) { | |
// targetSexp is single term. so just replace g_sexp | |
g_sexp = newExp; | |
return; | |
} | |
var parent = targetSexp.parent; | |
Replace(parent, targetSexp, newExp); | |
return newExp | |
} | |
// orgExp is already inside newSexp. | |
function ReplaceWithWrappedExpression(newSexp, orgExp) { | |
// use sel.obj.parent inside ReplaceExpression. So update after. | |
ReplaceExpression(orgExp, newSexp); | |
orgExp.parent = newSexp; | |
} | |
// --- replace expression's command below --- | |
function ReplaceWithFirstQueryLast(sexp, raw) { | |
ReplaceWithNthQueryLast(sexp, raw, 1); | |
} | |
function ReplaceWithFirst(sexp, raw) { | |
ReplaceWithNth(sexp, raw, 1); | |
} | |
function ReplaceWithNthQueryLast(sexp, raw, nth) { | |
var inputStr = QueryInput("Symbol", ""); | |
if(inputStr) { | |
var newSexp = JsonParser(raw); | |
var atom = ParseInputToTerm(inputStr); | |
Replace(newSexp, newSexp[newSexp.length-1], atom); | |
newSexp[nth] = sexp; | |
ReplaceWithWrappedExpression(newSexp, sexp); | |
g_selected = {obj: atom}; | |
} | |
} | |
function ReplaceWithNth(sexp, raw, nth) { | |
var newSexp = JsonParser(raw); | |
newSexp[nth] = sexp; | |
ReplaceWithWrappedExpression(newSexp, sexp); | |
} | |
function AddArgumentAt(selObj, argIndex) { | |
var inputStr = QueryInput("New symbol", ""); | |
if(!inputStr) | |
return; | |
var atom = ParseInputToTerm(inputStr); | |
if(selObj[argIndex] instanceof Array && | |
selObj[argIndex][0] == ",") { | |
atom.parent = selObj[argIndex]; | |
selObj[argIndex].push(atom); | |
} else { | |
var newSexp = JsonParser('[",", "dummy", "x"]'); | |
Replace(newSexp, newSexp[newSexp.length-1], atom); | |
newSexp.parent = selObj; | |
selObj[argIndex].parent = newSexp; | |
newSexp[1] = selObj[argIndex]; | |
selObj[argIndex] = newSexp; | |
} | |
} | |
function AddArgument(sel) { | |
var selObj = sel.obj; | |
if(selObj[0] != "fun" && selObj[0] != "max" && selObj[0] != "min") { | |
alert("argument cannot add to " + selObj[0]); | |
return; | |
} | |
var argIndex; | |
if(selObj[0] == "fun") { | |
argIndex = 2; | |
} else { | |
argIndex = 1; | |
} | |
return AddArgumentAt(selObj, argIndex); | |
} | |
function Minus(sel) { | |
if(sel.obj[0] == "-") { | |
var selected_obj = sel.obj; | |
var par = selected_obj.parent; | |
selected_obj[1].parent = par; | |
Replace(par, selected_obj, selected_obj[1]); | |
if(g_selected.obj == selected_obj) { | |
g_selected = {obj: selected_obj[1]}; // caution! no elem until UpdateSelection! | |
} | |
} else { | |
var newSexp = JsonParser('["-", "dummy"]'); | |
newSexp[1] = sel.obj; | |
ReplaceWithWrappedExpression(newSexp, sel.obj); | |
} | |
} | |
function SwitchSelection(sexp) { | |
if(sexp[0] == "=") { | |
swap(sexp, 1, 2); | |
return; | |
} else { | |
FracUpsideDown(g_selected.obj); | |
} | |
} | |
function FracUpsideDown(sexp) { | |
if(sexp[0] != "/") { | |
ReplaceWithNth(sexp, '["/", "1", "dummy"]', 2); | |
return; | |
} | |
swap(sexp, 1, 2); | |
if(sexp[2] == 1) { | |
MoveUpExpression(sexp[1]); | |
return; | |
} | |
} | |
function Flatten(sel) { | |
var sexp = sel.obj; | |
var tag = sexp[0]; | |
var parent = sexp.parent; | |
if(parent[0].toString() != tag.toString()) { | |
alert("parent and selected region not the same tag: " + tag + " & " + parent[0]); | |
return; | |
} | |
var index = FindIndex(parent, sexp); | |
var args = [index, 1]; | |
for(var i = 1; i < sexp.length; i++) { | |
sexp[i].parent = parent; | |
args.push(sexp[i]); | |
} | |
parent.splice.apply(parent, args); | |
g_selected = undefined; | |
} | |
function ParseInputToTerm(inputStr) { | |
var num = parseFloat(inputStr); | |
var atom; | |
if(!isNaN(num)) | |
{ | |
atom = new Number(num); | |
} else { | |
atom = new String(inputStr); | |
} | |
RegisterObjectToHash(atom); | |
return atom; | |
} | |
function ReplaceTermByInput(inputStr) { | |
var atom = ParseInputToTerm(inputStr); | |
var selObj = g_selected.obj; | |
Replace(selObj.parent, selObj, atom); | |
g_selected = {obj: atom }; | |
return; | |
} | |
// ------------------------ Replace related done ---------------------------- | |
function Paste(targetSexp) { | |
var raw = HeadRaw(); | |
if(raw) { | |
if(targetSexp.parent){ | |
UnSelectExpression(true); | |
var newCell = ReplaceExpression(targetSexp, JsonParser(raw)); | |
} else { | |
g_selected = undefined; | |
$('input').value = raw; | |
g_sexp = JsonParser($("input").value); | |
} | |
} | |
} | |
// ---------------- Move Terminal related --------------------- | |
function FindIndex(arr, obj) { | |
for(var i = 0; i < arr.length; i++) { | |
if(arr[i] == obj) | |
return i; | |
} | |
return -1; | |
} | |
function Replace(parent, target, newTarget) { | |
if(!parent) { | |
g_sexp = newTarget; | |
g_selected = undefined; | |
return; | |
} | |
var i = FindIndex(parent, target); | |
parent[i] = newTarget; | |
newTarget.parent = parent; | |
} | |
function swap(array, i, j) { | |
var iobj = array[i]; | |
array[i] = array[j]; | |
array[j] = iobj; | |
} | |
function MoveLeftTermInternal(parent_sexp, target_sexp) { | |
var i = FindIndex(parent_sexp, target_sexp); | |
if(i == 1) | |
return; | |
assertEq(parent_sexp[i], target_sexp); | |
swap(parent_sexp, i, i-1); | |
} | |
function MoveLeftTerm(sel) { | |
if(!sel.obj.parent) | |
return; | |
MoveLeftTermInternal(sel.obj.parent, sel.obj); | |
} | |
function MoveRightTermInternal(parent_sexp, target_sexp) { | |
var i = FindIndex(parent_sexp, target_sexp); | |
if(i == parent_sexp.length -1 ) | |
return; | |
assertEq(parent_sexp[i], target_sexp); | |
swap(parent_sexp, i, i+1); | |
} | |
function MoveRightTerm(sel) { | |
if(!sel.obj.parent) | |
return; | |
MoveRightTermInternal(sel.obj.parent, sel.obj); | |
} | |
// ------------------------ history related ------------------------ | |
function SaveExpression(entry, raw) { | |
var hidden = CE('input'); | |
hidden.type = 'hidden'; | |
hidden.name = 'raw_exp'; | |
hidden.value = raw; | |
entry.appendChild(hidden); | |
var span = CE('span'); | |
span.innerHTML = EncloseMath(JsonToMathML(JsonParser(raw))); | |
entry.appendChild(span); | |
} | |
function Register(raw){ | |
var history = $('history'); | |
var history_entry = CE('div'); | |
history_entry.contentEditable=false; | |
history_entry.setAttribute('style', "background-color:Aqua; word-wrap:normal;"); | |
var form = CE('form'); | |
form.innerHTML='<input type="button" value="O" onclick="OverwriteClicked(event.target)">'; | |
var input = CE('input'); | |
input.type = 'button'; | |
input.value = 'revive'; | |
input.setAttribute('onclick','Revive(event)'); | |
form.appendChild(input); | |
SaveExpression(form, raw); | |
history_entry.appendChild(form); | |
history.appendChild(history_entry); | |
history.appendChild(CE('br')); | |
// should I return history_entry? | |
return form; | |
} | |
function GetHistoryForms() { | |
var history = $('history'); | |
return history.getElementsByTagName("form"); | |
} | |
function HeadRaw() { | |
var cutbuf = $('cutbuffer'); | |
var li = cutbuf.firstChild | |
if(li) { | |
var inp = li.getElementsByTagName("input")[0]; | |
return inp.value; | |
} | |
return undefined; | |
} | |
function OverwriteClicked(buttonElem){ | |
var raw = $('input').value; | |
var form = buttonElem.parentNode; | |
form.elements.namedItem('raw_exp').value = raw; | |
form.getElementsByTagName('span')[0].innerHTML = EncloseMath(JsonToMathML(JsonParser(raw))); | |
} | |
function ReviveForm(form) { | |
var input = $('input'); | |
input.value = form.elements.namedItem('raw_exp').value; | |
UpdateInputToCanvas(); | |
} | |
function Revive(e){ | |
var target = e.target; | |
var form = target.form; | |
ReviveForm(form); | |
} | |
// ------------------------Export, Import, Save, Load ---------------------------- | |
function Export(){ | |
var forms = GetHistoryForms(); | |
var builder = []; | |
builder.push('[['); | |
for(var i = 0; i < forms.length; i++){ | |
builder.push("'"); | |
builder.push(encodeURI(forms.item(i).elements.namedItem('raw_exp').value)); | |
builder.push("'"); | |
builder.push(','); | |
} | |
builder.push('],['); | |
var cutbufEnts = $('cutbuffer').getElementsByTagName("li"); | |
for(var i = 0; i < cutbufEnts.length; i++) { | |
builder.push("'"); | |
builder.push(encodeURI(cutbufEnts.item(i).getElementsByTagName('input')[0].value)); | |
builder.push("'"); | |
builder.push(','); | |
} | |
builder.push(']]'); | |
prompt("Export", builder.join("")); | |
} | |
function Import(){ | |
var input = QueryInput("Import", $('import_data_hidden').value); | |
if(!input) | |
return; | |
var array = eval(input); | |
for(var i=0; i < array[0].length; i++){ | |
Register(decodeURI(array[0][i])); | |
} | |
for(var i = 0;i < array[1].length; i++) { | |
var j = array[1].length -i-1; | |
CopyRawExpression(decodeURI(array[1][j])); | |
} | |
} | |
function Save(){ | |
ClearMathCanvas(); | |
var console = $('console'); | |
console.value = "<html><head>\n" + document.head.innerHTML + "</head><body onload=\"onload()\">\n" + document.body.innerHTML + "</body></html>"; | |
} | |
function FrozenedContent(){ | |
var console = $('console'); | |
console.value =''; | |
var his = $('history'); | |
var hidden = $('import_data_hidden'); | |
var maths = his.getElementsByTagName("math"); | |
var div = CE('div'); | |
div.appendChild(hidden.cloneNode(true)); | |
var ul = CE('ul'); | |
for(var i = 0; i < maths.length; i++) { | |
var li = CE('li'); | |
li.appendChild(maths.item(i).cloneNode(true)); | |
ul.appendChild(li); | |
} | |
div.appendChild(ul); | |
console.value = div.innerHTML; | |
} | |
function GetDocumentFromString(str){ | |
var iframe = $('iframe'); | |
var tmpDoc = iframe.contentDocument; | |
tmpDoc.open(); | |
tmpDoc.write(str); | |
tmpDoc.close(); | |
return tmpDoc; | |
} | |
function Load(){ | |
var file = $('load_file').files[0]; | |
if(file){ | |
var reader = new FileReader(); | |
reader.readAsText(file, "UTF-8"); | |
reader.onload = function (){ | |
var doc = GetDocumentFromString(reader.result); | |
var history = doc.getElementById('history'); | |
var inputs = history.getElementsByTagName('input'); | |
for(var i = 0; i < inputs.length; i++){ | |
var input = inputs.item(i); | |
if(input.type == 'hidden' && input.name == 'raw_exp' && input.value){ | |
Register(input.value); | |
} | |
} | |
var cutbuffer = doc.getElementById('cutbuffer'); | |
var inputs = cutbuffer.getElementsByTagName('input'); | |
for(var i = inputs.length - 1; i >= 0; i --){ | |
var input = inputs.item(i); | |
if(input.type == 'hidden' && input.name == 'raw_exp' && input.value){ | |
CopyRawExpression(input.value); | |
} | |
} | |
}; | |
} | |
} | |
/* | |
--------------------------------------------------------- | |
Selection related | |
--------------------------------------------------------- | |
*/ | |
var g_selected = undefined; | |
function UnSelectExpression(notClear) { | |
if(g_selected && g_selected.elem) { | |
g_selected.elem.setAttribute("class", ""); | |
} | |
if(!notClear) | |
g_selected = undefined; | |
return false; | |
} | |
function WidenSelection(sel) { | |
var par = sel.obj.parent; | |
if(!par) | |
return; | |
if(!par.objId) | |
return; | |
var par_elem = $(par.objId); | |
if(!par_elem) | |
return; | |
g_selected = {obj: par, elem: par_elem }; | |
} | |
function SelectAll() { | |
var root = $(g_sexp.objId); | |
g_selected = {obj: g_sexp, elem: root}; | |
} | |
/* | |
--------------------------------------------------------- | |
DOM Event Handler related | |
--------------------------------------------------------- | |
*/ | |
var KEY_ENTER = 13; | |
var KEY_LEFT = 37; | |
var KEY_RIGHT = 39; | |
var KEY_A = 65; | |
var KEY_C = 67; | |
var KEY_D = 68; | |
var KEY_E = 69; | |
var KEY_F = 70; | |
var KEY_G = 71; | |
var KEY_I = 73; | |
var KEY_J = 74; | |
var KEY_L = 76; | |
var KEY_M = 77; | |
var KEY_O = 79; | |
var KEY_P = 80; | |
var KEY_R = 82; | |
var KEY_S = 83; | |
var KEY_T = 84; | |
var KEY_V = 86; | |
var KEY_W = 87; | |
var KEY_X = 88; | |
function BodyOnKeyUp(evt) { | |
if(g_inputFocus) | |
return; | |
if(evt.keyCode == KEY_A) { | |
SelectAll(); | |
UpdateSelection(); | |
return; | |
} | |
if(evt.keyCode == KEY_I && | |
evt.shiftKey) { | |
Import(); | |
return; | |
} | |
if(evt.keyCode == KEY_E && | |
evt.shiftKey) { | |
Export(); | |
return; | |
} | |
if(evt.keyCode == KEY_P){ | |
CutBufferRotate(evt.shiftKey); | |
return; | |
} | |
if(evt.keyCode == KEY_L){ | |
ReplaceWithFirstQueryLast(g_selected.obj, '["<=", "dummy", "x"]'); | |
Update(g_sexp); | |
return; | |
} | |
if(evt.keyCode == KEY_G){ | |
ReplaceWithFirstQueryLast(g_selected.obj, '[">=", "dummy", "x"]'); | |
Update(g_sexp); | |
return; | |
} | |
if(!g_selected) { | |
if(evt.keyCode == KEY_C) { | |
CopyRawExpression($('input').value); | |
return; | |
} | |
} | |
if(!g_selected) | |
return; | |
if(evt.keyCode == KEY_D && | |
evt.shiftKey) { | |
ReplaceWithFirstQueryLast(g_selected.obj, '["part", "dummy", "t"]'); | |
Update(g_sexp); | |
return; | |
} | |
if(evt.keyCode == KEY_M) { | |
if(evt.shiftKey) { | |
ReplaceWithFirstQueryLast(g_selected.obj, '["min", "dummy", "x"]'); | |
} else { | |
ReplaceWithFirstQueryLast(g_selected.obj, '["max", "dummy", "x"]'); | |
} | |
Update(g_sexp); | |
return; | |
} | |
if(evt.keyCode == KEY_I && !evt.shiftKey) { | |
ReplaceWithFirstQueryLast(g_selected.obj, '["int", "dummy", "t", 0, "x"]'); | |
Update(g_sexp); | |
return; | |
} | |
if(evt.keyCode == KEY_E && !evt.shiftKey) { | |
Flatten(g_selected); | |
Update(g_sexp); | |
return; | |
} | |
if(evt.keyCode == KEY_F) { | |
ReplaceWithFirstQueryLast(g_selected.obj, '["fun", "dummy", "x"]'); | |
Update(g_sexp); | |
return; | |
} | |
if(evt.keyCode == KEY_T) { | |
ReplaceWithFirst(g_selected.obj, '["dotover", "dummy"]'); | |
Update(g_sexp); | |
return; | |
} | |
if(evt.keyCode == KEY_W) { | |
WidenSelection(g_selected); | |
Update(g_sexp); | |
return; | |
} | |
if(evt.keyCode == KEY_C) { | |
Copy(g_selected.obj); | |
return; | |
} | |
if(evt.keyCode == KEY_R) { | |
var inputStr = QueryInput("Replace?", ""); | |
if(inputStr) { | |
ReplaceTermByInput(inputStr); | |
Update(g_sexp); | |
} | |
return; | |
} | |
if(evt.keyCode == KEY_S) { | |
SwitchSelection(g_selected.obj); | |
Update(g_sexp); | |
return; | |
} | |
if(evt.keyCode == KEY_X) { | |
Cut(g_selected.obj); | |
Update(g_sexp); | |
return; | |
} | |
if(evt.keyCode == KEY_V){ | |
Paste(g_selected.obj); | |
Update(g_sexp); | |
return; | |
} | |
} | |
function BodyOnKeyPress(evt) { | |
if(g_inputFocus) | |
return; | |
if(evt.charCode == "=".charCodeAt(0)) { | |
ReplaceWithFirstQueryLast(g_selected.obj, '["=", "dummy", "x"]'); | |
Update(g_sexp); | |
return; | |
} | |
if(evt.charCode == "<".charCodeAt(0)) { | |
ReplaceWithFirstQueryLast(g_selected.obj, '["<", "dummy", "x"]'); | |
Update(g_sexp); | |
return; | |
} | |
if(evt.charCode == ">".charCodeAt(0)) { | |
ReplaceWithFirstQueryLast(g_selected.obj, '[">", "dummy", "x"]'); | |
Update(g_sexp); | |
return; | |
} | |
if (!g_selected) { | |
if(evt.keyCode == KEY_ENTER){ | |
Register($("input").value); | |
return; | |
} else if(evt.charCode == "+".charCodeAt(0)) { | |
$('input').value = '"x"'; | |
UpdateInputToCanvas(); | |
return; | |
} | |
} | |
if(!g_selected) | |
return; | |
if(evt.keyCode == KEY_LEFT) { | |
MoveLeftTerm(g_selected); | |
Update(g_sexp); | |
} else if (evt.keyCode == KEY_RIGHT) { | |
MoveRightTerm(g_selected); | |
Update(g_sexp); | |
} else if (evt.keyCode == KEY_ENTER){ | |
var raw = SexpSerializer(g_selected.obj); | |
var form = Register(raw); | |
ReviveForm(form); | |
} else if(evt.charCode == "+".charCodeAt(0)) { | |
ReplaceWithFirstQueryLast(g_selected.obj, '["+", "dummy", "y"]'); | |
Update(g_sexp); | |
} else if(evt.charCode == "-".charCodeAt(0)) { | |
Minus(g_selected); | |
Update(g_sexp); | |
} else if(evt.charCode == "*".charCodeAt(0)) { | |
ReplaceWithFirstQueryLast(g_selected.obj, '["*", "dummy", "y"]'); | |
Update(g_sexp); | |
} else if(evt.charCode == "/".charCodeAt(0)) { | |
ReplaceWithFirstQueryLast(g_selected.obj, '["/", "dummy", "y"]'); | |
Update(g_sexp); | |
} else if(evt.charCode == "_".charCodeAt(0)) { | |
ReplaceWithFirstQueryLast(g_selected.obj, '["_", "dummy", "t"]'); | |
Update(g_sexp); | |
} else if(evt.charCode == "^".charCodeAt(0)) { | |
ReplaceWithFirstQueryLast(g_selected.obj, '["pow", "dummy", "t"]'); | |
Update(g_sexp); | |
} else if(evt.charCode == ",".charCodeAt(0)) { | |
AddArgument(g_selected); | |
Update(g_sexp); | |
} | |
// alert(evt.charCode); | |
// alert(evt.keyCode); | |
} | |
var g_inputFocus = false; | |
function OnInputFocus() { | |
g_inputFocus = true; | |
UnSelectExpression(); | |
} | |
function OnInputBlur() { | |
g_inputFocus = false; | |
} | |
function OnInputKeyDown(evt) { | |
if(evt.keyCode == KEY_J && evt.ctrlKey) | |
{ | |
evt.stopPropagation(); | |
evt.preventDefault(); | |
UpdateInputToCanvas(); | |
return false; | |
} | |
return true; | |
} | |
/* | |
--------------------------------------------------------- | |
Touch UI | |
--------------------------------------------------------- | |
*/ | |
function TranslateAndDispatchKeyEvent(key){ | |
if (key.length > 0) { | |
var event = document.createEvent('KeyboardEvent'); | |
var keyCode =KEY_A; | |
var shiftKey =false; | |
if(key.length >= 2){ | |
if(key == "LEFT") { | |
keyCode =KEY_LEFT; | |
} else if(key == "RIGHT") { | |
keyCode = KEY_RIGHT; | |
} | |
} else { | |
keyCode = key.charCodeAt(0); | |
shiftKey = ('A' <= key && key <= 'Z'); | |
if('a' <=key && key <= 'z') keyCode = key.toUpperCase().charCodeAt(0); | |
} | |
event.initKeyEvent('keypress', true, true, null, false, false, shiftKey, false, keyCode, keyCode); | |
document.body.dispatchEvent(event); | |
event.initKeyEvent('keyup', true, true, null, false, false, shiftKey, false, keyCode, keyCode); | |
document.body.dispatchEvent(event); | |
} else { | |
alert("Not implemented!"); | |
} | |
} | |
function HaloButtonClicked(ev) { | |
var key = ev.target.getAttribute('data-keycode'); | |
TranslateAndDispatchKeyEvent(key); | |
} | |
function InitHalo() { | |
var buttons = document.querySelectorAll('#halo input[type="button"]'); | |
for (var i = 0; i < buttons.length; i++) { | |
buttons[i].addEventListener('click', HaloButtonClicked); | |
} | |
} | |
/* | |
handler.singleTap handler.multiTap | |
*/ | |
function CreateMultiTouchListener(elem, handler){ | |
function largeEnough(delta){ | |
return Math.abs(delta) >10.0; | |
} | |
function isTheSameDirection(dx1, dy1, dx2, dy2) { | |
return dx1*dx2 >=0 && dy1*dy2 >= 0; | |
} | |
function isOppositeDirection(dx1, dy1, dx2, dy2) { | |
return dx1*dx2 <=0 && dy1*dy2 <=0; | |
} | |
function smallEnough(delta){ | |
return Math.abs(delta) < 4.0; | |
} | |
function oneDirectionIsLargeEnough(dx1, dy1, dx2, dy2) { | |
return (largeEnough(dx1) && largeEnough(dx2)) || | |
(largeEnough(dy1) && largeEnough(dy2)); | |
} | |
var STATUS_UNDEFINED =1; | |
var STATUS_GESTURE_FIRED = 2; | |
var STATUS_SINGLE_TAP =3; | |
var STATUS_MULTI_TAP = 4; | |
var status = STATUS_UNDEFINED; | |
var lastTouch =[{x:0, y:0}, {x:0, y:0}]; | |
var offsetX = elem.offsetLeft; | |
var offsetY = elem.offsetTop; | |
var initialX1; | |
var initialY1; | |
var initialX2; | |
var initialY2; | |
function isMulti(status) { | |
return status == STATUS_MULTI_TAP; | |
} | |
function touchToXY(touch){ | |
return {x: touch.pageX-offsetX, y:touch.pageY-offsetY} | |
} | |
function backupLastTouch(evt) { | |
lastTouch = []; | |
for(var i = 0; i <evt.touches.length; i++){ | |
lastTouch.push(touchToXY(evt.touches[i])); | |
} | |
} | |
return { | |
touchstart: function(evt){ | |
evt.preventDefault(); | |
backupLastTouch(evt); | |
if(evt.touches.length == 1){ | |
status = STATUS_SINGLE_TAP; | |
initialX1 = lastTouch[0].x; | |
initialY1 = lastTouch[0].y; | |
} else if(evt.touches.length == 2){ | |
status = STATUS_MULTI_TAP; | |
initialX1 = lastTouch[0].x; | |
initialY1 = lastTouch[0].y; | |
initialX2 = lastTouch[1].x; | |
initialY2 = lastTouch[1].y; | |
} | |
}, | |
touchmove: function(evt) { | |
evt.preventDefault(); | |
if(status == STATUS_GESTURE_FIRED) { | |
return; | |
} | |
backupLastTouch(evt); | |
if(evt.touches.length == 1) { | |
if(isMulti(status)) { | |
return; | |
} | |
var dx = lastTouch[0].x- initialX1 ; | |
var dy = lastTouch[0].y- initialY1 ; | |
if(largeEnough(dx) || largeEnough(dy)) { | |
if(largeEnough(dx)) { | |
if(dx > 0){ | |
handler.moveRight(); | |
} else { | |
handler.moveLeft(); | |
} | |
status = STATUS_GESTURE_FIRED | |
return; | |
} | |
} | |
} else if(evt.touches.length == 2) { | |
var dx1 = lastTouch[0].x- initialX1 ; | |
var dy1 = lastTouch[0].y- initialY1 ; | |
var dx2 = lastTouch[1].x- initialX2 ; | |
var dy2 = lastTouch[1].y- initialY2 ; | |
if(isOppositeDirection(dx1, dy1, dx2, dy2)) { | |
if(dx1 < 0) { | |
handler.pinchOut(); | |
} else { | |
handler.pinchIn(); | |
} | |
status = STATUS_GESTURE_FIRED; | |
} | |
} | |
}, | |
touchend: function(evt) { | |
evt.preventDefault(); | |
if(status == STATUS_GESTURE_FIRED || | |
status == STATUS_UNDEFINED) { | |
return; | |
} | |
if(status == STATUS_SINGLE_TAP){ | |
handler.singleTap(lastTouch[0]); | |
status = STATUS_GESTURE_FIRED; | |
return; | |
} | |
if(status == STATUS_MULTI_TAP){ | |
handler.multiTap(lastTouch); | |
status = STATUS_GESTURE_FIRED; | |
return; | |
} | |
}, | |
}; | |
} | |
function InitGesture() { | |
var div = $("gesturearea"); | |
var handler = {singleTap: function(evt){ | |
dp("tap: x=" + evt.x + ", y=" +evt.y ); | |
}, | |
multiTap: function(evts){ | |
TranslateAndDispatchKeyEvent("w"); | |
}, | |
moveRight: function() { | |
TranslateAndDispatchKeyEvent("RIGHT"); | |
}, | |
moveLeft: function() { | |
TranslateAndDispatchKeyEvent("LEFT"); | |
}, | |
pinchOut: function() { | |
TranslateAndDispatchKeyEvent("a"); | |
}, | |
pinchIn: function() { | |
dp("pinch in"); | |
} | |
}; | |
var multiListener = CreateMultiTouchListener(div, handler); | |
div.addEventListener("touchstart", function(event) { | |
multiListener.touchstart(event); | |
}, false); | |
div.addEventListener("touchmove", function(event) { | |
multiListener.touchmove(event); | |
}, false); | |
div.addEventListener("touchend", function(event) { | |
multiListener.touchend(event); | |
}, false); | |
} | |
function onload() { | |
UnitTest(); | |
var canv = $("mathcanvas"); | |
canv.addEventListener("click", function(evt) { | |
UnSelectExpression(); | |
var id = evt.target.getAttribute("id"); | |
var obj = g_hash[id]; | |
if(!obj) { | |
return; | |
} | |
g_selected = {obj: obj, elem: evt.target}; | |
g_selected.elem.setAttribute("class", "selectedTarget"); | |
// alert(g_selected.elem); | |
}); | |
document.body.addEventListener("keyup", BodyOnKeyUp); | |
document.body.addEventListener("keypress", BodyOnKeyPress); | |
InitHalo(); | |
InitGesture(); | |
} | |
/* | |
--------------------------------------------------------------- | |
Unit Test realted start | |
--------------------------------------------------------------- | |
*/ | |
function assertMatch(expectPat, actual, msg) { | |
if(!actual.match(expectPat)) { | |
assertFail("pat not match. " + msg + ": pat[" + expectPat + "], actual [" + actual + "]"); | |
} | |
} | |
function verify(input, expectPat, needLog) { | |
var sexp = JsonParser(input); | |
var result = JsonToMathML(sexp); | |
var actual_pat = expectPat; | |
if(needLog) | |
dp(result); | |
assertMatch(actual_pat, result, input); | |
} | |
function EvalTest() { | |
verify('["pow", "e", "a"]', /<msup id="[0-9]*"><mi[^>]*>e<\/mi><mi[^>]*>a<\/mi><\/msup>/); | |
verify('["pow", "e", ["*", "a", "b"]]', /<msup id="[0-9]*"><mi[^>]*>e<\/mi><mrow[^>]*><mi[^>]*>a<\/mi><mi id="[0-9]*">b<\/mi><\/mrow><\/msup>/); | |
verify('["*", "e", ["-", "a"]]', /<mrow id="[0-9]*"><mi id="[0-9]*">e<\/mi><mfenced id="[0-9]*"><mrow><mrow id="[0-9]*"><mo[^>]*>-<\/mo><mi id="[0-9]*">a<\/mi><\/mrow><\/mrow><\/mfenced><\/mrow>/); | |
//<mn id="84">3</mn><mo>+</mo><mrow id="86"><mi id="87">e</mi><mfenced id="91"><mrow><mrow id="89"><mo[^>]*>-</mo><mi id="90">a</mi></mrow></mrow></mfenced></mrow> | |
verify('["+", 3, ["*", "e", ["-", "a"]]]', /<mn id="[0-9]*">3<\/mn><mo>\+<\/mo><mrow id="[0-9]*"><mi id="[0-9]*">e<\/mi><mfenced id="[0-9]*"><mrow><mrow id="[0-9]*"><mo[^>]*>-<\/mo><mi id="[0-9]*">a<\/mi><\/mrow><\/mrow><\/mfenced><\/mrow>/); | |
// <mn id="106">3</mn><mo>+</mo><mfenced id="114"><mrow><mrow id="108"><mrow id="110"><mo[^>]*>-</mo><mi id="111">a</mi></mrow><mi id="113">e</mi></mrow></mrow></mfenced> | |
verify('["+",3,["*",["-","a"], "e"]]', /<mn id="[0-9]*">3<\/mn><mo>\+<\/mo><mfenced id="[0-9]*"><mrow><mrow id="[0-9]*"><mrow id="[0-9]*"><mo[^>]*>-<\/mo><mi id="[0-9]*">a<\/mi><\/mrow><mi id="[0-9]*">e<\/mi><\/mrow><\/mrow><\/mfenced>/); | |
verify('["+", "λ", "a"]', /<mi id="[0-9]*">λ<\/mi><mo>\+<\/mo><mi id="[0-9]*">a<\/mi>/); | |
verify('["dotover", "x"]', /<mover[^>]*><mi id="[0-9]*">x<\/mi><mo>.<\/mo><\/mover>/); | |
verify('["int", ["+", "x", "y"], "t"]', /<mo id="[0-9]*">∫<\/mo><mrow id="[0-9]*"><mi id="[0-9]*">x<\/mi><mo>\+<\/mo><mi id="[0-9]*">y<\/mi><\/mrow><mi id="[0-9]*">dt<\/mi>/); | |
verify('["int", "x", "t"]', /<mo id="[0-9]*">∫<\/mo><mi id="[0-9]*">x<\/mi><mi id="[0-9]*">dt<\/mi>/); | |
verify('["int", "x", "t", 0, "∞"]', /<msubsup><mo id="[0-9]*">∫<\/mo><mn id="[0-9]*">0<\/mn><mi id="[0-9]*">∞<\/mi><\/msubsup><mi id="[0-9]*">x<\/mi><mi id="[0-9]*">dt<\/mi>/); | |
verify('["=", "x", "y"]', /<mi id="[0-9]*">x<\/mi><mo id="[0-9]*">=<\/mo><mi id="[0-9]*">y<\/mi>/); | |
verify('["=", "x", ["+", "y", "z"]]', /<mi id="[0-9]*">x<\/mi><mo id="[0-9]*">=<\/mo><mrow id="[0-9]*"><mi id="[0-9]*">y<\/mi><mo>\+<\/mo><mi id="[0-9]*">z<\/mi><\/mrow>/); | |
verify('["part", "y", "x"]', /<mfrac id="[0-9]*"><mrow><mo>∂<\/mo><mi id="[0-9]*">y<\/mi><\/mrow><mrow><mo>∂<\/mo><mi id="[0-9]*">x<\/mi><\/mrow><\/mfrac>/); | |
verify('["/", "x", "y"]', /<mfrac id="[0-9]*"><mrow><mi id="[0-9]*">x<\/mi><\/mrow><mrow><mi id="[0-9]*">y<\/mi><\/mrow><\/mfrac>/); | |
verify('["max", "y", "x"]', /<mrow[^>]*><munder[^>]*><mi[^>]*>max<\/mi><mi[^>]*>x<\/mi><\/munder><mfenced open="{" close="}"><mi[^>]*>y<\/mi><\/mfenced><\/mrow>/); | |
verify('["min", "y", "x"]', /<mrow[^>]*><munder[^>]*><mi[^>]*>min<\/mi><mi[^>]*>x<\/mi><\/munder><mfenced open="{" close="}"><mi[^>]*>y<\/mi><\/mfenced><\/mrow>/); | |
verify('["_", "x", "a"]', /<msub[^>]*><mi[^>]*>x<\/mi><mi[^>]*>a<\/mi><\/msub>/); | |
verify('["fun", "f", [",", "a", "b"]]', /<mrow id="[0-9]*"><mi id="[0-9]*">f<\/mi><mfenced[^>]*><mi[^>]*>a<\/mi><mi[^>]*>b<\/mi><\/mfenced><\/mrow>/); | |
// mfence is added to parent! | |
verify('[",", "a", "b"]', /<mi[^>]*>a<\/mi><mi[^>]*>b<\/mi>/); | |
verify('["<", "x", "y"]', /<mi id="[0-9]*">x<\/mi><mo id="[0-9]*"><<\/mo><mi id="[0-9]*">y<\/mi>/); | |
verify('["<=", "x", "y"]', /<mi id="[0-9]*">x<\/mi><mo id="[0-9]*">≤<\/mo><mi id="[0-9]*">y<\/mi>/); | |
verify('[">", "x", "y"]', /<mi id="[0-9]*">x<\/mi><mo id="[0-9]*">><\/mo><mi id="[0-9]*">y<\/mi>/); | |
verify('[">=", "x", "y"]', /<mi id="[0-9]*">x<\/mi><mo id="[0-9]*">≥<\/mo><mi id="[0-9]*">y<\/mi>/); | |
} | |
function verifySerializer(input, expect) { | |
var actual = SexpSerializer(JsonParser(input)); | |
assertEq(expect, actual); | |
} | |
function SerializerTest() { | |
verifySerializer(["a", 1, 2],'["a",1,2]'); | |
verifySerializer(["a", [1, 2]],'["a",\n [1,2]\n]'); | |
} | |
function UnitTest() { | |
try { | |
EvalTest(); | |
SerializerTest(); | |
dp("test sucess!"); | |
}catch(e) { | |
dp("test fail!"); | |
dp(e); | |
} | |
} | |
</script> | |
<br> | |
<a name="input"></a> | |
<input value="Register" type="button" onclick="Register($('input').value);"> | |
<br> | |
<div id="mathcanvas"></div> | |
<ul id="cutbuffer" contenteditable="true"></ul> | |
<hr> | |
<div id="gesturearea"></div> | |
<div id="halo"> | |
<table> | |
<tr> | |
<td><input class="halobutton" value="+" type="button" data-keycode="+"></td> | |
<td><input class="halobutton" value="*" type="button" data-keycode="*"></td> | |
<td><input class="halobutton" value="/" type="button" data-keycode="/"></td> | |
<td><input class="halobutton" value="-" type="button" data-keycode="-"></td> | |
<td><input class="halobutton" value="_" type="button" data-keycode="_"></td> | |
<td><input class="halobutton" value="^" type="button" data-keycode="^"></td> | |
</tr> | |
<tr> | |
<td><input class="halobutton" value="r" type="button" data-keycode="r"></td> | |
<td><input class="halobutton" value="e" type="button" data-keycode="e"></td> | |
<td><input class="halobutton" value="c" type="button" data-keycode="c"></td> | |
<td><input class="halobutton" value="p" type="button" data-keycode="p"></td> | |
<td><input class="halobutton" value="v" type="button" data-keycode="v"></td> | |
<td><input class="halobutton" value="x" type="button" data-keycode="x"></td> | |
</tr> | |
<tr> | |
<td><input class="halobutton" value="=" type="button" data-keycode="="></td> | |
<td><input class="halobutton" value=">" type="button" data-keycode=">"></td> | |
<td><input class="halobutton" value=">=" type="button" data-keycode="g"></td> | |
<td><input class="halobutton" value="<" type="button" data-keycode="<"></td> | |
<td><input class="halobutton" value="<=" type="button" data-keycode="l"></td> | |
<td><input class="halobutton" value="D" type="button" data-keycode="D"></td> | |
</tr> | |
<tr> | |
<td><input class="halobutton" value="f" type="button" data-keycode="f"></td> | |
<td><input class="halobutton" value="i" type="button" data-keycode="i"></td> | |
<td><input class="halobutton" value="max" type="button" data-keycode="m"></td> | |
<td><input class="halobutton" value="min" type="button" data-keycode="M"></td> | |
<td><input class="halobutton" value="," type="button" data-keycode=","></td> | |
<td><input class="halobutton" value="Inv" type="button" data-keycode="s"></td> | |
</tr> | |
<tr> | |
<td><input class="halobutton" value="a" type="button" data-keycode="a"></td> | |
<td><input class="halobutton" value="w" type="button" data-keycode="w"></td> | |
<td><input class="halobutton" value="<-" type="button" data-keycode="LEFT"></td> | |
<td><input class="halobutton" value="->" type="button" data-keycode="RIGHT"></td> | |
</tr> | |
</table> | |
</div> | |
<!-- <td><input value="Rel" type="button" data-keycode=""></td> | |
<td><input value="Fn" type="button" data-keycode=""></td> | |
--> | |
<input value="FrozenedContent" type="button" onclick="FrozenedContent()"> | |
<input value="Clear" type="button" onclick="onClear();"><br> | |
<textarea id="console" rows="10" cols="100" ></textarea><br> | |
<textarea id="input" rows="2" cols="100" onkeydown="OnInputKeyDown(event)" onfocus="OnInputFocus()" onblur="OnInputBlur()"></textarea><br> | |
<input value="Set" type="button" onclick="UpdateInputToCanvas();"> | |
<hr> | |
<h3>キーバインド</h3> | |
<dl> | |
<dt>+</dt><dd>選択範囲にqueryした式を+で追加。選択されていなければ全体をxに置き換える。</dd> | |
<dt>*</dt><dd>選択範囲にqueryした式を*で追加</dd> | |
<dt>/</dt><dd>選択範囲をyで割る(分数になる)</dd> | |
<dt>-</dt><dd>選択範囲を-にする(トグル)</dd> | |
<dt>_</dt><dd>選択範囲にqueryした式を_で追加(下付きの添え字)</dd> | |
<dt>,</dt><dd>選択範囲が関数かmaxの時、引数にqueryした式を追加</dd> | |
<dt>></dt><dd>選択範囲を左辺として、大なりの不等式を作る</dd> | |
<dt>g</dt><dd>選択範囲を左辺として、大なりイコールの不等式を作る</dd> | |
<dt><</dt><dd>選択範囲を左辺として、小なりの不等式を作る</dd> | |
<dt>l</dt><dd>選択範囲を左辺として、小なりイコールの不等式を作る</dd> | |
<dt>=</dt><dd>選択範囲を左辺として、等式を作る</dd> | |
<dt>a</dt><dd>全て選択</dd> | |
<dt>c</dt><dd>選択範囲をコピー領域に追加</dd> | |
<dt>e</dt><dd>選択範囲が親と同じ複数項演算子なら親の子に参入(expand)</dd> | |
<dt>f</dt><dd>選択範囲を名前とした関数にする</dd> | |
<dt>i</dt><dd>選択範囲をインテグラルで囲む</dd> | |
<dt>m</dt><dd>選択範囲をmaxで囲む</dd> | |
<dt>M</dt><dd>選択範囲をminで囲む</dd> | |
<dt>p</dt><dd>コピーバッファを回転。Shiftキーと同時押しで逆回転</dd> | |
<dt>r</dt><dd>inputダイアログを表示し、そこに入力されたテキスト(または数字)で選択範囲を置き換える</dd> | |
<dt>s</dt><dd>選択範囲を逆数にする</dd> | |
<dt>v</dt><dd>選択されている部分をコピーバッファの先頭で上書き</dd> | |
<dt>w</dt><dd>選択範囲を広げる</dd> | |
<dt>x</dt><dd>選択範囲をコピー領域に追加して選択範囲を削除(parentが+か*の時だけ)</dd> | |
<dt>D</dt><dd>選択要素を偏微分</dd> | |
<dt>E</dt><dd>Export</dd> | |
<dt>I</dt><dd>Import</dd> | |
<dt>C-j (textarea内)</dt><dd>テキストエリア内を評価してmathcanvasに表示</dd> | |
</dl> | |
<hr> | |
以下は適当な例。<br> | |
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"> | |
<mi>ℋ</mi> | |
<mfenced> | |
<mi>x</mi> | |
<mi>y</mi> | |
</mfenced> | |
</math> | |
<h3>ToDo</h3> | |
<ul> | |
<li>Registerされた式の削除、及びメモの編集追加。</li> | |
<li>exコマンド | |
<li>undoを付ける | |
<li>選択の移動(h, l?) | |
<ul> | |
<li>兄弟を移動 | |
<li>子供に入っていく(a_xでaからxに行く、とか。兄弟を移動と一緒にすべき?) | |
</ul> | |
<li>先頭を選択、というキーが要る | |
<li>選択範囲のwidenの逆 | |
<li>バグ: シングルクォートがexport時にエスケープされてない(からImportに失敗する)。 | |
<li>バグ?: entity referenceがexportすると解決されちゃう。 | |
<li>historyの式と式の間に変な空白がある | |
<li>1/aとかでa消したら1になって欲しい。指数乗も。 | |
<li>-(a+b)でa+bをcutしたらこの項全体消えてほしい(+の時は消える) | |
<li>reviveの操作性改善 | |
<ul> | |
<li><s>checkboxとボタンを無くす(代わりのブロックの色を変えて選択)</s> | |
<li><s>選択してr(仮)でrevive</s> (これ終わってるという認識でいいよね?) | |
<li>displayをinline-blockにしてみる | |
</ul> | |
<li>lim (リミット) | |
<li>空の時のvでl相当 (+してaしてvでいいんじゃね?) | |
<li>maxでwiden出来ないのを直す(もうなおった?) | |
<li>rでtex書式で入力出来たら神だなぁ | |
<li>historyに入ってるのをいちいちreviveせずにコピー出来たらなぁ | |
<li>式が整合的な状態で気軽にsnap shotを残す仕組み(Registerされた式の子供に入ってfoldingとか。チェックボックス復活?) | |
<li>+と*でarrayが伸びるようにする。 | |
<li><s>AddArgument (',')もqueryするように変更</s> | |
<li><s>=全体を選択してる時のswitch (s)は左右入れ替えか。</s> | |
<li><s>lを無くす</s> | |
<li><s>ハットとか、一旦tとかで出しちゃうんじゃなくて、何乗するかqueryする方がいいかも。</s> | |
<li><s>^を入れる</s> | |
<li><s>integralを入れる</s> | |
<li><s>((-a)*b)を-(a*b)にする</s>手動で出来るかカット | |
<li><s>/を入れる</s> | |
<li><s>文字の入力を入れる(prompt)</s> | |
<li><s>-がトグルする様にする</s> | |
<li><s>項の削除を入れる(仕様は要検討)</s> | |
<li><s>aで全体を選択</s> | |
<li><s>選択している部分を親のarrayに展開する</s> | |
<li><s>空の時の+で単項をロード</s> | |
</ul> | |
<iframe id='iframe' style="display:none;"></iframe> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment