--- /dev/null
+<!-- Copyright 2002 NeuronForge Pty Ltd, see COPYING file for licensing [LGPL] -->
+<ibex>
+ <static>
+ var cursors = [];
+ var worddivider = [' ', '-'];
+
+ ibex.thread = function() {
+ while (true) { ibex.sleep(1000); for (var i=0; cursors.length > i; i++) { cursors[i].blink = !cursors[i].blink; } }
+ }
+
+ // Returns the number of characters the pixel position pos is into text t. end is the pixel with of t.
+ // t : string -- basis for character counting.
+ // pxpos : int -- pixel position on the text from string t.
+ // pxlen : int -- pixel width of text t (known in form of box length, so passed for speed).
+ // tfont : string -- name of font used for width of text.
+ var getpos = function(t, pxpos, pxlen, tfont) {
+ // Short circuit extremes.
+ if (0 >= pxpos) return 0;
+ if (pxpos >= pxlen) return t.length;
+
+ // Inital guess based on average character width.
+ var guessch = ibex.math.min(t.length, ibex.math.floor(pxpos / (pxlen / t.length)));
+ var guesspx = ibex.textwidth(tfont, t.substring(0, guessch));
+
+ if (guesspx > pxpos) {
+ while (guesspx > pxpos) {
+ // Textwidth of individual character must account for font kerning.
+ guesspx -= ibex.textwidth(tfont, t.substring(guessch -1, guessch +1)) - ibex.textwidth(tfont, t.charAt(guessch));
+ guessch--;
+ }
+ } else if (pxpos > guesspx) {
+ while (pxpos > guesspx) {
+ guessch++;
+ if (guessch >= t.length) break;
+ guesspx += ibex.textwidth(tfont, t.substring(guessch -1, guessch+1)) - ibex.textwidth(tfont, t.charAt(guessch));
+ }
+ guessch--; // Round down.
+ }
+
+ return guessch;
+ }
+ </static>
+
+
+ <template>
+ _multiline = function(v) {
+ if (!v) {
+ while (content.numchildren > 1) { reapLine(content[content.numchildren -1]); }
+ wrap = null;
+ }
+ }
+
+ _disabled = function(v) {
+ curs.disabled = v;
+ }
+
+ _editable = function(v) {
+ }
+
+ _wrap = function(v) {
+ // Used on _SizeChange if wrap needs to know.
+ var resize = function() {
+ ibex.thread = function() {
+ // TODO: Only run this change if the width is different.
+ for (var i = 0; content.numchildren > i; i++) { content[i].fulltext = content[i].fulltext; }
+ };
+ };
+
+ if (multiline and v == "line") {
+ content.vshrink = true;
+ content.hshrink = false;
+ content.maxwidth = ibex.maxdim; // Must reset maxwidth after shrink = true.
+ ref_fulltext = fulltext_linewrap;
+ _SizeChange = resize;
+
+ } else if (multiline and v == "word") {
+ content.vshrink = true;
+ content.hshrink = false;
+ content.maxwidth = ibex.maxdim;
+ ref_fulltext = fulltext_wordwrap;
+ _SizeChange = resize;
+
+ } else {
+ content.shrink = true;
+ ref_fulltext = fulltext_nowrap;
+ _SizeChange = null;
+ }
+
+ // Reset functions on current lines.
+ for (var i = 0; content.numchildren > i; i++) {
+ content[i]._fulltext = ref_fulltext;
+ content[i].fulltext = content[i].fulltext;
+ }
+ }
+
+ _selectcolor = function(v) { sel1.color = sel2.color = v; }
+ _selecttextcolor = function(v) { sel1.textcolor = sel2.textcolor = v; }
+
+ _font = function(f) {
+ lineheight = ibex.textheight(f);
+ if (lineheight > 0) { minheight = content.minheight = linecount * lineheight; }
+ for (var i=0; content.numchildren > i; i++) { content[i].font = f; }
+ sel1.font = sel2.font = curs.font = f;
+ }
+
+ __text = function(t) {
+ if (arguments.length == 0) {
+ var s = content[0].fulltext;
+ for (var i=1; content.numchildren > i; i++) { s = s + '\n' + content[i].fulltext; }
+ return s;
+ }
+
+ deleteText(0, 0, content.numchildren - 1, content[content.numchildren - 1].fulltext.length);
+ insertText(0, 0, t);
+ }
+
+ __selection = function(t) {
+ if (arguments.length == 0) {
+ if (sel1.cl == -1) return "";
+
+ var s = sel1.text;
+
+ if (sel1.cl == sel2.cl) {
+ for (var i=sel1.cy+1; sel2.cy > i; i++) { s += content[sel1.cl][i].text; }
+ } else {
+ for (var i=sel1.cy+1; content[sel1.cl].numchildren > i; i++) { s += content[sel1.cl][i].text; }
+ for (var i=sel1.cl+1; sel2.cl > i; i++) { s += '\n' + content[i].fulltext; }
+ s += '\n';
+ for (var i=0; sel2.cy > i; i++) { s += content[sel2.cl][i].text; }
+ s += sel2.text;
+ }
+
+ return s;
+ }
+
+ deleteSelection();
+ insertText(curs.cl, curs.cx, t);
+ }
+
+
+ // PRIVATE VARIABLES //////////////////////////////////////////////////////////////////////////////////////////
+
+ // Stores the inital point of the current selection.
+ var sel = { cl : 0, cy : 0, px : 0 };
+
+ // The pixel height of the current font.
+ var _lineheight = function(l) { curs.height = sel1.height = sel2.height = l; }
+
+ // Number of soft lines currently in this edit widget. Controlled by newLine() and reapLine().
+ var _linecount = function(l) {
+ arguments.cascade(l); if (l == 0) l = 1;
+ if (lineheight > 0) { minheight = content.minheight = l * lineheight; }
+ }
+
+ // Total number of characters stored in this text field.
+ var length = 0;
+
+
+ // PUBLIC FUNCTIONS //////////////////////////////////////////////////////////////////////////////////////////
+
+ // Insert the given text at the given position.
+ insertText = function(cl, cx, t) {
+ // Check passed string.
+ if (t == null || 1 > t.length) return;
+ t = t.toString();
+
+ // Limit checking.
+ if (limit > 0 and length + t.length > limit) {
+ ibex.println("WARNING: Limit applied on inserted text.");
+ t = t.substring(0, limit - length);
+ }
+
+ // Make sure there are enough lines before hand.
+ for (var i=content.numchildren; cl >= i; i++) { content[i] = newLine(); }
+
+ // Parse the carridge returns out of t.
+ var newT = t.split("\n");
+
+ if (newT.length == 0) {
+ return;
+ } else if (newT.length == 1) {
+ content[cl].fulltext = content[cl].fulltext.substring(0, cx) + t + content[cl].fulltext.substring(cx);
+ length += t.length;
+
+ moveCursor(cl, cx+t.length);
+ } else {
+ if (multiline) {
+ // Add extra lines required by passed text.
+ for (var i = newT.length - 1; i > 0; i--) { content[cl+1] = newLine(); }
+
+ // Add the new text
+ var lastline = newT[newT.length - 1] + content[cl].fulltext.substring(cx);
+ content[cl].fulltext = content[cl].fulltext.substring(0, cx) + newT[0];
+ for (var i=1; newT.length-1 > i; i++) { content[cl+i].fulltext = newT[i]; }
+ content[cl + newT.length - 1].fulltext = lastline;
+
+ moveCursor(cl + newT.length - 1, newT[newT.length -1].length);
+ } else {
+ ibex.println("WARNING: Single line edit, ignoring all text after the carrige return.");
+ content[0].fulltext = content[0].fulltext.substring(0, cx) + newT[0] + content[0].fulltext.substring(cx);
+
+ moveCursor(0, cx + newT[0].length);
+ }
+ }
+ }
+
+ // Delete the text within the given range.
+ deleteText = function(cl1, cx1, cl2, cx2) {
+ content[cl1].fulltext = content[cl1].fulltext.substring(0, cx1) + content[cl2].fulltext.substring(cx2);
+ for (; cl2 > cl1; cl2--) { reapLine(content[cl2]); }
+ }
+
+ // Select the text within the given range.
+ selectText = function(cl1, cx1, cl2, cx2) {
+ // Find cy1 and px1.
+ var cy1 = 0;
+ var px1 = cx1;
+ for (; content[cl1].numchildren > cy1; cy1++) {
+ if (content[cl1][cy1].text.length > px1) { break; }
+ else { px1 -= content[cl1][cy1].text.length; }
+ }
+
+ // Find cy2 and px2.
+ var cy2 = 0;
+ var px2 = cx2;
+ for (; content[cl2].numchildren > cy2; cy2++) {
+ if (content[cl2][cy2].text.length >= px2) { break; }
+ else { px2 -= content[cl2][cy2].text.length; }
+ }
+
+ // Call the internal select function.
+ sel.cl = cl1; sel.cy = cy1; sel.px = px1;
+ select(cl1, cy1, px1, cl2, cy2, px2);
+ moveCursorToCy(cl2, cy2, px2);
+ }
+
+ // Clear the current selection.
+ clearSelection = function() {
+ if (sel.cl == -1 || sel1.cl == -1) return;
+ moveCursorToCy(sel1.cl, sel1.cy, sel1.px);
+
+ // Clear any selected lines.
+ for (var i=sel1.cl; sel2.cl >= i; i++) { if (content[i] != null) content[i].selected = false; }
+
+ // Clear the selection values
+ sel.cl = sel.px = sel.cy = sel1.cl = sel1.cx = sel1.cy = sel2.cl = sel2.cx = sel2.cy = -1;
+ sel1.text = sel2.text = "";
+ }
+
+ // Delete the text currently within the selected range.
+ deleteSelection = function() {
+ if (sel1.cl == -1 || sel2.cl == -1) return;
+ deleteText(sel1.cl, calcCx(sel1.cl, sel1.cy, sel1.px), sel2.cl, calcCx(sel2.cl, sel2.cy, sel2.px));
+ clearSelection();
+ }
+
+ // External interface for moving the mouse cursor.
+ moveCursor = function(cl, cx) {
+ // Work out what subline cx is on.
+ var cy, px;
+
+ if (cl >= content.numchildren) return;
+
+ if (cx > content[cl].fulltext.length) {
+ if (content.numchildren -1 > cl) {
+ cl++; cx = 0; cy = 0; px = 0;
+ } else {
+ cx = content[cl].fulltext.length;
+ cy = content[cl].numchildren -1;
+ px = content[cl][cy].text.length;
+ }
+ } else {
+ px = cx;
+
+ for (cy = 0; content[cl].numchildren > cy; cy++) {
+ if (content[cl][cy].text.length >= px) break;
+ px -= content[cl][cy].text.length;
+ }
+ }
+
+ // Call internal move function.
+ moveCursorToPos(cl, cx, cy, px);
+ }
+
+
+ // PRIVATE FUNCTIONS //////////////////////////////////////////////////////////////////////////////////////////
+
+ var updateSelectionCx = function(cl, cx) {
+ if (cl >= content.numchildren) {
+ var t = content[content.numchildren -1];
+ updateSelection(content.numchildren -1, t.numchildren, t[t.numchildren -1].text.length);
+ return;
+ } else if (cx > content[cl].fulltext.length) {
+ updateSelection(cl, content[cl].numchildren -1, content[cl][content[cl].numchildren -1].text.length);
+ return;
+ }
+
+ var cy;
+ for (cy = 0; cx > 0; cy++) {
+ cx -= content[cl][cy].text.length;
+ }
+ cy--;
+
+ updateSelection(cl, cy, content[cl][cy].text.length + cx);
+ }
+
+ // Used in the _KeyPress trap to literally update a current selection. The new 'floating point' is passed, and
+ // the original value stored in the private property sel is used to balance with.
+ var updateSelection = function(cl, cy, px) {
+ // Very very very padentic checking. I dare you to do more checking. :-)
+ if (0 > px || 0 > cy || 0 > cl) {
+ if (cy - 1 >= 0) { cy--; px = content[cl][cy].text.length; }
+ else if (cl - 1 >= 0) { cl--; cy = content[cl].numchildren - 1; px = content[cl][cy].text.length; }
+ else { cl = 0; cy = 0; px = 0; }
+ } else if (cl >= content.numchildren) {
+ cl = content.numchildren - 1;
+ cy = content[cl].numchildren - 1;
+ px = content[cl][cy].text.length;
+ } else if (cy >= content[cl].numchildren || px > content[cl][cy].text.length) {
+ if (content[cl].numchildren > cy + 1) { cy++; px = 0; }
+ else if (content.numchildren > cl + 1) { cl++; cy = 0; px = 0; }
+ else { cl = content.numchildren - 1; cy = content[cl].numchildren - 1; px = content[cl][cy].text.length; }
+ }
+
+ // If there is no current selection, set to current mouse position.
+ if (sel.cl == -1) { sel.cl = curs.cl; sel.cy = curs.cy; sel.px = curs.px; }
+
+ // Decide on dominant point and call internal select function.
+ if (cl > sel.cl || (cl == sel.cl and cy > sel.cy) || (cl == sel.cl and cy == sel.cy and px > sel.px)) {
+ select(sel.cl, sel.cy, sel.px, cl, cy, px);
+ } else {
+ select(cl, cy, px, sel.cl, sel.cy, sel.px);
+ }
+
+ // Update cursor position.
+ moveCursorToCy(cl, cy, px);
+ }
+
+ // The meat behind all select calls. This function draws the select boxes on top of the edit widget.
+ var select = function(cl1, cy1, px1, cl2, cy2, px2) {
+ if (disabled) return;
+
+ // Deselect the current full-selection lines that are outside the new selection area.
+ if (sel1.cl == cl1) {
+ for (var i=sel1.cy+1; cy1 >= i; i++) { content[cl1][i].selected = false; }
+ } else if (cl1 > sel1.cl) {
+ for (var i=ibex.math.max(0, sel1.cl); cl1 >= i; i++) { content[i].selected = false; }
+ }
+ if (sel2.cl == cl2) {
+ for (var i=sel2.cy-1; i >= cy2; i--) { content[cl2][i].selected = false; }
+ } else if (sel2.cl > cl2) {
+ for (var i=ibex.math.max(0, sel2.cl); i >= cl2; i--) { content[i].selected = false; }
+ }
+
+ // Store point data.
+ sel1.cl = cl1; sel1.cy = cy1; sel1.px = px1;
+ sel2.cl = cl2; sel2.cy = cy2; sel2.px = px2;
+
+ // Place first select box.
+ sel1.y = content[cl1].y + content[cl1][cy1].y;
+ sel1.x = ibex.textwidth(font, content[cl1][cy1].text.substring(0, px1));
+
+ if (cl1 == cl2 and cy1 == cy2) {
+ // Only the first select box is required.
+ sel1.text = content[cl1][cy1].text.substring(px1, px2);
+ sel2.text = "";
+ } else {
+ sel1.text = content[cl1][cy1].text.substring(px1);
+ sel2.y = content[cl2].y + content[cl2][cy2].y;
+ sel2.x = 0;
+ sel2.text = content[cl2][cy2].text.substring(0, px2);
+
+ // Mark middle lines.
+ if (cl1 == cl2) {
+ for (var i=cy1+1; cy2 > i; i++) { content[cl1][i].selected = true; }
+ } else {
+ for (var i=cy1+1; content[cl1].numchildren > i; i++) { content[cl1][i].selected = true; }
+ for (var i=cl1+1; cl2 > i; i++) { content[i].selected = true; }
+ for (var i=0; cy2 > i; i++) { content[cl2][i].selected = true; }
+ }
+ }
+ }
+
+ // Internal reference function. Calculates the cx position of the cursor based on cl, cy and px,
+ // and then passes it to the primary internal function moveCursorToPos() for movement.
+ var moveCursorToCy = function(cl, cy, px) { moveCursorToPos(cl, calcCx(cl, cy, px), cy, px); }
+
+ var calcCx = function(cl, cy, px) { for (cy--; cy >= 0; cy--) { px += content[cl][cy].text.length; } return px; }
+
+ // Internal function for moving the mouse cursor. px represents number of characters over in specified subline.
+ // NOTE: The mouse cursor is the closest the external functions get to affecting the internal structure of a line.
+ var moveCursorToPos = function(cl, cx, cy, px) {
+ // Check the passed values are within reasonable constaints.
+ if (cl >= content.numchildren) { cl = content.numchildren - 1; }
+ if (cy >= content[cl].numchildren) {
+ if (content.numchildren - 1 > cl) { cl++; cy = 0; cx = calcCx(cl, cy, px); }
+ else { cy = content[cl].numchildren -1; cx = calcCx(cl, cy, px); }
+ } else if (0 > cy) {
+ if (cl > 0) { cl--; cy = content[cl].numchildren - 1; cx = calcCx(cl, cy, px); }
+ else { cy = 0; cx = calcCx(cl, cy, px); }
+ }
+ if (0 > px) { px = 0; cx = calcCx(cl, cy, px); }
+ else if (px > content[cl][cy].text.length) { px = content[cl][cy].text.length; cx = calcCx(cl, cy, px); }
+
+ // Move the cursor.
+ curs.cl = cl; curs.cx = cx; curs.cy = cy; curs.px = px;
+ curs.y = content.y + content[cl].y + (lineheight * cy);
+ curs.x = content.x + ibex.textwidth(font, content[cl][cy].text.substring(0, px)) -1;
+ curs.blink = false;
+
+ // Speed Hack: As the cursor has values that match the names used by the focusarea variable, we
+ // simply pass the curs reference as the focusarea.
+ focusarea = curs;
+
+ if (0 > curs.x) curs.x = 0;
+ }
+
+ // Returns a box ready to be a full line, armed with the current fulltext trap.
+ var newLine = function() {
+ var b = ibex.newBox();
+
+ b.color = color;
+ b.align = "topleft";
+ b.invisible = false;
+
+ b[0] = newSoftline();
+ b._Press1 = function() { ref_press(arguments.trapee); }
+ b._selected = function(s) { for (var i=0; b.numchildren > i; i++) { b[i].selected = s; } }
+ b._font = function(f) { for (var i=0; b.numchildren > i; i++) { b[i].font = f; } }
+ b._fulltext = ref_fulltext;
+ b.fulltext = "";
+ b.orient = "vertical";
+ b.vshrink = true;
+
+ return b;
+ }
+
+ // Returns a box ready to be a soft line; one of the components of a line.
+ var newSoftline = function() {
+ var b = ibex.newBox();
+
+ b._selected = function(s) {
+ arguments.trapee.color = s ? selectcolor : color;
+ arguments.trapee.textcolor = s ? selecttextcolor : textcolor;
+ };
+ b.minheight = lineheight;
+ b.shrink = true;
+ b.color = color;
+ b.textcolor = textcolor;
+ b.font = font;
+ b.align = "topleft";
+ b.invisible = false;
+
+ linecount++;
+
+ return b;
+ }
+
+ // Takes the given line and strips it of traps and returns it to the static stack.
+ var reapLine = function(toReap) {
+ if (content.indexof(toReap) == -1) {
+ // Soft-line
+ linecount--;
+ } else {
+ // Hard-line, count all softline children.
+ linecount -= toReap.numchildren;
+ }
+
+ toReap.thisbox = null;
+ }
+
+
+ // SUBLINE FUNCTIONS //////////////////////////////////////////////
+
+ var fulltext_nowrap = function(t) {
+ arguments.trapee[0].text = t;
+ }
+ var fulltext_linewrap = function(t) {
+ var cw = width;
+
+ if (cw == 0) return;
+
+ var i = 0;
+ if (t.length == 0) arguments.trapee[0].text = "";
+
+ for (; t.length > 0; i++) {
+ if (i == arguments.trapee.numchildren) { arguments.trapee[i] = newSoftline(); }
+
+ // TODO: Switch to getpos
+ var nl = static.getpos(t, cw, ibex.textwidth(font, t), font);
+ arguments.trapee[i].text = t.substring(0, nl);
+ t = t.substring(nl);
+ }
+
+ // Remove any excess lines.
+ if (i == 0) i++;
+ while (arguments.trapee.numchildren > i) { reapLine(arguments.trapee[i]); }
+ }
+ var fulltext_wordwrap = function(t) {
+ var cw = width;
+
+ if (cw == 0) return;
+
+ var i = 0;
+ if (t.length == 0) arguments.trapee[0].text = "";
+
+ for (; t.length > 0; i++) {
+ if (i == arguments.trapee.numchildren) { arguments.trapee[i] = newSoftline(); }
+
+ var nl = static.getpos(t, cw, ibex.textwidth(font, t), font);
+
+ var rl = nl;
+ if (t.length > nl) {
+ // TODO: Clean up, make it work off a static array of possible break points.
+ // TODO: Make it themeable as well.
+ for (; rl > 0; rl--) {
+ if (t.charAt(rl) == ' ' || t.charAt(rl) == '-') { rl++; break; }
+ }
+ if (0 >= rl || rl > nl) rl = nl;
+ }
+
+ arguments.trapee[i].text = t.substring(0, rl);
+ t = t.substring(rl);
+ }
+
+ // Remove any excess lines.
+ if (i == 0) i++;
+ while (arguments.trapee.numchildren > i) { reapLine(arguments.trapee[i]); }
+ }
+
+ // Reference to the current function to use for the fulltext trap.
+ var ref_fulltext = fulltext_nowrap;
+
+ // Handles selection/cursor movement from mouse events.
+ // The passed value is a reference to the selected hard line.
+ var ref_press = function(refline) {
+ if (disabled) return;
+
+ root._Move = function() {
+ // Update Selection.
+ var linediff = ibex.math.floor((content.mousey - (content[sel.cl].y + content[sel.cl][sel.cy].y)) / lineheight);
+
+ var cl = sel.cl;
+ var cy = sel.cy;
+
+ // End of selection comes after start.
+ while (linediff > 0) {
+ cy++;
+ if (cy >= content[cl].numchildren) { cl++; cy = 0; }
+ if (cl >= content.numchildren) { cl--; break; }
+ linediff--;
+ }
+
+ // End of selection comes before start.
+ while (0 > linediff) {
+ cy--;
+ if (0 > cy) { cl--; cy = content[cl].numchildren -1; }
+ if (0 > cl) { cl=0; cy = 0; break; }
+ linediff++;
+ }
+
+ var px = static.getpos(content[cl][cy].text, content[cl][cy].mousex, content[cl][cy].width, font);
+
+ updateSelection(cl, cy, px);
+ }
+
+ root._Release1 = function() {
+ root._Move = root._Release1 = null;
+ // TODO: Put selection to clipboard.
+ }
+
+ // Set selection root position.
+ clearSelection();
+ sel.cl = content.indexof(refline);
+ sel.cy = ibex.math.floor(refline.mousey / lineheight);
+
+ if (sel.cy >= refline.numchildren) sel.cy = refline.numchildren -1;
+ else if (0 > sel.cy) sel.cy = 0;
+
+ sel.px = static.getpos(refline[sel.cy].text, refline[sel.cy].mousex, refline[sel.cy].width, font);
+
+ moveCursorToCy(sel.cl, sel.cy, sel.px);
+ }
+
+
+ // HELPER FUNCTIONS //////////////////////////////////////////////
+
+ // Nessesary for when a used clicks in the middle of a current selection.
+ _sel1 = _sel2 = function(s) {
+ if (s != null) {
+ s._Press1 = function(t) { if (arguments.trapee.cl >= 0) content[arguments.trapee.cl].Press1 = t; };
+ s._Release1 = function(t) { if (arguments.trapee.cl >= 0) content[arguments.trapee.cl].Release1 = t; };
+ }
+ }
+
+ _content = function(c) { if (c != null and c.numchildren == 0) c[0] = newLine(); }
+
+ _curs = function(c) {
+ if (c == null) {
+ for (var i=0; static.cursors.length > i; i++) {
+ if (static.cursors[i] == arguments.cascade()) { static.cursors[i] = null; break; }
+ }
+ } else {
+ // Add cursor to static array for 'blinking'.
+ static.cursors[static.cursors.length] = c;
+ }
+ }
+
+ key_enter = function() {
+ if (!multiline) return;
+ content[curs.cl +1] = newLine();
+ content[curs.cl +1].fulltext = content[curs.cl].fulltext.substring(curs.cx);
+ content[curs.cl].fulltext = content[curs.cl].fulltext.substring(0, curs.cx);
+ ibex.thread = function() { moveCursor(curs.cl +1, 0); }
+ }
+
+ key_back_space = function() {
+ if (curs.cx == 0) {
+ if (curs.cl > 0) {
+ var px = content[curs.cl -1].fulltext.length;
+ content[curs.cl -1].fulltext = content[curs.cl -1].fulltext + content[curs.cl].fulltext;
+ reapLine(content[curs.cl]);
+ moveCursor(curs.cl -1, px); // Safe, moving up not down.
+ }
+ } else {
+ content[curs.cl].fulltext = content[curs.cl].fulltext.substring(0, curs.cx -1) + content[curs.cl].fulltext.substring(curs.cx);
+ moveCursor(curs.cl, curs.cx -1); // Safe, not moving cl.
+ }
+ }
+
+ key_delete = function() {
+ if (curs.cx == content[curs.cl].fulltext.length) {
+ if (content.numchildren > 1) {
+ content[curs.cl].fulltext = content[curs.cl].fulltext + content[curs.cl +1].fulltext;
+ reapLine(content[curs.cl +1]);
+ }
+ } else {
+ content[curs.cl].fulltext = content[curs.cl].fulltext.substring(0, curs.cx) + content[curs.cl].fulltext.substring(curs.cx +1);
+ }
+ }
+
+ // KEY HANDLER //////////////////////////////////////////////
+ _keypress = function(k) {
+ if (k == null || !editable || disabled) return;
+
+ // Process shortcut for single character entries.
+ if (k.length == 1) {
+ deleteSelection();
+
+ if (k.charAt(0) == '\n') {
+ insertText(curs.cl, curs.cx, k);
+ } else {
+ content[curs.cl].fulltext = content[curs.cl].fulltext.substring(0, curs.cx)
+ + k + content[curs.cl].fulltext.substring(curs.cx);
+ ibex.thread = function() { moveCursor(curs.cl, curs.cx+1); }
+ textChanged = true;
+ }
+
+ return;
+ }
+
+ k = k.substring(k.lastIndexOf('-')+1);
+
+ // Process movement commands.
+ if (k == "enter") {
+ deleteSelection(); key_enter();
+ textChanged = true;
+ } else if (k == "back_space") {
+ if (sel1.cl > -1) { deleteSelection(); }
+ else { key_back_space(); }
+ textChanged = true;
+ } else if (k == "delete") {
+ if (sel1.cl > -1) { deleteSelection(); }
+ else { key_delete(); }
+ textChanged = true;
+ }
+ }
+
+ </template>
+</ibex>