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