mass rename and rebranding from xwt to ibex - fixed to use ixt files
[org.ibex.core.git] / src / org / ibex / builtin / edit.ibex
1 <!-- Copyright 2002 NeuronForge Pty Ltd, see COPYING file for licensing [LGPL] -->
2 <ibex>
3     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.
4
5     TRAPS:
6
7     - multiline       : boolean  -- If true, edit widget will expand vertically to handle newlines.
8     - editable        : boolean  -- If true, the user can insert, paste and delete text.
9     - disabled        : boolean  -- If false, user can select text and copy to clipboard.
10     - wrap            : string   -- Either "none", "line" or "word", specifing the form of text wrap to use.
11     - selection       : string   -- Returns the currently selected text. Putting values does nothing, see FUNCTIONS, selectText().
12     - text            : string   -- Represents the complete text of this edit widget. Can be read and written to.
13     - limit           : int      -- A limit imposed on the total number of characters in the edit widget. 0 > limit means no limit.
14     - textcolor       : color    -- Color of the text.
15     - selectcolor     : color    -- Background Color of the currently selected text.
16     - selecttextcolor : color    -- Color of the currently selected text.
17     - textChanged     : boolean  -- Set to true when the contents of the edit widget has changed.
18
19
20     FUNCTIONS:
21
22     If you wish to directly manipulate the contents of the edit widget consider using these functions to speed up manipulation.
23     All line references are based on hard carrige returns. You do not have to consider soft line wraps when making calculations.
24
25     - insertText(line, index, text)                        - Insert text at given char index on given line.
26     - deleteText(startline, startindex, endline, endindex) - Delete text in given range.
27     - selectText(startline, startindex, endline, endindex) - Select text in given range.
28     - clearSelection()                                     - Deselect any current selection.
29     - deleteSelection()                                    - Delete the text within the current selection range.
30     - moveCursor(line, index)                              - Move the cursor to the given position.
31
32
33     THEME NOTES:
34
35     - Most of the implementation of this widget does not need to be considered in a theme, however a particular theme may wish
36       to override _KeyPressed to add extra theme features (eg. Monopoly word skip on Ctrl+Left Arrow).
37
38
39     IMPLEMENTATION NOTES:
40
41     - In the implementation of the edit widget, there are two systems (loosely similar to model-view) used to reference text.
42     
43       The first, referencing each 'hard' line (from one carrige return to the next) as a box inside $content
44       with the string property fulltext representing the contents of the row is used as the text model.
45       Text is inserted, removed and read using the $content[cl].fulltext properties.
46
47       The second is the on screen reprsentation of the text. Each $content[cl] box can have any number of children ( must be > 0)
48       called 'soft' lines. These softlines are created/managed at the discretion of the $content[cl]._fulltext trap. This means
49       different wrapping systems can be completely isolated inside the _fulltext trap. The only components of the system that
50       work outside of the model are cursor and selection positioning. The functions that manipulate these features have public
51       functions that mimic the 'model' style functions, but also have private internal functions that use cy and px variables
52       to reference location.
53
54     - A reference to each cursor is stored in the static area, and a global thread is used to call the _blink trap on cursors.
55
56     - Boxes are also stored in a static array when unused, however the effective value of this is questionable, and should be
57       properly benchmarked.
58
59     - If moving the cursor to a different cl line after changing the structure of the edit box, be sure to call the funciton from
60       a background thread. Otherwise, the position x,y values will be wrong.
61
62
63     TODO:
64     - keep length value up to date so limit checking works.
65     - insert key
66
67     <preapply name="org.ibex.builtin.edit_lib"/>
68
69     <template multiline="false" disabled="false" editable="true" wrap="none" font="sansserif"
70               selectcolor="blue" selecttextcolor="#FFFFFF" color="#FFFFFF" orient="vertical">
71
72         content = $content;
73         curs    = $curs;
74         sel1    = $sel1;
75         sel2    = $sel2;
76
77         // Handles key events that occur in the surrounding whitespace.
78         $vspace._Press1 = function(v) { ref_press(content[content.numchildren -1]); }
79         $hspace._Press1 = function(v) {
80             for (var i=0; content.numchildren > i; i++) {
81                 if (content[i].y + content[i].height >= content.mousey) { ref_press(content[i]); return; }
82             }
83             ref_press(content[content.numchildren -1]);
84         }
85
86         _focused = function(f) {
87             curs.focused = f; arguments.cascade(f); curs.blink = false;
88             if (!f) { clearSelection(); }
89         }
90
91         _disabled = function(d) { if (d) { cursor = "default"; } else { cursor = "text"; } }
92
93         _keypress = function(k) {
94             if (k == null || disabled) return;
95
96             var key = k.substring(k.lastIndexOf('-')+1).toLowerCase();
97
98             if (key.length == 1) {
99                 if (ibex.control) {
100                     if (key == 'a') {
101                         selectText(0, 0, content.numchildren -1, content[content.numchildren -1].fulltext.length);
102                         arguments.cascade(null);
103                     } else if (key == 'x') {
104                         ibex.clipboard = selection; deleteSelection();
105                         arguments.cascade(null);
106                     } else if (key == 'c') {
107                         ibex.clipboard = selection;
108                         arguments.cascade(null);
109                     } else if (key == 'v') {
110                         deleteSelection(); insertText(curs.cl, curs.cx, ibex.clipboard);
111                         textChangd = true;
112                         arguments.cascade(null);
113                     } else {
114                         arguments.cascade(k);
115                     }
116                 } else {
117                     arguments.cascade(k);
118                 }
119             } else if (key == "left") {
120                 var cl, cx;
121
122                 if (ibex.control) {
123                     // Skip word algorithm. Ugly to look at.
124                     cl = curs.cl; cx = curs.cx;
125
126                     while (true) {
127                         for (cx--; cx >= 0; cx--) { if (content[cl].fulltext.charAt(cx) != ' ') break; }
128                         if (0 > cx) { if (cl > 0) { cl--; cx = content[cl].fulltext.length; } else { break; } }
129
130                         findendchar: for (cx--; cx >= 0; cx--) {
131                             switch (content[cl].fulltext.charAt(cx)) {
132                                 case ' ': break findendchar;
133                                 case '-': break findendchar;
134                             }
135                         }
136                         cx++; break;
137                     }
138                 } else {
139                     // Use right boundry of selection, otherwise use the cursor.
140                     if (sel1.cl != -1) { cl = sel1.cl; cx = calcCx(cl, sel1.cy, sel1.px) -1; }
141                     else               { cl = curs.cl; cx = curs.cx -1; }
142                 }
143
144                 if (ibex.shift) { updateSelectionCx(cl, cx); }
145                 else           { clearSelection(); moveCursor(cl, cx); }
146                 arguments.cascade(null);
147
148             } else if (key == "right") {
149                 var cl, cx;
150
151                 if (ibex.control) {
152                     // Skip word algorithm. Ugly to look at.
153                     cl = curs.cl; cx = curs.cx;
154
155                     while (true) {
156                         for (cx++; content[cl].fulltext.length > cx; cx++) { if (content[cl].fulltext.charAt(cx) != ' ') break; }
157                         if (cx > content[cl].fulltext.length) { if (content.numchildren > cl) { cl++; cx = 0; } else { break; } }
158
159                         findendchar: for (cx++; content[cl].fulltext.length > cx; cx++) {
160                             switch (content[cl].fulltext.charAt(cx)) {
161                                 case ' ': break findendchar;
162                                 case '-': break findendchar;
163                             }
164                         }
165                         break;
166                     }
167                 } else {
168                     // Use right boundry of selection, otherwise use the cursor.
169                     if (sel2.cl != -1) { cl = sel2.cl; cx = calcCx(cl, sel2.cy, sel2.px) +1; }
170                     else               { cl = curs.cl; cx = curs.cx +1; }
171                 }
172
173                 if (ibex.shift) { updateSelectionCx(cl, cx); }
174                 else           { clearSelection(); moveCursor(cl, cx); }
175                 arguments.cascade(null);
176
177             } else if (key == "down") {
178                 var cl, cy;
179                 if (ibex.control) { cl = content.numchildren -1; cy = content[cl].numchildren -1; }
180                 else  { if (curs.cy == content[curs.cl].numchildren -1) { cl = curs.cl +1; cy = 0; } else { cl = curs.cl; cy = curs.cy + 1; } }
181
182                 if (ibex.shift) { updateSelection(cl, cy, curs.px); }
183                 else           { var px = curs.px; clearSelection(); moveCursorToCy(cl, cy, px); }
184                 arguments.cascade(null);
185
186             } else if (key == "up") {
187                 var cl, cy;
188                 if (ibex.control) { cl = 0; cy = content[0].numchildren -1; }
189                 else  {
190                     if (curs.cy == 0) { if (curs.cl == 0) { cl=0; cy=0; } else { cl = curs.cl -1; cy = content[cl].numchildren -1; } }
191                     else { cl = curs.cl; cy = curs.cy -1; }
192                 }
193
194                 if (ibex.shift) { updateSelection(cl, cy, curs.px); }
195                 else           { var px = curs.px; clearSelection(); moveCursorToCy(cl, cy, px); }
196                 arguments.cascade(null);
197
198             } else if (key == "home") {
199                 var cy;
200                 if (ibex.control) { cy = 0; }
201                 else             { cy = curs.cy; }
202
203                 if (ibex.shift) { updateSelection(curs.cl, cy, 0); }
204                 else           { var cl = curs.cl; clearSelection(); moveCursorToCy(cl, cy, 0); }
205                 arguments.cascade(null);
206
207             } else if (key == "end") {
208                 var cy;
209                 if (ibex.control) { cy = content[curs.cl].numchildren -1; }
210                 else             { cy = curs.cy; }
211
212                 if (ibex.shift) { updateSelection(curs.cl, cy, content[curs.cl][cy].text.length); }
213                 else           { var cl = curs.cl; clearSelection(); moveCursorToCy(cl, cy, content[cl][cy].text.length); }
214                 arguments.cascade(null);
215
216             } else if (key == "insert") {
217                 ibex.println("NOT YET IMPLEMENTED: insert key"); // TODO
218                 arguments.cascade(null);
219             }
220         }
221
222         <box orient="horizontal">
223             <box id="content" hpad="0" vpad="0" align="topleft" orient="vertical" shrink="true"/>
224             <box id="hspace"/>
225         </box>
226         <box id="vspace"/>
227
228         <box absolute="true" id="sel1" cl="0" cy="0" px="0" shrink="true"/>
229         <box absolute="true" id="sel2" cl="0" cy="0" px="0" shrink="true"/>
230         <box absolute="true" id="curs" cl="0" cx="0" cy="0" px="0" width="1" blink="false" color="black">
231             _blink = function(v) { invisible = (focused and !disabled) ? v : true; }
232         </box>
233     </template>
234 </ibex>