--- /dev/null
+<!-- Copyright 2002 Adam Megacz, see the COPYING file for licensing [LGPL] -->
+<xwt>
+
+ A text edit box.
+
+ multiline : boolean -- if true, lines will be broken at newline characters
+ editable : boolean -- if false, the text cannot be changed, although it can be copied
+
+ <redirect target="self"/>
+
+ <static>
+ var boxen = [];
+ var cr_regexp = /[\n\r]/g;
+ var nonwhitespace = /\S/;
+ </static>
+
+ <template textcolor="black" orient="vertical" vpad="2" editable="true" multiline="false" text="" cursor="text">
+
+ // Structural Stuff //////////////////////////////////////////////////////////////////////
+
+ // Cursor1 is the cursor -- when there is no selection, its width is
+ // 1, and its height is the height of one line. When the user selects
+ // a one-line region, Cursor1 is expanded to cover that region (but
+ // placed behind the text on the z-axis). When the user selects more
+ // than one line, Cursor1 covers the background of the first line of
+ // the selection, Cursor3 covers the last line, and the intermediate
+ // lines get their color set to "blue".
+
+ var curs = $curs; // the main cursor
+ var curs2 = $curs2; // the "backup" cursor for multiline selections
+ var numcursors = 2; // the number of non-textline children of this box
+ var master = 0;
+
+ <box text="" align="topleft"/>
+ <box id="curs" absolute="true" textcolor="white" color="blue" width="1" y="0" x="1" invisible="true"/>
+ <box id="curs2" absolute="true" textcolor="white" color="blue" width="1" y="0" x="1" invisible="true"/>
+
+ // Cursor Manipulation //////////////////////////////////////////////////////////////////////
+
+ // cx1, cy1 is the coordinates (in characters, not pixels) of the start of the selection
+ // cx2, cy2 is the coordinates (in characters, not pixels) of the end of the selection
+ cx2 = cx1 = cy2 = cy1 = 0;
+
+ // the x-coordinate of the beginning of the selection, in characters
+ _cx1 = function(a) {
+ a = xwt.math.floor(a);
+ if (0 > a) { if (cy1 > 0) cy1--; a = thisbox[cy1].text.length; }
+ if (numchildren - numcursors > cy1 and a > thisbox[cy1].text.length) { cy1++; a = 0; }
+ curs.x = xwt.textwidth(font, thisbox[cy1].text.substring(0, a));
+ arguments.cascade(a);
+ sync();
+ }
+
+ // the x-coordinate of the end of the selection, in characters
+ _cx2 = function(a) {
+ a = xwt.math.floor(a);
+ if (0 > a) { cy2--; a = thisbox[cy2].text.length; }
+ if (numchildren - numcursors > cy2 and a > thisbox[cy2].text.length) { cy2++; a = 0; }
+ arguments.cascade(a);
+ sync();
+ }
+
+ // the y-coordinate of the beginning of the selection, in characters
+ _cy1 = function(a) {
+ a = xwt.math.floor(a);
+ if (0 > a) { a = 0; cx1 = 0; }
+ arguments.cascade(a);
+ if (cx1 > thisbox[a].text.length) cx1 = thisbox[a].text.length;
+ sync();
+ }
+
+ // the y-coordinate of the end of the selection, in characters
+ _cy2 = function(a) {
+ a = xwt.math.floor(a);
+ arguments.cascade(a);
+ while (a >= numchildren - numcursors) {
+ var b = getbox();
+ b.text = "";
+ thisbox[numchildren - numcursors] = b;
+ }
+ if (cx2 > thisbox[a].text.length) cx2 = thisbox[a].text.length;
+ sync();
+ }
+
+ // the x-coordinate of the beginning of the selection, measured in _pixels_
+ __px1 = function() {
+ if (cy1 > numchildren || thisbox[cy1].text == null) return 0;
+ return xwt.textwidth(font, thisbox[cy1].text.substring(0, cx1));
+ }
+
+ // the x-coordinate of the end of the selection, measured in _pixels_
+ __px2 = function() {
+ if (cy2 > numchildren || thisbox[cy2].text == null) return 0;
+ return xwt.textwidth(font, thisbox[cy2].text.substring(0, cx2));
+ }
+
+ // on the r-th row, this will determine how many characters are to the left of a
+ // point b pixels from the left edge of the edit Algorithm is a binary search,
+ // making educated guesses based on the average width of a character on that line.
+ getboundary = function(r, b) {
+
+ if (o >= b) return 0;
+ var s = thisbox[r].text;
+
+ var left = 0; // the left boundary of the search region, in characters
+ var right = s.length; // the right boundary of the search region, in characters
+ var start = 0; // the left boundary of the search region, in pixels
+ // the right boundary of the search region, in pixels
+ var end = xwt.textwidth(font, s);
+ if (b >= end) return right; // short circuit if we're off the end
+
+ var avgwidth = end / right; // average width of one character
+
+ while(true) {
+ var middle = left + (b - start) / avgwidth; // make a guess at where we should look
+
+ // extra safety guard against infinite loops
+ if (left == right) return left;
+ if (left >= middle) middle = left + 1;
+ if (middle >= right) middle = left - 1;
+
+ start = xwt.textwidth(font, s.substring(0, middle));
+ if (start > b) {
+ right = middle;
+ } else if (b >= start + xwt.textwidth(font, s.charAt(middle))) {
+ left = middle;
+ } else {
+ return middle;
+ }
+ }
+ }
+
+ // returns the row containing the point yp pixels from the top of the widget
+ getrow = function(yp) { return xwt.math.min(numchildren - numcursors - 1, xwt.math.floor(yp / lineheight)); }
+
+ sync = function() {
+ curs.x = px1;
+ curs.y = thisbox[cy1].y; //lineheight * cy1 + thisbox[0].y;
+ curs.width = px2 - px1;
+ if (1 > curs.width) { curs.width = 1; curs.text = ""; }
+ curs2.invisible = true;
+
+ if (cy1 != cy2) {
+ curs.width = width - curs.x;
+ curs2.invisible = false;
+ curs2.x = 0;
+ curs2.y = thisbox[cy2].y; //lineheight * cy2;
+ curs2.height = lineheight;
+ curs2.width = px2;
+ }
+
+ for(var i=0; numchildren - numcursors>i; i++) {
+ if (i>cy1 and cy2>i) {
+ thisbox[i].textcolor = "white";
+ thisbox[i].color = "blue";
+ } else {
+ thisbox[i].textcolor = "black";
+ thisbox[i].color = null;
+ }
+ }
+
+ curs.text = (thisbox[cy1] == null || thisbox[cy1].text == null) ? null :
+ thisbox[cy1].text.substring(getboundary(cy1, curs.x),
+ getboundary(cy1, curs.x + curs.width));
+
+ curs2.text = (thisbox[cy2] == null || thisbox[cy2].text == null) ? null :
+ thisbox[cy2].text.substring(getboundary(cy2, curs2.x),
+ getboundary(cy2, curs2.x + curs2.width));
+ }
+
+
+
+ // Externally Visible Traps //////////////////////////////////////////////////////////
+
+ _focused = function(f) {
+ if (!f) {
+ if (curs.width == 1) curs.invisible = true;
+ } else {
+ curs.invisible = false;
+ }
+ }
+
+ // returns the currently-selected text
+ __selection = function() {
+ var ret = "";
+ if (cy1 == cy2) {
+ ret += thisbox[cy1].text.substring(cx1, cx2);
+ } else {
+ ret += thisbox[cy1].text.substring(cx1) + "\n";
+ for(var i=cy1 + 1; cy2 > i; i++) ret += thisbox[i].text + "\n";
+ ret += thisbox[cy2].text.substring(0, cx2);
+ }
+ return ret;
+ }
+
+ _font = function(f) {
+ curs.height = curs2.height = lineheight = f == null ? xwt.textheight() : xwt.textheight(f);
+ for(var i=0; numchildren>i; i++) {
+ thisbox[i].font = f;
+ if (numchildren - numcursors > i) thisbox[i].minheight = lineheight;
+ }
+ }
+ font = null;
+
+ _editable = function(e) {
+ if (e) {
+ auto_focus = true;
+ color = "white";
+ } else {
+ auto_focus = false;
+ focused = false;
+ curs.invisible = true;
+ curs2.invisible = true;
+ }
+ }
+
+ _multiline = function(m) {
+ if (m and m != "false") {
+ while(numchildren > numcursors + 1) thisbox[1].thisbox = null;
+ arguments.cascade(true);
+ } else {
+ arguments.cascade(false);
+ }
+ }
+
+ __text = function(t) {
+ if (arguments.length == 0) {
+ var ret = "";
+ for(var i=0; numchildren - numcursors>i; i++) ret = ret + thisbox[i].text + "\n";
+ return ret.substring(0, ret.length - 1); // chop off trailing CR
+ }
+ var me = this;
+ for(var i = numchildren - numcursors - 1; i >= 0; i--) {
+ static.boxen[static.boxen.length] = me[i];
+ me[i].thisbox = null;
+ }
+ cy1 = 0; cy2 = 0; cx1 = 0; cx2 = 0;
+ xwt.thread = function() {
+ try { insert_text(t); } catch (e) { xwt.println(e); }
+ }
+ }
+
+ last_was_a_kill = false; // indicates if the last "action" was a kill -- lets us know if we
+ // we should append newly killed text to the kill buffer or clear it
+
+
+
+ // Text Manipulation /////////////////////////////////////////////////////////////
+
+ // deletes the region between the start and end of the selection
+ var nuke_selection = function() {
+ if (cy1 != cy2 and thisbox[cy1].text != null and thisbox[cy2].text != null) {
+ thisbox[cy1].text = thisbox[cy1].text.substring(0, cx1) + thisbox[cy2].text.substring(cx2);
+ for(var i=cy1 + 1; cy2 >= i; i++) thisbox[cy1 + 1].thisbox = null;
+ cy2 = cy1;
+ cx2 = cx1;
+ } else {
+ thisbox[cy1].text = thisbox[cy1].text.substring(0, cx1) + thisbox[cy1].text.substring(cx2);
+ cx2 = cx1;
+ }
+ }
+
+ var getbox = function() {
+ if (static.boxen.length == 0) static.boxen[0] = xwt.newBox("box");
+ var ret = static.boxen[static.boxen.length - 1];
+ static.boxen.length--;
+ ret.minheight = lineheight;
+ ret.vshrink = true;
+ ret.textcolor = "black";
+ ret.font = font;
+ ret.align = "topleft";
+ ret.invisible = false;
+ ret.absolute = false;
+ return ret;
+ }
+
+ // nukes the selected region and inserts arg in its place
+ var insert_text = function(arg) {
+ var mine = master = xwt.math.random();
+ if (!multiline) arg = (arg + "").replace(static.cr_regexp, "");
+ if (curs.width > 1) nuke_selection();
+ while(!(arg == null || arg == "" || arg.replace == null)) {
+ cy2 = cy1; cx2 = cx1;
+ // insert a new line if necessary
+ if (arg.charAt(0) == '\n') {
+ var n = getbox();
+ thisbox[cy1 + 1] = n;
+ n.text = thisbox[cy1].text.substring(cx1);
+ thisbox[cy1].text = thisbox[cy1].text.substring(0, cx1);
+ cy1++; cy2 = cy1; cx1 = 0; cx2 = 0;
+ arg = arg.substring(1);
+ xwt.yield();
+ if (master != mine) return;
+ }
+ var upto = arg.indexOf('\n') == -1 ? arg.length : arg.indexOf('\n');
+ var itext = arg.substring(0, upto);
+ thisbox[cy1].text = thisbox[cy1].text.substring(0, cx1) + itext + thisbox[cy1].text.substring(cx1);
+ cx1 += itext.length;
+ arg = arg.substring(upto);
+ }
+ cy2 = cy1;
+ cx2 = cx1;
+ master = 0;
+ }
+
+
+ // XWT Event Handlers //////////////////////////////////////////////////////////////
+
+_Press2 = function() {
+xwt.println("curs.x = " + curs.x);
+xwt.println("curs.y = " + curs.y);
+xwt.println("curs.w = " + curs.width);
+xwt.println("curs.h = " + curs.height);
+xwt.println("curs.i = " + curs.invisible);
+xwt.println("curs[] = " + indexof(curs));
+xwt.println("numchi = " + numchildren);
+}
+
+ _Press1 = function() {
+ focused = true;
+ if (master != 0) return;
+ last_was_a_kill = false;
+
+ root._Move = function() {
+ curs.invisible = false;
+ var mousey_row = getrow(mousey);
+ var pressy_row = getrow(pressy);
+
+ if (pressy_row > mousey_row || (mousey_row == pressy_row and pressx > mousex)) {
+ cy1 = mousey_row;
+ cy2 = pressy_row;
+ cx1 = getboundary(cy1, mousex);
+ cx2 = getboundary(cy2, pressx);
+
+ } else {
+ var newcy2 = mousey_row;
+ if (newcy2 > numchildren - numcursors - 1) newcy2 = numchildren - numcursors - 1;
+ cy2 = newcy2;
+ cy1 = pressy_row;
+ cx2 = xwt.math.min(getboundary(cy2, mousex), thisbox[cy2].text.length);
+ cx1 = getboundary(cy1, pressx);
+ }
+ }
+
+ root.__Release1 = function() {
+ root._Move = null;
+ root._Release1 = null;
+ if (cx1 != cx2 || cy1 != cy2) xwt.clipboard = selection;
+ }
+
+ pressx = mousex;
+ pressy = mousey;
+ cy1 = getrow(mousey);
+ cy2 = getrow(mousey);
+ cx2 = xwt.math.min(getboundary(cy1, mousex), thisbox[cy1].text.length);
+ cx1 = xwt.math.min(getboundary(cy1, mousex), thisbox[cy1].text.length);
+ }
+
+ __Press3 = function() {
+ last_was_a_kill = false;
+ cy1 = cy2 = getrow(mousey);
+ cx2 = cx1 = xwt.math.min(getboundary(cy1, mousex), thisbox[cy1].text.length);
+ if (xwt.clipboard != null and editable) insert_text(xwt.clipboard);
+ }
+
+ _DoubleClick1 = function() {
+ cy1 = cy2 = getrow(mousey);
+ cx1 = 0; cx2 = thisbox[cy1].text.length;
+ xwt.clipboard = selection;
+ }
+
+ var key_C_Q = function() {
+
+ // C-q => fill
+ var start = cy1;
+ var stop = cy2;
+
+ // if a region wasn't selected, fill this paragraph alone
+ if (cy1 == cy2) {
+ while (start > 0 and thisbox[start].text.match(static.nonwhitespace)) start--;
+ while (numchildren - numcursors > stop and thisbox[stop].text.match(static.nonwhitespace)) stop++;
+ }
+
+ // pull out the text-to-be filled
+ var filltext = "";
+ for(var i=start; stop >= i; i++) {
+ filltext += thisbox[start].text + "\n";
+ if (numchildren > numcursors) {
+ static.boxen[static.boxen.length] = thisbox[start];
+ thisbox[start].thisbox = null;
+ } else {
+ thisbox[start].text = "";
+ }
+ }
+
+ xwt.println("prefill text:\n" + filltext);
+
+ filltext = filltext.replace(/^\\s+/);
+
+ var quoted = false;
+ if (filltext.substring(0, 2) == "> ") {
+ filltext = filltext.substring(2).replace(/\n> /g, "\n");
+ quoted = true;
+ }
+
+ filltext = filltext.replace(/\s+/g, " ");
+
+ var newfilltext = "";
+
+ while (filltext.length > 0) {
+ var tmp = filltext.substring(0, 78);
+ var tmp2;
+ if (tmp.lastIndexOf(' ') != -1) {
+ filltext = tmp.substring(tmp.lastIndexOf(' ') + 1) + filltext.substring(78);
+ tmp2 = tmp.substring(0, tmp.lastIndexOf(' '));
+ } else {
+ tmp2 = filltext;
+ filltext = "";
+ }
+ newfilltext += (quoted ? "> " : "") + tmp2 + "\n";
+ }
+
+ cy1 = start; cy2 = start;
+ cx1 = 0; cx2 = 0;
+
+ xwt.println("newfilltext:\n" + newfilltext);
+
+ insert_text("\n" + newfilltext + "\n");
+ }
+
+ var key_C_W = function() { if (xwt.clipboard != null) xwt.clipboard = selection;
+ if (editable) nuke_selection();
+ }
+ var key_C_K = function() { if (!editable) return;
+ if (1 >= curs.width and cx1 == thisbox[cy1].text.length) cx2++;
+ else if (1 >= curs.width) cx2 = thisbox[cy2].text.length;
+ if (!last_was_a_kill and xwt.clipboard != null) xwt.clipboard = "";
+ if (xwt.clipboard != null) xwt.clipboard += selection;
+ else xwt.clipboard = selection;
+ nuke_selection();
+ }
+
+ _KeyPressed = function(key) {
+ if (master != 0) return;
+ if (!focused) return;
+ var kill = false;
+ if (key == "C-a") { cx1 = 0; cx2 = 0; }
+ else if (key == "C-e") { cx1 = thisbox[cy2].text.length; cx2 = cx1; cy2 = cy1; }
+ else if (key == "C-d" || key == "delete") { if (!editable) return; if (1 >= curs.width) cx2++; nuke_selection(); }
+ else if (key == "C-y") { if (xwt.clipboard != null and editable) insert_text(xwt.clipboard); }
+ else if (key == "C-k") key_C_K();
+ else if (key == "C-w") key_C_W();
+ else if (key == "C-h" || key == "back_space") {
+ if (!editable) return;
+ if (cx1 == 0 and cx2 == 0 and cy1 == 0 and cy2 == 0) return;
+ if (1 >= curs.width) cx1--;
+ nuke_selection();
+ }
+ else if (key == "C-b" || key == "left") { cx1--; cy2 = cy1; cx2 = cx1; }
+ else if (key == "C-p" || key == "up") { cy1--; cy2 = cy1; cx2 = cx1; }
+ else if (key == "C-f" || key == "right") { cx2++; cy1 = cy2; cx1 = cx2; }
+ else if (key == "C-m" || key == "C-o" || key == "enter" || key == "C-j") { if (editable) insert_text("\n"); }
+ else if (key == "C-n" || key == "down") { cy2++; cy1 = cy2; cx1 = cx2; }
+ else if (key == "C-q") key_C_Q();
+ else if (key.length == 1) {
+ if (!editable) return;
+ if (curs.width > 1) nuke_selection();
+ thisbox[cy1].text = thisbox[cy1].text.substring(0, cx1) +
+ key +
+ thisbox[cy1].text.substring(cx1);
+ cx2 = cx1 + 1; cx1 = cx2;
+ }
+
+ last_was_a_kill = key == "C-k";
+ }
+
+ </template>
+</xwt>
+