1
2 /******************************************************************************
3 * MODULE : input_widget.cpp
4 * DESCRIPTION: Input of data by the user in textual form
5 * COPYRIGHT : (C) 1999 Joris van der Hoeven
6 *******************************************************************************
7 * This software falls under the GNU general public license version 3 or later.
8 * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE
9 * in the root directory or <http://www.gnu.org/licenses/gpl-3.0.html>.
10 ******************************************************************************/
11
12 #include "analyze.hpp"
13 #include "font.hpp"
14 #include "file.hpp"
15 #include "window.hpp"
16 #include "Widkit/attribute_widget.hpp"
17 #include "Widkit/layout.hpp"
18 #include "scheme.hpp"
19 #include "message.hpp"
20
21 #ifdef OS_WIN32
22 #define URL_CONCATER '\\'
23 #else
24 #define URL_CONCATER '/'
25 #endif
26
27 /******************************************************************************
28 * Input widgets
29 ******************************************************************************/
30
31 class input_widget_rep: public attribute_widget_rep {
32 string s; // the string being entered
33 string draw_s; // the string being displayed
34 SI text_h; // text height
35 string type; // expected type of string
36 string name; // optional name of the input field
37 string serial; // optional serial number of the input field
38 array<string> def; // default possible input values
39 command call_back; // routine called on <return> or <escape>
40 int style; // style of widget
41 bool greyed; // greyed input
42 string width; // width of input field
43 bool persistent; // don't complete after loss of focus
44 bool ok; // input not canceled
45 bool done; // call back has been called
46 int def_cur; // current choice between default possible values
47 SI dw, dh; // border width and height
48 int pos; // cursor position
49 SI scroll; // how much scrolled to the left
50 bool got_focus; // got keyboard focus
51 bool hilit; // hilit on keyboard focus
52 array<string> tabs; // tab completions
53 int tab_nr; // currently visible tab-completion
54 int tab_pos; // cursor position where tab was pressed
55
56 public:
57 input_widget_rep (command call_back, int style, string width, bool persist);
58 operator tree ();
59 void update_draw_s ();
60 void commit ();
61 void cancel ();
62 void set_type (string type);
63 bool continuous ();
64
65 void handle_get_size (get_size_event ev);
66 void handle_repaint (repaint_event ev);
67 void handle_keypress (keypress_event ev);
68 void handle_mouse (mouse_event ev);
69 void handle_keyboard_focus (keyboard_focus_event ev);
70 void handle_keyboard_focus_on (keyboard_focus_on_event ev);
71
72 void handle_set_string (set_string_event ev);
73 void handle_get_string (get_string_event ev);
74 void handle_get_coord2 (get_coord2_event ev);
75 void handle_set_coord2 (set_coord2_event ev);
76 };
77
78 /******************************************************************************
79 * Routines for input_widgets
80 ******************************************************************************/
81
82 #define SHRINK 3
83
input_widget_rep(command cb2,int st2,string w2,bool p2)84 input_widget_rep::input_widget_rep (command cb2, int st2, string w2, bool p2):
85 attribute_widget_rep (south_west),
86 s (""), draw_s (""),
87 type ("default"), name ("default"), serial ("default"), def (),
88 call_back (cb2), style (st2),
89 greyed ((style & WIDGET_STYLE_INERT) != 0),
90 width (w2), persistent (p2),
91 ok (true), done (false), def_cur (0),
92 dw (4*PIXEL), dh (2*PIXEL), pos (N(s)), scroll (0),
93 got_focus (false), hilit (false)
94 {
95 if ((style & WIDGET_STYLE_MINI) != 0) dh= 1.5 * PIXEL;
96 dw *= SHRINK;
97 dh *= SHRINK;
98 font fn= get_default_styled_font (style);
99 text_h = (fn->y2- fn->y1+ 2*dh+ (SHRINK-1))/SHRINK;
100 }
101
operator tree()102 input_widget_rep::operator tree () {
103 return tree (TUPLE, "input", s);
104 }
105
106 void
update_draw_s()107 input_widget_rep::update_draw_s () {
108 draw_s= s;
109 if (type == "password") {
110 draw_s= copy (s);
111 for (int i=0; i<N(s); i++)
112 draw_s[i]= '*';
113 }
114 }
115
116 void
commit()117 input_widget_rep::commit () {
118 if (continuous ()) return;
119 ok= true;
120 done= true;
121 call_back (list_object (object (s)));
122 }
123
124 void
cancel()125 input_widget_rep::cancel () {
126 ok= false;
127 done= true;
128 call_back (list_object (object (false)));
129 }
130
131 void
set_type(string t)132 input_widget_rep::set_type (string t) {
133 int i= search_forwards (":", 0, t);
134 if (i >= 0) {
135 type= t (i+1, N(t));
136 name= t (0, i);
137 int j= search_forwards ("#", 0, name);
138 if (j >= 0) {
139 serial= name (j+1, N(name));
140 name = name (0, j);
141 }
142 }
143 else type= t;
144 }
145
146 bool
continuous()147 input_widget_rep::continuous () {
148 return
149 starts (type, "search") ||
150 starts (type, "replace-") ||
151 starts (serial, "form-");
152 }
153
154 void
handle_get_size(get_size_event ev)155 input_widget_rep::handle_get_size (get_size_event ev) {
156 SI ww = decode_length (width, this, style);
157 SI dww= (SI) ((2*dw) / SHRINK);
158 if (ends (width, "w") && is_double (width (0, N(width) - 1))) {
159 font fn= get_default_styled_font (style);
160 if (ev->mode == -1) ev->w= 0;
161 else if (ev->mode == 0);
162 else if (ev->mode == 1) ev->w= ww;
163 ev->w= max (ev->w, (4*fn->wquad) / SHRINK + dww);
164 }
165 else if (ends (width, "em") && is_double (width (0, N(width) - 2)))
166 ev->w= ww + dww;
167 else if (ends (width, "px") && is_double (width (0, N(width) - 2)))
168 ev->w= ww + dww;
169 else if (ev->mode == 1)
170 ev->w= ww;
171 ev->h= text_h;
172 abs_round (ev->w, ev->h);
173 }
174
175 void
handle_repaint(repaint_event ev)176 input_widget_rep::handle_repaint (repaint_event ev) { (void) ev;
177 renderer ren= ev->win;
178 update_draw_s ();
179 SI ecart= max (0, (h - (2*dh / SHRINK) - text_h) >> 1);
180
181 metric ex;
182 font fn= get_default_styled_font (style);
183 fn->var_get_extents (draw_s, ex);
184 SI left= ex->x1, bottom= fn->y1, right= ex->x2;
185 fn->var_get_extents (draw_s (0, pos), ex);
186 SI current= ex->x2- ex->x1;
187
188 SI text_width= right-left;
189 SI width= w*SHRINK- 2*dw, height= h*SHRINK;
190 SI marge= width>>2;
191 if ((current-scroll) > (width-marge)) scroll= current+ marge- width;
192 if ((current-scroll) < marge) scroll= current- marge;
193 if (scroll > (text_width- width)) scroll= text_width- width;
194 if (scroll < 0) scroll= 0;
195 left += scroll;
196 current -= scroll;
197
198 layout_default (ren, 0, 0, w, h);
199 if (true) {
200 SI yy= ((ecart + PIXEL/2) / PIXEL) * PIXEL;
201 SI hh= ((h - 2*ecart + PIXEL/2) / PIXEL) * PIXEL;
202 if (yy + hh + 2*PIXEL <= h) hh += 2 * PIXEL;
203 else if (yy + hh + PIXEL <= h) hh += PIXEL;
204 if (greyed) layout_default (ren, 0, yy, w, hh);
205 else layout_pastel (ren, 0, yy, w, hh);
206 layout_lower (ren, 0, yy, w, hh);
207 }
208 else if (got_focus && hilit) {
209 layout_dark (ren, 0, 0, w, h);
210 layout_lower (ren, 0, 0, w, h);
211 }
212
213 ren->set_shrinking_factor (SHRINK);
214 SI pixel= SHRINK*PIXEL;
215 ren->set_pencil (pencil (black, pixel));
216 if (greyed) ren->set_pencil (pencil (dark_grey, pixel));
217 ecart *= SHRINK;
218 fn->var_draw (ren, draw_s, dw - left, dh - bottom + ecart);
219 if (got_focus) {
220 ren->set_pencil (pencil (red, pixel));
221 ren->line (current + dw, dh + ecart,
222 current + dw, height - pixel - dh - ecart);
223 ren->line (current + dw - pixel, dh + ecart,
224 current + dw + pixel, dh + ecart);
225 ren->line (current + dw - pixel, height - pixel - dh - ecart,
226 current + dw + pixel, height - pixel - dh - ecart);
227 }
228 ren->set_shrinking_factor (1);
229 }
230
231 void
handle_keypress(keypress_event ev)232 input_widget_rep::handle_keypress (keypress_event ev) {
233 if (greyed) return;
234 string key= ev->key;
235 while ((N(key) >= 5) && (key(0,3) == "Mod") && (key[4] == '-') &&
236 (key[3] >= '1') && (key[3] <= '5')) key= key (5, N(key));
237 if (key == "space") key= " ";
238 if (key == "<") key= "<less>";
239 if (key == ">") key= "<gtr>";
240
241 /* tab-completion */
242 if (continuous ());
243 else if ((key == "tab" || key == "S-tab") && N(tabs) != 0) {
244 int d = (key == "tab"? 1: N(tabs)-1);
245 tab_nr= (tab_nr + d) % N(tabs);
246 s = s (0, tab_pos) * tabs[tab_nr];
247 pos = N(s);
248 this << emit_invalidate_all ();
249 return;
250 }
251 else if (key == "tab" || key == "S-tab") {
252 if (pos != N(s)) return;
253 tabs= copy (def);
254 if (ends (type, "file") || type == "directory") {
255 url search= url_here ();
256 url dir= (ends (s, string (URL_CONCATER))? url (s): head (url (s)));
257 if (type == "smart-file") search= url ("$TEXMACS_FILE_PATH");
258 if (is_rooted (dir)) search= url_here ();
259 if (is_none (dir)) dir= url_here ();
260 tabs= file_completions (search, dir);
261 }
262 tabs= strip_completions (tabs, s);
263 tabs= close_completions (tabs);
264 if (N (tabs) == 0);
265 else if (N (tabs) == 1) {
266 s = s * tabs[0];
267 pos = N(s);
268 tabs= array<string> (0);
269 }
270 else {
271 tab_nr = 0;
272 tab_pos= N(s);
273 s = s * tabs[0];
274 pos = N(s);
275 beep ();
276 }
277 this << emit_invalidate_all ();
278 return;
279 }
280 else {
281 tabs = array<string> (0);
282 tab_nr = 0;
283 tab_pos= 0;
284 }
285
286 /* other actions */
287 if (continuous () &&
288 (key == "return" ||
289 key == "S-return" ||
290 key == "home" ||
291 key == "end" ||
292 key == "up" ||
293 key == "down" ||
294 key == "pageup" ||
295 key == "pagedown" ||
296 key == "tab" ||
297 key == "S-tab" ||
298 key == "escape"));
299 else if (key == "return") commit ();
300 else if ((key == "escape") || (key == "C-c") ||
301 (key == "C-g")) cancel ();
302 else if ((key == "left") || (key == "C-b")) {
303 if (pos>0) tm_char_backwards (s, pos); }
304 else if ((key == "right") || (key == "C-f")) {
305 if (pos<N(s)) tm_char_forwards (s, pos); }
306 else if ((key == "home") || (key == "C-a")) pos=0;
307 else if ((key == "end") || (key == "C-e")) pos=N(s);
308 else if ((key == "up") || (key == "C-p")) {
309 if (N(def) > 0) {
310 def_cur= (def_cur+1) % N(def);
311 s = copy (def[def_cur]);
312 pos = N(s);
313 }
314 }
315 else if ((key == "down") || (key == "C-n")) {
316 if (N(def) > 0) {
317 def_cur= (def_cur+N(def)-1) % N(def);
318 s = copy (def[def_cur]);
319 pos = N(s);
320 }
321 }
322 else if (key == "C-k") s= s (0, pos);
323 else if ((key == "C-d") || (key == "delete")) {
324 if ((pos<N(s)) && (N(s)>0)) {
325 int end= pos;
326 tm_char_forwards (s, end);
327 s= s (0, pos) * s (end, N(s));
328 }
329 }
330 else if (key == "backspace" || key == "S-backspace") {
331 if (pos>0) {
332 int end= pos;
333 tm_char_backwards (s, pos);
334 s= s (0, pos) * s (end, N(s));
335 }
336 }
337 else if (key == "C-backspace") {
338 s= "";
339 pos= 0;
340 }
341 else {
342 if (starts (key, "<#"));
343 else if (key == "<less>" || key == "<gtr>");
344 else {
345 if (N(key)!=1) return;
346 int i (key[0]);
347 if ((i>=0) && (i<32)) return;
348 }
349 s= s (0, pos) * key * s(pos, N(s));
350 pos += N(key);
351 }
352 this << emit_invalidate_all ();
353 if (continuous ())
354 call_back (list_object (list_object (object (s), object (key))));
355 }
356
357 void
handle_mouse(mouse_event ev)358 input_widget_rep::handle_mouse (mouse_event ev) {
359 if (greyed) return;
360 update_draw_s ();
361
362 string type= ev->type;
363 SI x = ev->x;
364 font fn = get_default_styled_font (style);
365
366 if (type == "press-left") {
367 if (N(s)>0) {
368 metric ex;
369 SI old= 0;
370 pos=0; tm_char_forwards (s, pos);
371 for (; pos<=N(s); tm_char_forwards (s, pos)) {
372 fn->var_get_extents (draw_s (0, pos), ex);
373 if (((old+ ex->x2+ dw- ex->x1) >> 1) > (x*SHRINK+ scroll)) {
374 tm_char_backwards (s, pos);
375 break;
376 }
377 old= ex->x2+ dw- ex->x1;
378 if (pos >= N(s)) break;
379 }
380 }
381 win->set_keyboard_focus (this);
382 this << emit_invalidate_all ();
383 }
384
385 if (type == "press-middle") {
386 tree t; string sel;
387 (void) get_selection ("primary", t, sel, "verbatim");
388 if (is_tuple (t, "extern", 1)) {
389 string ins= as_string (t[1]);
390 ins= tm_encode (ins);
391 s= s (0, pos) * ins * s(pos, N(s));
392 pos += N(ins);
393 this << emit_invalidate_all ();
394 }
395 else if (is_tuple (t, "texmacs", 3));
396 }
397 }
398
399 void
handle_keyboard_focus(keyboard_focus_event ev)400 input_widget_rep::handle_keyboard_focus (keyboard_focus_event ev) {
401 if (got_focus && !ev->flag && !done && !persistent) {
402 //if (type == "string") commit (); else cancel ();
403 cancel ();
404 }
405 got_focus= ev->flag;
406 if (attached ())
407 this << emit_invalidate_all ();
408 }
409
410 void
handle_keyboard_focus_on(keyboard_focus_on_event ev)411 input_widget_rep::handle_keyboard_focus_on (keyboard_focus_on_event ev) {
412 if (ev->field == serial || ev->field == name || ev->field == type) {
413 ev->done= true;
414 send_keyboard_focus (abstract (this));
415 }
416 }
417
418 void
handle_set_string(set_string_event ev)419 input_widget_rep::handle_set_string (set_string_event ev) {
420 if (ev->which == "input") {
421 s= copy (ev->s);
422 pos= N(s);
423 ok= (ev->s != "#f");
424 if (attached ()) this << emit_invalidate_all ();
425 }
426 else if (ev->which == "type") set_type (copy (ev->s));
427 else if (ev->which == "default") def << copy (ev->s);
428 else attribute_widget_rep::handle_set_string (ev);
429 }
430
431 void
handle_get_string(get_string_event ev)432 input_widget_rep::handle_get_string (get_string_event ev) {
433 if (ev->which == "input") {
434 if (ok) ev->s= scm_quote (s);
435 else ev->s= "#f";
436 }
437 else attribute_widget_rep::handle_get_string (ev);
438 }
439
440 void
handle_get_coord2(get_coord2_event ev)441 input_widget_rep::handle_get_coord2 (get_coord2_event ev) {
442 if (ev->which != "extra width") attribute_widget_rep::handle_get_coord2 (ev);
443 else { ev->c1= 0; ev->c2= 0; }
444 }
445
446 void
handle_set_coord2(set_coord2_event ev)447 input_widget_rep::handle_set_coord2 (set_coord2_event ev) {
448 if (ev->which != "extra width") attribute_widget_rep::handle_set_coord2 (ev);
449 }
450
451 /******************************************************************************
452 * Interface
453 ******************************************************************************/
454
455 event
set_input_string(string s)456 set_input_string (string s) {
457 return set_string ("input", s);
458 }
459
460 event
get_input_string(string & s)461 get_input_string (string& s) {
462 return get_string ("input", s);
463 }
464
465 wk_widget
input_text_wk_widget(command call_back,int style,string w,bool persistent)466 input_text_wk_widget (command call_back,
467 int style, string w, bool persistent)
468 {
469 (void) style;
470 return tm_new<input_widget_rep> (call_back, style, w, persistent);
471 }
472
473 wk_widget
input_text_wk_widget(command cb,string type,array<string> def,int style,string w,bool persistent)474 input_text_wk_widget (command cb, string type, array<string> def,
475 int style, string w, bool persistent)
476 {
477 (void) style;
478 int i, n= N(def);
479 wk_widget inp= input_text_wk_widget (cb, style, w, persistent);
480 inp << set_string ("type", type);
481 if (n>0) inp << set_string ("input", def[0]);
482 for (i=0; i<n; i++) inp << set_string ("default", def[i]);
483 return inp;
484 }
485