initial checkin
[org.ibex.widgets.git] / src / ibex / lib / text / edit.t
1 <!-- Copyleft 2004 - see COPYING for details [LGPL] -->
2
3 <ibex>
4
5     // Returns number of chars the pixel position pos is into text t.
6     //
7     // Always rounds down.
8     //
9     //  t     : string -- basis for character counting
10     //  pxpos : int    -- pixel position on the text from string t
11     //  pxlen : int    -- pixel width of text t (often box width)
12     //  tfont : font   -- name of font used for width of text
13     //  tsize : int    -- size of font used for width of text
14     static.getpos = function(t, pxpos, pxlen, tfont, tsize) {
15         // short circuit extremes
16         if (0 >= pxpos) return 0;
17         if (pxpos >= pxlen) return t.length;
18
19         // inital guess based on average character width
20         var guessch = ibex.math.min(t.length, ibex.math.floor(pxpos / (pxlen / t.length)));
21         var guesspx = ibex.ui.font.width(tfont, tsize, t.substring(0, guessch));
22
23         if (guesspx > pxpos) {
24             while (guesspx > pxpos) {
25                 // textwidth of individual character must account for font kerning
26                 guesspx -= ibex.ui.font.width(tfont, tsize, t.substring(guessch -1, guessch +1));
27                 guesspx += ibex.ui.font.width(tfont, tsize, t.charAt(guessch));
28                 guessch--;
29             }
30         } else if (pxpos > guesspx) {
31             while (pxpos > guesspx) {
32                 guessch++;
33                 if (guessch >= t.length) break;
34                 guesspx += ibex.ui.font.width(tfont, tsize, t.substring(guessch -1, guessch+1));
35                 guesspx -= ibex.ui.font.width(tfont, tsize, t.charAt(guessch));
36             }
37             guessch--;  // round down
38         }
39
40         return guessch;
41     }
42
43
44     <ui:box shrink="true">
45
46         // add and remove cursor traps as the cursor box changes
47         thisbox.cur ++= function(newcur) {
48             if (cur) {
49                 cur.pos  --= cur_pos;
50                 cur.posx --= cur_posx_read;
51                 cur.posx --= cur_posx_write;
52             }
53             if (newcur) {
54                 newcur.pos  ++= cur_pos;
55                 newcur.posx ++= cur_posx_read;
56                 newcur.posx ++= cur_posx_write;
57             }
58         }
59
60         // add and remove selection traps as the selection box changes
61         thisbox.sel ++= function(newsel) {
62             if (sel) {
63                 sel.pos1 --= sel_pos1;
64                 sel.pos2 --= sel_pos2;
65             }
66             if (newsel) {
67                 newsel.pos1 ++= sel_pos1;
68                 newsel.pos2 ++= sel_pos2;
69             }
70         }
71
72         // designed to trap cur.pos -- updates position of the cursor
73         // based on the character position stored in cur.pos
74         var cur_pos = function(newpos) {
75             //ibex.log.info("cur.pos trap put "+newpos+", text.length="+text.length);
76             if (newpos > text.length) newpos = text.length;
77             else if (0 > newpos) newpos = 0;
78
79             if (newpos > 0) ibex.thread = function() {
80                 ibex.ui.font.wait(font, fontsize, text);
81                 cur.x = ibex.ui.font.width(font, fontsize, text.substring(0, newpos));
82             } else cur.x = 0;
83
84             if (focused) cur.visible = true;
85         }
86
87         // designed to trap cur.posx --
88         // returns the pixel based position of the cursor
89         var cur_posx_read = function() { return cur.x; }
90
91         // designed to trap cur.posx -- updates cur.pos
92         // based on the pixel value stored in cur.posx
93         //
94         // this two-step put to the x value is designed to normalise
95         // the position of the cursor to being between characters
96         var cur_posx_write = function(newx) {
97             // calculate the cursor position in terms of characters
98             ibex.thread = function() {
99                 ibex.ui.font.wait(font, fontsize, text);
100
101                 // short circuit limits
102                 if (0 >= newx) { cur.pos = 0; return; }
103                 if (newx > width) { cur.pos = text.length; return; }
104
105                 var pos = static.getpos(text, newx, width, font, fontsize);
106
107                 // getpos always rounds down see if we can get closer
108                 // to the mark by skipping over a char
109                 if (text.length > pos) {
110                     var diff = newx - ibex.ui.font.width(font, fontsize, text.substring(0, pos));
111                     if (diff > ibex.ui.font.width(font, fontsize, text.charAt(pos)) / 2) pos++;
112                 }
113     
114                 cur.pos = pos;
115             }
116         }
117
118         // designed to trap sel.pos1 -- resets the selection and
119         // prepares it to start again from the given character position
120         var sel_pos1 = function(v) {
121             sel.text = null;
122         }
123
124         // designed to trap sel.pos2 -- obtains the second selection
125         // position and 
126         var sel_pos2 = function(v) {
127             ibex.log.info("pos2 trapped, new value="+v+", (pos1="+sel.pos1+")");
128             var p1, p2;
129             if (v > sel.pos1) { p1 = sel.pos1; p2 = v; }
130             else              { p2 = sel.pos1; p1 = v; }
131
132             // update selected text
133             sel.text = text.substring(p1, p2);
134             sel.x = ibex.ui.font.width(font, fontsize, text.substring(0, p1));
135         }
136
137         // designed to be applied to thisbox.Move
138         var move = function(v) {
139             cur.posx = mouse.x;
140             ibex.thread = function() { sel.pos2 = cur.pos; }
141         }
142
143         focused ++= function(v) {
144             cur.visible = v;
145         }
146
147         Press1 ++= function(v) {
148             ibex.log.info("Press1 in ibex.lib.text");
149             cur.posx = mouse.x;
150             // TODO: enable selection
151             ibex.thread = function() {
152                 sel.pos1 = cur.pos;
153                 Move ++= move;
154             }
155         }
156
157         // TODO: use surface.Release1
158         Release1 ++= function(v) {
159             ibex.log.info("Release1 in ibex.lib.text");
160             Move --= move;
161         }
162
163         KeyPressed ++= function(v) {
164             ibex.log.info("KeyPressed called with value: "+v);
165
166             // TODO: check for casing and for prefix modifers (eg. C-Y, a-g)
167             if (v.length > 1) switch (v) {
168                 // TODO
169                 case "left":  cur.pos--; return;
170                 case "C-LEFT":
171                 case "c-left": return;
172
173                 case "right": cur.pos++; return;
174                 case "C-RIGHT":
175                 case "c-right": return;
176
177                 case "home":  cur.pos = 0; return;
178                 case "end":   cur.pos = text.length; return;
179                 case "DELETE":
180                 case "back_space":
181                     text = text.substring(0, cur.pos - 1) + text.substring(cur.pos);
182                     cur.pos--;
183                     return;
184                 case "BACK_SPACE":
185                 case "delete":
186                     text = text.substring(0, cur.pos) + text.substring(cur.pos + 1);
187                     return;
188                 case "shift":  ibex.log.warn("not-yet-implemented"); return;
189                 case "alt":    ibex.log.warn("not-yet-implemented"); return;
190                 case "ctrl":   ibex.log.warn("not-yet-implemented"); return;
191                 case "insert": ibex.log.warn("not-yet-implemented"); return;
192                 case "tab":    ibex.log.warn("not-yet-implemented"); return;
193             }
194
195             if (v.length == 1) {
196                 ibex.log.info("KeyPressed resulting in text insertion of "+v);
197                 text = text.substring(0, cur.pos) + v + text.substring(cur.pos);
198                 cur.pos += v.length;
199             }
200         }
201    </ui:box>
202 </ibex>