initial version

This commit is contained in:
Fabian Jakobs 2010-04-02 15:39:45 +02:00
commit 7e462ec372
3 changed files with 656 additions and 0 deletions

35
Editor.mm Normal file
View file

@ -0,0 +1,35 @@
<map version="0.8.0">
<!-- To view this file, download free mind mapping software FreeMind from http://freemind.sourceforge.net -->
<node CREATED="1270106801605" ID="Freemind_Link_1615101381" MODIFIED="1270106809036" TEXT="Editor">
<node CREATED="1270106809716" ID="_" MODIFIED="1270106896494" POSITION="right" TEXT="core features">
<node CREATED="1270106812606" ID="Freemind_Link_941515617" MODIFIED="1270106824625" TEXT="text input"/>
<node CREATED="1270106825885" ID="Freemind_Link_871701424" MODIFIED="1270106831385" TEXT="key navigation"/>
<node CREATED="1270106831861" ID="Freemind_Link_497186050" MODIFIED="1270106836217" TEXT="shortcuts"/>
<node CREATED="1270106836717" ID="Freemind_Link_53741891" MODIFIED="1270106841480" TEXT="copy&amp;paste"/>
<node CREATED="1270106842020" ID="Freemind_Link_985111837" MODIFIED="1270106846968" TEXT="drag&amp;drop"/>
<node CREATED="1270106849069" ID="Freemind_Link_81832450" MODIFIED="1270106852800" TEXT="selection"/>
<node CREATED="1270106853508" ID="Freemind_Link_1490464696" MODIFIED="1270106861072" TEXT="syntax highlighting"/>
<node CREATED="1270106861565" ID="Freemind_Link_1400628964" MODIFIED="1270106864776" TEXT="line numbers"/>
<node CREATED="1270106865228" ID="Freemind_Link_1547807601" MODIFIED="1270106879192" TEXT="breakpoints (gutter)"/>
</node>
<node CREATED="1270106901715" ID="Freemind_Link_621059230" MODIFIED="1270106915606" POSITION="right" TEXT="additional features">
<node CREATED="1270106916179" ID="Freemind_Link_138436821" MODIFIED="1270106921566" TEXT="column highlight"/>
<node CREATED="1270106922066" ID="Freemind_Link_1177585742" MODIFIED="1270106928030" TEXT="rectangular selection"/>
<node CREATED="1270106928514" ID="Freemind_Link_1778337415" MODIFIED="1270106933390" TEXT="undo/redo"/>
<node CREATED="1270106936530" ID="Freemind_Link_1313653586" MODIFIED="1270106939485" TEXT="auto indent"/>
<node CREATED="1270106940626" ID="Freemind_Link_785606540" MODIFIED="1270106943653" TEXT="match braces"/>
<node CREATED="1270106944617" ID="Freemind_Link_265205410" MODIFIED="1270106951045" TEXT="auto insert of code blocks"/>
<node CREATED="1270106951682" ID="Freemind_Link_1064064918" MODIFIED="1270106955581" TEXT="autocomplete"/>
<node CREATED="1270106957130" ID="Freemind_Link_1118872285" MODIFIED="1270106964437" TEXT="mark-highlight-variables"/>
<node CREATED="1270106965257" ID="Freemind_Link_977412851" MODIFIED="1270106967828" TEXT="code folding"/>
<node CREATED="1270106969353" ID="Freemind_Link_1003992965" MODIFIED="1270106972165" TEXT="refactoring"/>
</node>
<node CREATED="1270106975057" ID="Freemind_Link_1471854995" MODIFIED="1270106981986" POSITION="right" TEXT="supported browsers">
<node CREATED="1270106983313" ID="Freemind_Link_594954564" MODIFIED="1270106988068" TEXT="IE 6-9"/>
<node CREATED="1270106988584" ID="Freemind_Link_1809785833" MODIFIED="1270107014323" TEXT="FF 3 &gt;= 3"/>
<node CREATED="1270107000488" ID="Freemind_Link_497511816" MODIFIED="1270107008460" TEXT="Webkit &gt;= 4"/>
<node CREATED="1270107015287" ID="Freemind_Link_417983587" MODIFIED="1270107017819" TEXT="Chrome"/>
<node CREATED="1270107018191" ID="Freemind_Link_393314987" MODIFIED="1270107038858" TEXT="Opera 9 or &gt;= 10?"/>
</node>
</node>
</map>

521
editor.html Normal file
View file

@ -0,0 +1,521 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Editor</title>
<meta name="author" content="Fabian Jakobs">
<style type="text/css" media="screen">
#container {
position: absolute;
width: 600px;
}
#canvas {
position: absolute;
border: 1px solid black;
margin: 4px;
width: 590px;
height: 400px;
overflow-x: auto;
overflow-y: auto;
font-family: Courier New;
white-space: nowrap;
}
#canvas.focus {
border: 1px solid #327fbd;;
}
.composition {
position: absolute;
text-decoration: underline;
}
.cursor {
position: absolute;
width: 1px;
background: black;
}
.line {
white-space: nowrap;
}
.line.odd {
background: #FAFAFA;
}
</style>
</head>
<body>
<div id="container">
<div id="canvas"></div>
</div>
<script type="text/javascript" charset="utf-8">
function addListener(elem, type, callback) {
if (elem.addEventListener) {
return elem.addEventListener(type, callback, false);
}
if (elem.attachEvent) {
elem.attachEvent("on" + type, function() {
callback(window.event);
});
}
}
function setText(elem, text) {
if (elem.innerText !== undefined) {
elem.innerText = text;
}
if (elem.textContent !== undefined) {
elem.textContent = text;
}
}
function stopEvent(e) {
stopPropagation(e);
preventDefault(e);
return false;
}
function stopPropagation(e) {
if (e.stopPropagation)
e.stopPropagation();
else
e.cancelBubble = true;
}
function preventDefault (e)
{
if (e.preventDefault)
e.preventDefault();
else
e.returnValue = false;
}
function TextInput(parentNode, host) {
var text = document.createElement("textarea");
var style = text.style;
style.position = "absolute";
style.left = "-10000px";
style.top = "-10000px";
parentNode.appendChild(text);
var inCompostion = false;
var onTextInput = function(e) {
setTimeout(function() {
if (!inCompostion) {
if (text.value) host.onTextInput(text.value);
text.value = "";
}
}, 0)
}
var onCompositionStart = function(e)
{
inCompostion = true;
if (text.value) host.onTextInput(text.value);
text.value = "";
host.onCompositionStart();
setTimeout(onCompositionUpdate, 0);
}
var onCompositionUpdate = function() {
host.onCompositionUpdate(text.value);
}
var onCompositionEnd = function()
{
inCompostion = false;
host.onCompositionEnd();
onTextInput();
}
addListener(text, "keypress", onTextInput, false);
addListener(text, "textInput", onTextInput, false);
addListener(text, "paste", onTextInput, false);
addListener(text, "propertychange", onTextInput, false);
addListener(text, "compositionstart", onCompositionStart, false);
addListener(text, "compositionupdate", onCompositionUpdate, false);
addListener(text, "compositionend", onCompositionEnd, false);
addListener(text, "blur", function() {
host.onBlur();
}, false);
addListener(text, "focus", function() {
host.onFocus();
}, false);
this.focus = function() {
text.focus();
}
this.blur = function() {
this.blur();
}
};
var keys = {
UP: 38,
RIGHT: 39,
DOWN: 40,
LEFT: 37,
POS1: 36,
END: 35,
DELETE: 46,
BACKSPACE: 8,
TAB: 9
}
function KeyBinding(element, host)
{
addListener(element, "keydown", function(e)
{
var key = e.keyCode;
switch (key)
{
case keys.UP:
host.moveUp();
return stopEvent(e);
case keys.DOWN:
host.moveDown();
return stopEvent(e);
case keys.LEFT:
host.moveLeft();
return stopEvent(e);
case keys.RIGHT:
host.moveRight();
return stopEvent(e);
case keys.POS1:
host.moveLineStart();
return stopEvent(e);
case keys.END:
host.moveLineEnd();
return stopEvent(e);
case keys.DELETE:
host.removeRight();
return stopEvent(e);
case keys.BACKSPACE:
host.removeLeft();
return stopEvent(e);
case keys.TAB:
host.onTextInput(" ");
return stopEvent(e);
}
});
};
function Editor(containerId, canvasId)
{
this.container = document.getElementById(containerId);
this.canvas = document.getElementById(canvasId);
var textInput = new TextInput(container, this);
new KeyBinding(this.container, this);
var self = this;
addListener(container, "mousedown", function(e) {
textInput.focus();
self.placeCursorToMouse(e.pageX, e.pageY);
return preventDefault(e);
});
this.measureSizes();
this.composition = document.createElement("div");
this.composition.className = "composition";
this.composition.style.height = this.lineHeight + "px";
this.cursor = document.createElement("div");
this.cursor.className = "cursor";
this.cursor.style.height = this.lineHeight + "px";
this.row = 0;
this.col = 0;
this.lines = [""];
this.draw();
}
Editor.prototype = {
measureSizes : function()
{
var measureNode = document.createElement("div");
var style = measureNode.style;
style.width = style.height = "auto";
style.left = style.top = "-1000px";
style.visibility = "hidden";
style.position = "absolute";
style.overflow = "visible";
measureNode.innerHTML = "X<br>X";
this.canvas.appendChild(measureNode);
this.lineHeight = Math.round(measureNode.offsetHeight / 2);
this.characterWidth = measureNode.offsetWidth;
this.canvas.removeChild(measureNode);
},
draw : function()
{
if (this.cursor.parentNode) {
this.canvas.removeChild(this.cursor);
}
// TODO HACK
var longestLine = this.canvas.clientWidth;
for (var i=0; i < this.lines.length; i++) {
longestLine = Math.max(longestLine, (this.lines[i].length * this.characterWidth));
}
var html = [];
for (var i=0; i < this.lines.length; i++)
{
html.push(
"<div class='line ",
i % 2 == 0 ? "even" : "odd",
"' style='height:" + this.lineHeight + "px;",
"width:", longestLine, "px'>",
this.lines[i].
replace(/&/g, "&amp;").
replace(/</g, "&lt;").
replace(/\s/g, "&nbsp;"),
"</div>"
);
};
this.canvas.innerHTML = html.join("");
this.updateCursor();
},
updateCursor : function()
{
var left = this.col * this.characterWidth;
var top = this.row * this.lineHeight;
this.cursor.style.left = left + "px";
this.cursor.style.top = top + "px";
this.canvas.appendChild(this.cursor);
if (this.canvas.scrollLeft > left) {
this.canvas.scrollLeft = left;
}
if (this.canvas.scrollLeft + this.canvas.clientWidth < left + this.characterWidth) {
this.canvas.scrollLeft = left + this.characterWidth - this.canvas.clientWidth;
}
if (this.canvas.scrollTop > top) {
this.canvas.scrollTop = top;
}
if (this.canvas.scrollTop + this.canvas.clientHeight < top + this.lineHeight) {
this.canvas.scrollTop = top + this.lineHeight - this.canvas.clientHeight;
}
},
onFocus : function() {
this.canvas.className = "focus";
},
onBlur : function() {
this.canvas.className = "";
},
placeCursorToMouse : function(pageX, pageY)
{
var canvas = this.canvas;
var canvasPos = canvas.getBoundingClientRect();
var row = Math.floor((pageY + canvas.scrollTop - canvasPos.top) / this.lineHeight);
var col = Math.floor((pageX + canvas.scrollLeft - canvasPos.left) / this.characterWidth);
this.moveTo(row, col)
},
onTextInput: function(text)
{
var newLines = text.split(/[\n\r]/);
if (text == "\n")
{
var line = this.lines[this.row] || "";
this.lines[this.row] = line.substring(0, this.col);
this.lines.splice(this.row+1, 0, line.substring(this.col));
this.row += 1;
this.col = 0;
}
else if (newLines.length == 1)
{
var line = this.lines[this.row] || "";
this.lines[this.row] = line.substring(0, this.col) + text + line.substring(this.col);
this.col += text.length;
}
else
{
var line = this.lines[this.row] || "";
this.lines[this.row] = line.substring(0, this.col) + newLines[0];
this.lines[this.row+1] = newLines[newLines.length-1] + line.substring(this.col);
if (newLines.length > 2)
{
var args = [this.row + 1, 0]
args.push.apply(args, newLines.slice(1, -1));
this.lines.splice.apply(this.lines, args);
}
this.row = this.row + newLines.length - 1;
this.col = newLines[newLines.length-1].length;
}
this.draw();
},
removeRight : function()
{
var currentLine = this.lines[this.row];
if (this.col == currentLine.length)
{
this.lines[this.row] = currentLine + (this.lines[this.row+1] || "");
this.lines.splice(this.row+1, 1);
}
else
{
this.lines[this.row] = currentLine.substring(0, this.col) + currentLine.substring(this.col + 1);
}
this.draw();
},
removeLeft : function()
{
var currentLine = this.lines[this.row] || "";
if (this.col == 0)
{
if (this.row !== 0)
{
var prevLine = this.lines[this.row-1] || ""
this.lines[this.row-1] = currentLine + prevLine;
this.lines.splice(this.row, 1);
this.row -! 1;
this.col = prevLine.length;
}
}
else
{
this.lines[this.row] = currentLine.substring(0, this.col-1) + currentLine.substring(this.col);
this.col -= 1;
}
this.draw();
},
onCompositionStart : function()
{
setText(this.composition, "");
this.composition.style.left = (this.col * this.characterWidth) + "px";
this.composition.style.top = (this.row * this.lineHeight) + "px";
this.onTextInput(" ");
this.canvas.appendChild(this.composition);
},
onCompositionUpdate : function(text) {
setText(this.composition, text);
},
onCompositionEnd : function() {
if (this.composition.parentNode) {
this.canvas.removeChild(this.composition);
}
this.removeLeft();
},
moveUp : function() {
this.moveBy(-1, 0);
},
moveDown : function() {
this.moveBy(1, 0);
},
moveLeft : function() {
if (this.col == 0) {
if (this.row > 0) {
this.moveTo(this.row-1, this.lines[this.row-1].length);
}
}
this.moveBy(0, -1);
},
moveRight : function()
{
if (this.col == this.lines[this.row].length) {
if (this.row < this.lines.length-1) {
this.moveTo(this.row+1, 0);
}
} else {
this.moveBy(0, 1);
}
},
moveLineStart : function() {
this.moveTo(this.row, 0);
},
moveLineEnd : function() {
this.moveTo(this.row, this.lines[this.row].length);
},
moveBy : function(rows, chars) {
this.moveTo(this.row+rows, this.col+chars);
},
moveTo : function(row, column)
{
this.row = Math.min(this.lines.length-1, Math.max(0, row));
this.col = Math.min(this.lines[this.row].length, Math.max(0, column));
this.updateCursor();
}
}
new Editor("container", "canvas", "text");
</script>
</body>
</html>

100
key_event_logger.html Normal file
View file

@ -0,0 +1,100 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Text Events</title>
<meta name="author" content="Fabian Jakobs">
<style type="text/css" media="screen">
#container {
border: 1px solid black;
width: 600px;
}
#canvas {
border: 1px solid black;
margin: 4px;
width: 590px;
height: 400px;
}
</style>
</head>
<body>
<div id="container">
<textarea id="text"></textarea>
<div id="canvas"></div>
</div>
<input type="button" value="Clear" id="some_name" onclick="document.getElementById('logger').innerHTML = ''">
<div id="logger">
</div>
<script type="text/javascript" charset="utf-8">
if (!window.console) window.console = {};
if (!console.log) {
var logger = document.getElementById("logger");
console.log = function() {
logger.innerHTML += Array.prototype.join.call(arguments, ", ") + "<br>";
}
}
function addListener(elem, type, callback) {
if (elem.addEventListener) {
return elem.addEventListener(type, callback, false);
}
if (elem.attachEvent) {
elem.attachEvent("on" + type, function() {
callback(window.event);
});
}
}
var container = document.getElementById("container");
var canvas = document.getElementById("canvas");
var text = document.getElementById("text");
function log(e) {
console.log(e.type, e);
}
function logKey(e) {
console.log(e.type, e.charCode, e.keyCode, e);
}
addListener(text, "keydown", logKey, false);
addListener(text, "keyup", logKey, false);
addListener(text, "keypress", logKey, false);
addListener(text, "textInput", function(e) {
console.log(e.type, e.data, e);
}, false);
addListener(text, "copy", log, false);
addListener(text, "paste", log, false);
addListener(text, "cut", log, false);
addListener(text, "beforecopy", log, false);
addListener(text, "beforepaste", log, false);
addListener(text, "beforecut", log, false);
addListener(text, "compositionstart", log, false);
addListener(text, "compositionupdate", log, false);
addListener(text, "compositionend", log, false);
addListener(text, "propertychange", function(e) {
console.log(e.type, e.propertyName, e);
}, false);
</script>
</body>
</html>