Copy and paste and basic selection support

This commit is contained in:
Fabian Jakobs 2010-04-06 11:07:33 +02:00
commit f85c1b9758
6 changed files with 358 additions and 22 deletions

View file

@ -13,7 +13,10 @@ function DumbRenderer(containerId)
this.cursor = document.createElement("div");
this.cursor.className = "cursor";
this.cursor.style.height = this.lineHeight + "px";
this.cursor.style.height = this.lineHeight + "px";
this.markers = {};
this._markerId = 1;
}
DumbRenderer.prototype =
@ -167,6 +170,38 @@ DumbRenderer.prototype =
this.container.className = "";
},
addMarker : function(range, clazz)
{
var id = this._markerId++;
this.markers[id] = {
range: range,
type: "line",
clazz: clazz
};
this.draw();
return id;
},
removeMarker : function(markerId)
{
var marker = this.markers[markerId];
if (marker) {
delete(this.markers[markerId]);
this.draw();
}
},
updateMarker : function(markerId, range)
{
var marker = this.markers[markerId];
if (marker) {
marker.range = range;
this.draw();
}
},
showComposition : function(position)
{
setText(this.composition, "");

101
Editor.js
View file

@ -39,12 +39,26 @@ function TextInput(parentNode, host) {
host.onCompositionEnd();
onTextInput();
}
var onCopy = function() {
text.value = host.getCopyText();
text.select();
}
var onCut = function() {
text.value = host.getCopyText();
host.onCut();
text.select();
}
addListener(text, "keypress", onTextInput, false);
addListener(text, "textInput", onTextInput, false);
addListener(text, "paste", onTextInput, false);
addListener(text, "paste", onTextInput, false);
addListener(text, "propertychange", onTextInput, false);
addListener(text, "copy", onCopy, false);
addListener(text, "cut", onCut, false);
addListener(text, "compositionstart", onCompositionStart, false);
addListener(text, "compositionupdate", onCompositionUpdate, false);
addListener(text, "compositionend", onCompositionEnd, false);
@ -84,6 +98,12 @@ function KeyBinding(element, host)
addListener(element, "keydown", function(e)
{
var key = e.keyCode;
// TODO
/*
if (!e.shiftKey) {
host.clearSelection();
}*/
switch (key)
{
@ -96,11 +116,21 @@ function KeyBinding(element, host)
return stopEvent(e);
case keys.LEFT:
host.moveLeft();
if (e.metaKey) {
host.moveLineStart();
} else {
host.moveLeft();
}
return stopEvent(e);
case keys.RIGHT:
host.moveRight();
if (e.metaKey) {
host.moveLineEnd();
} else if (e.shiftKey) {
host.selectRight();
} else {
host.moveRight();
}
return stopEvent(e);
case keys.POS1:
@ -147,13 +177,17 @@ function Editor(doc, renderer)
return preventDefault(e);
});
this.cursor = {
row: 0,
column: 0
}
this.doc = doc;
renderer.setDocument(doc);
this.cursor = {
row: 0,
column: 0
};
this.selectionRange = null;
this.selection = null;
this.draw();
}
@ -179,6 +213,25 @@ Editor.prototype =
this.renderer.visualizeBlur();
},
getCopyText : function()
{
if (this.selectionRange) {
return this.doc.getTextRange(this.selectionRange);
} else {
return "";
}
},
onCut : function()
{
if (this.selectionRange)
{
this.cursor = this.doc.remove(this.selectionRange);
this.clearSelection();
this.draw();
}
},
placeCursorToMouse : function(pageX, pageY)
{
var pos = this.renderer.screenToTextCoordinates(pageX, pageY);
@ -289,6 +342,40 @@ Editor.prototype =
this.renderer.scrollCursorIntoView();
},
clearSelection : function()
{
this.selectionRange = null;
if (this.selection) {
this.renderer.removeMarker(this.selection);
this.selection = null;
}
},
selectRight : function()
{
if (!this.selectionRange) {
this.selectionRange = {
start: {
row: this.cursor.row,
column: this.cursor.column
}
}
}
this.moveRight();
this.selectionRange.end = {
row: this.cursor.row,
column: this.cursor.column
}
if (this.selection) {
this.renderer.updateMarker(this.selection, this.selectionRange);
} else {
this.selection = this.renderer.addMarker(this.selectionRange, "selection");
}
},
moveBy : function(rows, chars) {
this.moveTo(this.cursor.row+rows, this.cursor.column+chars);
},

View file

@ -117,7 +117,20 @@ TextDocument.prototype =
};
}
},
getTextRange : function(range)
{
if (range.start.row == range.end.row) {
return this.lines[range.start.row].substring(range.start.column, range.end.column);
} else {
return (
this.lines[range.start.row].substring(range.start.column) +
this.lines.slice(range.start.row+1, range.end.row+1) +
this.lines[range.end.row].substring(0, range.end.column)
);
}
},
remove : function(range)
{
var firstRow = range.start.row;

View file

@ -8,6 +8,21 @@ function VirtualRenderer(containerId)
row: 0,
column: 0
};
this.layers = [];
this.layers.push({
element: this.canvas,
update: this.updateLines
});
this.markerEl = document.createElement("div");
this.markerEl.className = "markers";
this.container.appendChild(this.markerEl);
this.layers.push({
element: this.markerEl,
update: this.updateMarkers
});
}
inherits(VirtualRenderer, DumbRenderer);
@ -19,15 +34,27 @@ VirtualRenderer.prototype.draw = function()
var minHeight = this.container.clientHeight + offset;
var longestLine = this.getLongestLineWidth(lines);
this.canvas.style.marginTop = (-offset) + "px";
this.canvas.style.height = minHeight + "px";
this.canvas.style.width = longestLine + "px";
var lineCount = Math.ceil(minHeight / this.lineHeight);
this.firstRow = firstRow = Math.round((this.scrollTop - offset) / this.lineHeight);
var lastRow = Math.min(lines.length, firstRow+lineCount);
for (var i=0; i < this.layers.length; i++)
{
var layer = this.layers[i];
var style = layer.element.style;
style.marginTop = (-offset) + "px";
style.height = minHeight + "px";
style.width = longestLine + "px";
layer.update.call(this, layer.element, firstRow, lastRow, longestLine);
};
this.updateCursor(this.cursorPos);
}
VirtualRenderer.prototype.updateLines = function(element, firstRow, lastRow, width)
{
var html = [];
for (var i=firstRow; i<lastRow; i++)
{
@ -35,16 +62,14 @@ VirtualRenderer.prototype.draw = function()
"<div class='line ",
i % 2 == 0 ? "even" : "odd",
"' style='height:" + this.lineHeight + "px;",
"width:", longestLine, "px'>"
"width:", width, "px'>"
);
this.renderLine(html, i),
html.push("</div>");
}
this.canvas.innerHTML = html.join("");
this.updateCursor(this.cursorPos);
}
element.innerHTML = html.join("");
};
VirtualRenderer.prototype.renderLine = function(stringBuilder, row)
{
@ -66,6 +91,67 @@ VirtualRenderer.prototype.renderLine = function(stringBuilder, row)
};
};
VirtualRenderer.prototype.updateMarkers = function(element, firstRow, lastRow, width)
{
var html = [];
for (var key in this.markers)
{
var marker = this.markers[key];
var range = marker.range;
if (range.start.row !== range.end.row)
{
if (range.start.row >= firstRow && range.start.row <= lastRow)
{
html.push(
"<div class='", marker.clazz, "' style='",
"height:", this.lineHeight, "px;",
"width:", width - (range.start.column * this.characterWidth), "px;",
"top:", (range.start.row-firstRow) * this.lineHeight, "px;",
"left:", range.start.column * this.characterWidth, "px;'></div>"
);
}
if (range.end.row >= firstRow && range.end.row <= lastRow)
{
html.push(
"<div class='", marker.clazz, "' style='",
"height:", this.lineHeight, "px;",
"top:", (range.end.row-firstRow) * this.lineHeight, "px;",
"width:", range.end.column * this.characterWidth, "px;'></div>"
);
};
for (var row=range.start.row+1; row < range.end.row; row++)
{
if (row >= firstRow && row <= lastRow)
{
html.push(
"<div class='", marker.clazz, "' style='",
"height:", this.lineHeight, "px;",
"width:", width, "px;",
"top:", (row-firstRow) * this.lineHeight, "px;'></div>"
);
}
};
}
else
{
if (range.start.row >= firstRow && range.start.row <= lastRow)
{
html.push(
"<div class='", marker.clazz, "' style='",
"height:", this.lineHeight, "px;",
"width:", (range.end.column - range.start.column) * this.characterWidth, "px;",
"top:", (range.start.row-firstRow) * this.lineHeight, "px;",
"left:", range.start.column * this.characterWidth, "px;'></div>"
);
}
}
}
element.innerHTML = html.join("");
};
VirtualRenderer.prototype.updateCursor = function(position)
{
this.cursorPos = {

105
cut_copy.html Normal file
View file

@ -0,0 +1,105 @@
<!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);
function fillSelection() {
text.value = "Juhu Kinners";
text.select();
}
addListener(text, "copy", fillSelection, false);
addListener(text, "paste", log, false);
addListener(text, "cut", fillSelection, 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>

View file

@ -11,7 +11,7 @@
#virtual_container {
position: absolute;
padding: 3px;
/*padding: 3px;*/
border: 1px solid black;
overflow-x: auto;
overflow-y: hidden;
@ -37,6 +37,7 @@
}
.canvas {
z-index: 2;
position: absolute;
overflow: hidden;
font-family: Monaco, "Courier New";
@ -62,7 +63,7 @@
}
.line.odd {
background: #FAFAFA;
/*background: #FAFAFA;*/
}
.keyword {
@ -77,6 +78,15 @@
font-style: italic;
color: rgb(0, 102, 255);
}
.markers {
z-index: 1;
}
.selection {
position: absolute;
background: rgba(77, 151, 255, 0.33);
}
</style>
<script src="lib.js" type="text/javascript" charset="utf-8"></script>