1 <!-- Copyright 2002 Adam Megacz, see the COPYING file for licensing [LGPL] -->
6 multiline : boolean -- if true, lines will be broken at newline characters
7 editable : boolean -- if false, the text cannot be changed, although it can be copied
9 <redirect target="self"/>
13 var cr_regexp = /[\n\r]/g;
14 var nonwhitespace = /\S/;
17 <template textcolor="black" orient="vertical" vpad="2" editable="true" multiline="false" text="" cursor="text">
19 // Structural Stuff //////////////////////////////////////////////////////////////////////
21 // Cursor1 is the cursor -- when there is no selection, its width is
22 // 1, and its height is the height of one line. When the user selects
23 // a one-line region, Cursor1 is expanded to cover that region (but
24 // placed behind the text on the z-axis). When the user selects more
25 // than one line, Cursor1 covers the background of the first line of
26 // the selection, Cursor3 covers the last line, and the intermediate
27 // lines get their color set to "blue".
29 var curs = $curs; // the main cursor
30 var curs2 = $curs2; // the "backup" cursor for multiline selections
31 var numcursors = 2; // the number of non-textline children of this box
34 <box text="" align="topleft"/>
35 <box id="curs" absolute="true" textcolor="white" color="blue" width="1" y="0" x="1" invisible="true"/>
36 <box id="curs2" absolute="true" textcolor="white" color="blue" width="1" y="0" x="1" invisible="true"/>
38 // Cursor Manipulation //////////////////////////////////////////////////////////////////////
40 // cx1, cy1 is the coordinates (in characters, not pixels) of the start of the selection
41 // cx2, cy2 is the coordinates (in characters, not pixels) of the end of the selection
42 cx2 = cx1 = cy2 = cy1 = 0;
44 // the x-coordinate of the beginning of the selection, in characters
46 a = xwt.math.floor(a);
47 if (0 > a) { if (cy1 > 0) cy1--; a = thisbox[cy1].text.length; }
48 if (numchildren - numcursors > cy1 and a > thisbox[cy1].text.length) { cy1++; a = 0; }
49 curs.x = xwt.textwidth(font, thisbox[cy1].text.substring(0, a));
54 // the x-coordinate of the end of the selection, in characters
56 a = xwt.math.floor(a);
57 if (0 > a) { cy2--; a = thisbox[cy2].text.length; }
58 if (numchildren - numcursors > cy2 and a > thisbox[cy2].text.length) { cy2++; a = 0; }
63 // the y-coordinate of the beginning of the selection, in characters
65 a = xwt.math.floor(a);
66 if (0 > a) { a = 0; cx1 = 0; }
68 if (cx1 > thisbox[a].text.length) cx1 = thisbox[a].text.length;
72 // the y-coordinate of the end of the selection, in characters
74 a = xwt.math.floor(a);
76 while (a >= numchildren - numcursors) {
79 thisbox[numchildren - numcursors] = b;
81 if (cx2 > thisbox[a].text.length) cx2 = thisbox[a].text.length;
85 // the x-coordinate of the beginning of the selection, measured in _pixels_
87 if (cy1 > numchildren || thisbox[cy1].text == null) return 0;
88 return xwt.textwidth(font, thisbox[cy1].text.substring(0, cx1));
91 // the x-coordinate of the end of the selection, measured in _pixels_
93 if (cy2 > numchildren || thisbox[cy2].text == null) return 0;
94 return xwt.textwidth(font, thisbox[cy2].text.substring(0, cx2));
97 // on the r-th row, this will determine how many characters are to the left of a
98 // point b pixels from the left edge of the edit Algorithm is a binary search,
99 // making educated guesses based on the average width of a character on that line.
100 getboundary = function(r, b) {
102 if (o >= b) return 0;
103 var s = thisbox[r].text;
105 var left = 0; // the left boundary of the search region, in characters
106 var right = s.length; // the right boundary of the search region, in characters
107 var start = 0; // the left boundary of the search region, in pixels
108 // the right boundary of the search region, in pixels
109 var end = xwt.textwidth(font, s);
110 if (b >= end) return right; // short circuit if we're off the end
112 var avgwidth = end / right; // average width of one character
115 var middle = left + (b - start) / avgwidth; // make a guess at where we should look
117 // extra safety guard against infinite loops
118 if (left == right) return left;
119 if (left >= middle) middle = left + 1;
120 if (middle >= right) middle = left - 1;
122 start = xwt.textwidth(font, s.substring(0, middle));
125 } else if (b >= start + xwt.textwidth(font, s.charAt(middle))) {
133 // returns the row containing the point yp pixels from the top of the widget
134 getrow = function(yp) { return xwt.math.min(numchildren - numcursors - 1, xwt.math.floor(yp / lineheight)); }
138 curs.y = thisbox[cy1].y; //lineheight * cy1 + thisbox[0].y;
139 curs.width = px2 - px1;
140 if (1 > curs.width) { curs.width = 1; curs.text = ""; }
141 curs2.invisible = true;
144 curs.width = width - curs.x;
145 curs2.invisible = false;
147 curs2.y = thisbox[cy2].y; //lineheight * cy2;
148 curs2.height = lineheight;
152 for(var i=0; numchildren - numcursors>i; i++) {
153 if (i>cy1 and cy2>i) {
154 thisbox[i].textcolor = "white";
155 thisbox[i].color = "blue";
157 thisbox[i].textcolor = "black";
158 thisbox[i].color = null;
162 curs.text = (thisbox[cy1] == null || thisbox[cy1].text == null) ? null :
163 thisbox[cy1].text.substring(getboundary(cy1, curs.x),
164 getboundary(cy1, curs.x + curs.width));
166 curs2.text = (thisbox[cy2] == null || thisbox[cy2].text == null) ? null :
167 thisbox[cy2].text.substring(getboundary(cy2, curs2.x),
168 getboundary(cy2, curs2.x + curs2.width));
173 // Externally Visible Traps //////////////////////////////////////////////////////////
175 _focused = function(f) {
177 if (curs.width == 1) curs.invisible = true;
179 curs.invisible = false;
183 // returns the currently-selected text
184 __selection = function() {
187 ret += thisbox[cy1].text.substring(cx1, cx2);
189 ret += thisbox[cy1].text.substring(cx1) + "\n";
190 for(var i=cy1 + 1; cy2 > i; i++) ret += thisbox[i].text + "\n";
191 ret += thisbox[cy2].text.substring(0, cx2);
196 _font = function(f) {
197 curs.height = curs2.height = lineheight = f == null ? xwt.textheight() : xwt.textheight(f);
198 for(var i=0; numchildren>i; i++) {
200 if (numchildren - numcursors > i) thisbox[i].minheight = lineheight;
205 _editable = function(e) {
212 curs.invisible = true;
213 curs2.invisible = true;
217 _multiline = function(m) {
218 if (m and m != "false") {
219 while(numchildren > numcursors + 1) thisbox[1].thisbox = null;
220 arguments.cascade(true);
222 arguments.cascade(false);
226 __text = function(t) {
227 if (arguments.length == 0) {
229 for(var i=0; numchildren - numcursors>i; i++) ret = ret + thisbox[i].text + "\n";
230 return ret.substring(0, ret.length - 1); // chop off trailing CR
233 for(var i = numchildren - numcursors - 1; i >= 0; i--) {
234 static.boxen[static.boxen.length] = me[i];
235 me[i].thisbox = null;
237 cy1 = 0; cy2 = 0; cx1 = 0; cx2 = 0;
238 xwt.thread = function() {
239 try { insert_text(t); } catch (e) { xwt.println(e); }
243 last_was_a_kill = false; // indicates if the last "action" was a kill -- lets us know if we
244 // we should append newly killed text to the kill buffer or clear it
248 // Text Manipulation /////////////////////////////////////////////////////////////
250 // deletes the region between the start and end of the selection
251 var nuke_selection = function() {
252 if (cy1 != cy2 and thisbox[cy1].text != null and thisbox[cy2].text != null) {
253 thisbox[cy1].text = thisbox[cy1].text.substring(0, cx1) + thisbox[cy2].text.substring(cx2);
254 for(var i=cy1 + 1; cy2 >= i; i++) thisbox[cy1 + 1].thisbox = null;
258 thisbox[cy1].text = thisbox[cy1].text.substring(0, cx1) + thisbox[cy1].text.substring(cx2);
263 var getbox = function() {
264 if (static.boxen.length == 0) static.boxen[0] = xwt.newBox();
265 var ret = static.boxen[static.boxen.length - 1];
266 static.boxen.length--;
267 ret.minheight = lineheight;
269 ret.textcolor = "black";
271 ret.align = "topleft";
272 ret.invisible = false;
273 ret.absolute = false;
277 // nukes the selected region and inserts arg in its place
278 var insert_text = function(arg) {
279 var mine = master = xwt.math.random();
280 if (!multiline) arg = (arg + "").replace(static.cr_regexp, "");
281 if (curs.width > 1) nuke_selection();
282 while(!(arg == null || arg == "" || arg.replace == null)) {
283 cy2 = cy1; cx2 = cx1;
284 // insert a new line if necessary
285 if (arg.charAt(0) == '\n') {
287 thisbox[cy1 + 1] = n;
288 n.text = thisbox[cy1].text.substring(cx1);
289 thisbox[cy1].text = thisbox[cy1].text.substring(0, cx1);
290 cy1++; cy2 = cy1; cx1 = 0; cx2 = 0;
291 arg = arg.substring(1);
293 if (master != mine) return;
295 var upto = arg.indexOf('\n') == -1 ? arg.length : arg.indexOf('\n');
296 var itext = arg.substring(0, upto);
297 thisbox[cy1].text = thisbox[cy1].text.substring(0, cx1) + itext + thisbox[cy1].text.substring(cx1);
299 arg = arg.substring(upto);
307 // XWT Event Handlers //////////////////////////////////////////////////////////////
309 _Press2 = function() {
310 xwt.println("curs.x = " + curs.x);
311 xwt.println("curs.y = " + curs.y);
312 xwt.println("curs.w = " + curs.width);
313 xwt.println("curs.h = " + curs.height);
314 xwt.println("curs.i = " + curs.invisible);
315 xwt.println("curs[] = " + indexof(curs));
316 xwt.println("numchi = " + numchildren);
319 _Press1 = function() {
321 if (master != 0) return;
322 last_was_a_kill = false;
324 root._Move = function() {
325 curs.invisible = false;
326 var mousey_row = getrow(mousey);
327 var pressy_row = getrow(pressy);
329 if (pressy_row > mousey_row || (mousey_row == pressy_row and pressx > mousex)) {
332 cx1 = getboundary(cy1, mousex);
333 cx2 = getboundary(cy2, pressx);
336 var newcy2 = mousey_row;
337 if (newcy2 > numchildren - numcursors - 1) newcy2 = numchildren - numcursors - 1;
340 cx2 = xwt.math.min(getboundary(cy2, mousex), thisbox[cy2].text.length);
341 cx1 = getboundary(cy1, pressx);
345 root.__Release1 = function() {
347 root._Release1 = null;
348 if (cx1 != cx2 || cy1 != cy2) xwt.clipboard = selection;
353 cy1 = getrow(mousey);
354 cy2 = getrow(mousey);
355 cx2 = xwt.math.min(getboundary(cy1, mousex), thisbox[cy1].text.length);
356 cx1 = xwt.math.min(getboundary(cy1, mousex), thisbox[cy1].text.length);
359 __Press3 = function() {
360 last_was_a_kill = false;
361 cy1 = cy2 = getrow(mousey);
362 cx2 = cx1 = xwt.math.min(getboundary(cy1, mousex), thisbox[cy1].text.length);
363 if (xwt.clipboard != null and editable) insert_text(xwt.clipboard);
366 _DoubleClick1 = function() {
367 cy1 = cy2 = getrow(mousey);
368 cx1 = 0; cx2 = thisbox[cy1].text.length;
369 xwt.clipboard = selection;
372 var key_C_Q = function() {
378 // if a region wasn't selected, fill this paragraph alone
380 while (start > 0 and thisbox[start].text.match(static.nonwhitespace)) start--;
381 while (numchildren - numcursors > stop and thisbox[stop].text.match(static.nonwhitespace)) stop++;
384 // pull out the text-to-be filled
386 for(var i=start; stop >= i; i++) {
387 filltext += thisbox[start].text + "\n";
388 if (numchildren > numcursors) {
389 static.boxen[static.boxen.length] = thisbox[start];
390 thisbox[start].thisbox = null;
392 thisbox[start].text = "";
396 xwt.println("prefill text:\n" + filltext);
398 filltext = filltext.replace(/^\\s+/);
401 if (filltext.substring(0, 2) == "> ") {
402 filltext = filltext.substring(2).replace(/\n> /g, "\n");
406 filltext = filltext.replace(/\s+/g, " ");
408 var newfilltext = "";
410 while (filltext.length > 0) {
411 var tmp = filltext.substring(0, 78);
413 if (tmp.lastIndexOf(' ') != -1) {
414 filltext = tmp.substring(tmp.lastIndexOf(' ') + 1) + filltext.substring(78);
415 tmp2 = tmp.substring(0, tmp.lastIndexOf(' '));
420 newfilltext += (quoted ? "> " : "") + tmp2 + "\n";
423 cy1 = start; cy2 = start;
426 xwt.println("newfilltext:\n" + newfilltext);
428 insert_text("\n" + newfilltext + "\n");
431 var key_C_W = function() { if (xwt.clipboard != null) xwt.clipboard = selection;
432 if (editable) nuke_selection();
434 var key_C_K = function() { if (!editable) return;
435 if (1 >= curs.width and cx1 == thisbox[cy1].text.length) cx2++;
436 else if (1 >= curs.width) cx2 = thisbox[cy2].text.length;
437 if (!last_was_a_kill and xwt.clipboard != null) xwt.clipboard = "";
438 if (xwt.clipboard != null) xwt.clipboard += selection;
439 else xwt.clipboard = selection;
443 _KeyPressed = function(key) {
444 if (master != 0) return;
445 if (!focused) return;
447 if (key == "C-a") { cx1 = 0; cx2 = 0; }
448 else if (key == "C-e") { cx1 = thisbox[cy2].text.length; cx2 = cx1; cy2 = cy1; }
449 else if (key == "C-d" || key == "delete") { if (!editable) return; if (1 >= curs.width) cx2++; nuke_selection(); }
450 else if (key == "C-y") { if (xwt.clipboard != null and editable) insert_text(xwt.clipboard); }
451 else if (key == "C-k") key_C_K();
452 else if (key == "C-w") key_C_W();
453 else if (key == "C-h" || key == "back_space") {
454 if (!editable) return;
455 if (cx1 == 0 and cx2 == 0 and cy1 == 0 and cy2 == 0) return;
456 if (1 >= curs.width) cx1--;
459 else if (key == "C-b" || key == "left") { cx1--; cy2 = cy1; cx2 = cx1; }
460 else if (key == "C-p" || key == "up") { cy1--; cy2 = cy1; cx2 = cx1; }
461 else if (key == "C-f" || key == "right") { cx2++; cy1 = cy2; cx1 = cx2; }
462 else if (key == "C-m" || key == "C-o" || key == "enter" || key == "C-j") { if (editable) insert_text("\n"); }
463 else if (key == "C-n" || key == "down") { cy2++; cy1 = cy2; cx1 = cx2; }
464 else if (key == "C-q") key_C_Q();
465 else if (key.length == 1) {
466 if (!editable) return;
467 if (curs.width > 1) nuke_selection();
468 thisbox[cy1].text = thisbox[cy1].text.substring(0, cx1) +
470 thisbox[cy1].text.substring(cx1);
471 cx2 = cx1 + 1; cx1 = cx2;
474 last_was_a_kill = key == "C-k";