2003/05/12 05:05:36
authormegacz <megacz@xwt.org>
Fri, 30 Jan 2004 07:00:15 +0000 (07:00 +0000)
committermegacz <megacz@xwt.org>
Fri, 30 Jan 2004 07:00:15 +0000 (07:00 +0000)
darcs-hash:20040130070015-2ba56-e7ecc94475ca0b3361011d73f259e4e6103e85a2.gz

src/org/xwt/builtin/edit.xwt

index d721bb2..707129a 100644 (file)
-<!-- Copyright 2002 Adam Megacz, see the COPYING file for licensing [LGPL] -->
+<!-- Copyright 2002 NeuronForge Pty Ltd, see COPYING file for licensing [LGPL] -->
 <xwt>
+    A single-line or multi-line edit widget. Only handles text with one font and one color, and is capable of line or word wrapping.
 
-    A text edit box.
+    TRAPS:
 
-        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
+    - multiline       : boolean  -- If true, edit widget will expand vertically to handle newlines.
+    - editable        : boolean  -- If true, the user can insert, paste and delete text.
+    - disabled        : boolean  -- If false, user can select text and copy to clipboard.
+    - wrap            : string   -- Either "none", "line" or "word", specifing the form of text wrap to use.
+    - selection       : string   -- Returns the currently selected text. Putting values does nothing, see FUNCTIONS, selectText().
+    - text            : string   -- Represents the complete text of this edit widget. Can be read and written to.
+    - limit           : int      -- A limit imposed on the total number of characters in the edit widget. 0 > limit means no limit.
+    - textcolor       : color    -- Color of the text.
+    - selectcolor     : color    -- Background Color of the currently selected text.
+    - selecttextcolor : color    -- Color of the currently selected text.
+    - textChanged     : boolean  -- Set to true when the contents of the edit widget has changed.
 
-    <redirect target="self"/>
 
-    <static>
-        var boxen = [];
-        var cr_regexp = /[\n\r]/g;
-        var nonwhitespace = /\S/;
-    </static>
+    FUNCTIONS:
 
-    <template textcolor="black" orient="vertical" vpad="2" editable="true" multiline="false" text="" cursor="text">
+    If you wish to directly manipulate the contents of the edit widget consider using these functions to speed up manipulation.
+    All line references are based on hard carrige returns. You do not have to consider soft line wraps when making calculations.
 
-        // Structural Stuff //////////////////////////////////////////////////////////////////////
+    - insertText(line, index, text)                        - Insert text at given char index on given line.
+    - deleteText(startline, startindex, endline, endindex) - Delete text in given range.
+    - selectText(startline, startindex, endline, endindex) - Select text in given range.
+    - clearSelection()                                     - Deselect any current selection.
+    - deleteSelection()                                    - Delete the text within the current selection range.
+    - moveCursor(line, index)                              - Move the cursor to the given position.
 
-        // 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;
+    THEME NOTES:
 
-        <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"/>
+    - Most of the implementation of this widget does not need to be considered in a theme, however a particular theme may wish
+      to override _KeyPressed to add extra theme features (eg. Monopoly word skip on Ctrl+Left Arrow).
 
-        // 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;
+    IMPLEMENTATION NOTES:
 
-        // 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));
-        }
+    - In the implementation of the edit widget, there are two systems (loosely similar to model-view) used to reference text.
+    
+      The first, referencing each 'hard' line (from one carrige return to the next) as a box inside $content
+      with the string property fulltext representing the contents of the row is used as the text model.
+      Text is inserted, removed and read using the $content[cl].fulltext properties.
 
-        // 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));
-        }
+      The second is the on screen reprsentation of the text. Each $content[cl] box can have any number of children ( must be > 0)
+      called 'soft' lines. These softlines are created/managed at the discretion of the $content[cl]._fulltext trap. This means
+      different wrapping systems can be completely isolated inside the _fulltext trap. The only components of the system that
+      work outside of the model are cursor and selection positioning. The functions that manipulate these features have public
+      functions that mimic the 'model' style functions, but also have private internal functions that use cy and px variables
+      to reference location.
 
-        // 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) {
+    - A reference to each cursor is stored in the static area, and a global thread is used to call the _blink trap on cursors.
 
-            if (o >= b) return 0;
-            var s = thisbox[r].text;
+    - Boxes are also stored in a static array when unused, however the effective value of this is questionable, and should be
+      properly benchmarked.
 
-            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
+    - If moving the cursor to a different cl line after changing the structure of the edit box, be sure to call the funciton from
+      a background thread. Otherwise, the position x,y values will be wrong.
 
-            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
+    TODO:
+    - keep length value up to date so limit checking works.
+    - insert key
 
-                // extra safety guard against infinite loops
-                if (left == right) return left;
-                if (left >= middle) middle = left + 1;
-                if (middle >= right) middle = left - 1;
+    <preapply name="org.xwt.builtin.edit_lib"/>
 
-                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;
-                }
-            }
-        }
+    <template multiline="false" disabled="false" editable="true" wrap="none" font="sansserif"
+              selectcolor="blue" selecttextcolor="#FFFFFF" color="#FFFFFF" orient="vertical">
 
-        // 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;
-            }
+        content = $content;
+        curs    = $curs;
+        sel1    = $sel1;
+        sel2    = $sel2;
 
-            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;
-                }
+        // Handles key events that occur in the surrounding whitespace.
+        $vspace._Press1 = function(v) { ref_press(content[content.numchildren -1]); }
+        $hspace._Press1 = function(v) {
+            for (var i=0; content.numchildren > i; i++) {
+                if (content[i].y + content[i].height >= content.mousey) { ref_press(content[i]); return; }
             }
-
-            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));
+            ref_press(content[content.numchildren -1]);
         }
 
-
-
-        // 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();
-            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;
+            curs.focused = f; arguments.cascade(f); curs.blink = false;
+            if (!f) { clearSelection(); }
+        }
+
+        _disabled = function(d) { if (d) { cursor = "default"; } else { cursor = "text"; } }
+
+        _keypress = function(k) {
+            if (k == null || disabled) return;
+
+            var key = k.substring(k.lastIndexOf('-')+1).toLowerCase();
+
+            if (key.length == 1) {
+                if (xwt.control) {
+                    if (key == 'a') {
+                        selectText(0, 0, content.numchildren -1, content[content.numchildren -1].fulltext.length);
+                        arguments.cascade(null);
+                    } else if (key == 'x') {
+                        xwt.clipboard = selection; deleteSelection();
+                        arguments.cascade(null);
+                    } else if (key == 'c') {
+                        xwt.clipboard = selection;
+                        arguments.cascade(null);
+                    } else if (key == 'v') {
+                        deleteSelection(); insertText(curs.cl, curs.cx, xwt.clipboard);
+                        textChangd = true;
+                        arguments.cascade(null);
+                    } else {
+                        arguments.cascade(k);
+                    }
+                } else {
+                    arguments.cascade(k);
                 }
-                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 if (key == "left") {
+                var cl, cx;
+
+                if (xwt.control) {
+                    // Skip word algorithm. Ugly to look at.
+                    cl = curs.cl; cx = curs.cx;
+
+                    while (true) {
+                        for (cx--; cx >= 0; cx--) { if (content[cl].fulltext.charAt(cx) != ' ') break; }
+                        if (0 > cx) { if (cl > 0) { cl--; cx = content[cl].fulltext.length; } else { break; } }
+
+                        findendchar: for (cx--; cx >= 0; cx--) {
+                            switch (content[cl].fulltext.charAt(cx)) {
+                                case ' ': break findendchar;
+                                case '-': break findendchar;
+                            }
+                        }
+                        cx++; break;
+                    }
                 } 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);
+                    // Use right boundry of selection, otherwise use the cursor.
+                    if (sel1.cl != -1) { cl = sel1.cl; cx = calcCx(cl, sel1.cy, sel1.px) -1; }
+                    else               { cl = curs.cl; cx = curs.cx -1; }
                 }
-            }
-
-            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;
+                if (xwt.shift) { updateSelectionCx(cl, cx); }
+                else           { clearSelection(); moveCursor(cl, cx); }
+                arguments.cascade(null);
+
+            } else if (key == "right") {
+                var cl, cx;
+
+                if (xwt.control) {
+                    // Skip word algorithm. Ugly to look at.
+                    cl = curs.cl; cx = curs.cx;
+
+                    while (true) {
+                        for (cx++; content[cl].fulltext.length > cx; cx++) { if (content[cl].fulltext.charAt(cx) != ' ') break; }
+                        if (cx > content[cl].fulltext.length) { if (content.numchildren > cl) { cl++; cx = 0; } else { break; } }
+
+                        findendchar: for (cx++; content[cl].fulltext.length > cx; cx++) {
+                            switch (content[cl].fulltext.charAt(cx)) {
+                                case ' ': break findendchar;
+                                case '-': break findendchar;
+                            }
+                        }
+                        break;
+                    }
                 } else {
-                    thisbox[start].text = "";
+                    // Use right boundry of selection, otherwise use the cursor.
+                    if (sel2.cl != -1) { cl = sel2.cl; cx = calcCx(cl, sel2.cy, sel2.px) +1; }
+                    else               { cl = curs.cl; cx = curs.cx +1; }
                 }
-            }
 
-            xwt.println("prefill text:\n" + filltext);            
+                if (xwt.shift) { updateSelectionCx(cl, cx); }
+                else           { clearSelection(); moveCursor(cl, cx); }
+                arguments.cascade(null);
+
+            } else if (key == "down") {
+                var cl, cy;
+                if (xwt.control) { cl = content.numchildren -1; cy = content[cl].numchildren -1; }
+                else  { if (curs.cy == content[curs.cl].numchildren -1) { cl = curs.cl +1; cy = 0; } else { cl = curs.cl; cy = curs.cy + 1; } }
+
+                if (xwt.shift) { updateSelection(cl, cy, curs.px); }
+                else           { var px = curs.px; clearSelection(); moveCursorToCy(cl, cy, px); }
+                arguments.cascade(null);
+
+            } else if (key == "up") {
+                var cl, cy;
+                if (xwt.control) { cl = 0; cy = content[0].numchildren -1; }
+                else  {
+                    if (curs.cy == 0) { if (curs.cl == 0) { cl=0; cy=0; } else { cl = curs.cl -1; cy = content[cl].numchildren -1; } }
+                    else { cl = curs.cl; cy = curs.cy -1; }
+                }
 
-            filltext = filltext.replace(/^\\s+/);
+                if (xwt.shift) { updateSelection(cl, cy, curs.px); }
+                else           { var px = curs.px; clearSelection(); moveCursorToCy(cl, cy, px); }
+                arguments.cascade(null);
 
-            var quoted = false;
-            if (filltext.substring(0, 2) == "> ") {
-                filltext = filltext.substring(2).replace(/\n> /g, "\n");
-                quoted = true;
-            }
+            } else if (key == "home") {
+                var cy;
+                if (xwt.control) { cy = 0; }
+                else             { cy = curs.cy; }
 
-            filltext = filltext.replace(/\s+/g, " ");
+                if (xwt.shift) { updateSelection(curs.cl, cy, 0); }
+                else           { var cl = curs.cl; clearSelection(); moveCursorToCy(cl, cy, 0); }
+                arguments.cascade(null);
 
-            var newfilltext = "";
+            } else if (key == "end") {
+                var cy;
+                if (xwt.control) { cy = content[curs.cl].numchildren -1; }
+                else             { cy = curs.cy; }
 
-            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";  
-            }
+                if (xwt.shift) { updateSelection(curs.cl, cy, content[curs.cl][cy].text.length); }
+                else           { var cl = curs.cl; clearSelection(); moveCursorToCy(cl, cy, content[cl][cy].text.length); }
+                arguments.cascade(null);
 
-            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 == "insert") {
+                xwt.println("NOT YET IMPLEMENTED: insert key"); // TODO
+                arguments.cascade(null);
             }
-            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";
         }
-    
+
+        <box orient="horizontal">
+            <box id="content" hpad="0" vpad="0" align="topleft" orient="vertical" shrink="true"/>
+            <box id="hspace"/>
+        </box>
+        <box id="vspace"/>
+
+        <box absolute="true" id="sel1" cl="0" cy="0" px="0" shrink="true"/>
+        <box absolute="true" id="sel2" cl="0" cy="0" px="0" shrink="true"/>
+        <box absolute="true" id="curs" cl="0" cx="0" cy="0" px="0" width="1" blink="false" color="black">
+            _blink = function(v) { invisible = (focused and !disabled) ? v : true; }
+        </box>
     </template>
 </xwt>
-