--- /dev/null
+<!-- Copyright 2002 NeuronForge Pty Ltd, see COPYING file for licensing [LGPL] -->
+<ibex>
+ 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.
+
+ TRAPS:
+
+ - 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.
+
+
+ FUNCTIONS:
+
+ 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.
+
+ - 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.
+
+
+ THEME NOTES:
+
+ - 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).
+
+
+ IMPLEMENTATION NOTES:
+
+ - 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 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.
+
+ - A reference to each cursor is stored in the static area, and a global thread is used to call the _blink trap on cursors.
+
+ - Boxes are also stored in a static array when unused, however the effective value of this is questionable, and should be
+ properly benchmarked.
+
+ - 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.
+
+
+ TODO:
+ - keep length value up to date so limit checking works.
+ - insert key
+
+ <preapply name="org.ibex.builtin.edit_lib"/>
+
+ <template multiline="false" disabled="false" editable="true" wrap="none" font="sansserif"
+ selectcolor="blue" selecttextcolor="#FFFFFF" color="#FFFFFF" orient="vertical">
+
+ content = $content;
+ curs = $curs;
+ sel1 = $sel1;
+ sel2 = $sel2;
+
+ // 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; }
+ }
+ ref_press(content[content.numchildren -1]);
+ }
+
+ _focused = function(f) {
+ 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 (ibex.control) {
+ if (key == 'a') {
+ selectText(0, 0, content.numchildren -1, content[content.numchildren -1].fulltext.length);
+ arguments.cascade(null);
+ } else if (key == 'x') {
+ ibex.clipboard = selection; deleteSelection();
+ arguments.cascade(null);
+ } else if (key == 'c') {
+ ibex.clipboard = selection;
+ arguments.cascade(null);
+ } else if (key == 'v') {
+ deleteSelection(); insertText(curs.cl, curs.cx, ibex.clipboard);
+ textChangd = true;
+ arguments.cascade(null);
+ } else {
+ arguments.cascade(k);
+ }
+ } else {
+ arguments.cascade(k);
+ }
+ } else if (key == "left") {
+ var cl, cx;
+
+ if (ibex.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 {
+ // 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; }
+ }
+
+ if (ibex.shift) { updateSelectionCx(cl, cx); }
+ else { clearSelection(); moveCursor(cl, cx); }
+ arguments.cascade(null);
+
+ } else if (key == "right") {
+ var cl, cx;
+
+ if (ibex.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 {
+ // 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; }
+ }
+
+ if (ibex.shift) { updateSelectionCx(cl, cx); }
+ else { clearSelection(); moveCursor(cl, cx); }
+ arguments.cascade(null);
+
+ } else if (key == "down") {
+ var cl, cy;
+ if (ibex.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 (ibex.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 (ibex.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; }
+ }
+
+ if (ibex.shift) { updateSelection(cl, cy, curs.px); }
+ else { var px = curs.px; clearSelection(); moveCursorToCy(cl, cy, px); }
+ arguments.cascade(null);
+
+ } else if (key == "home") {
+ var cy;
+ if (ibex.control) { cy = 0; }
+ else { cy = curs.cy; }
+
+ if (ibex.shift) { updateSelection(curs.cl, cy, 0); }
+ else { var cl = curs.cl; clearSelection(); moveCursorToCy(cl, cy, 0); }
+ arguments.cascade(null);
+
+ } else if (key == "end") {
+ var cy;
+ if (ibex.control) { cy = content[curs.cl].numchildren -1; }
+ else { cy = curs.cy; }
+
+ if (ibex.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);
+
+ } else if (key == "insert") {
+ ibex.println("NOT YET IMPLEMENTED: insert key"); // TODO
+ arguments.cascade(null);
+ }
+ }
+
+ <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>
+</ibex>