source: mainline/uspace/lib/clui/tinput.c@ 672b22e

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 672b22e was 36a75a2, checked in by Jiri Svoboda <jiri@…>, 15 years ago

Move text input from Bdsh to a separate library, clui.

  • Property mode set to 100644
File size: 14.4 KB
Line 
1/*
2 * Copyright (c) 2010 Jiri Svoboda
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include <stdio.h>
30#include <stdlib.h>
31#include <str.h>
32#include <io/console.h>
33#include <io/keycode.h>
34#include <io/style.h>
35#include <io/color.h>
36#include <vfs/vfs.h>
37#include <clipboard.h>
38#include <macros.h>
39#include <errno.h>
40#include <assert.h>
41#include <bool.h>
42
43#include "tinput.h"
44
45/** Seek direction */
46typedef enum {
47 seek_backward = -1,
48 seek_forward = 1
49} seek_dir_t;
50
51static void tinput_init(tinput_t *ti);
52static void tinput_insert_string(tinput_t *ti, const char *str);
53static void tinput_sel_get_bounds(tinput_t *ti, int *sa, int *sb);
54static bool tinput_sel_active(tinput_t *ti);
55static void tinput_sel_all(tinput_t *ti);
56static void tinput_sel_delete(tinput_t *ti);
57static void tinput_key_ctrl(tinput_t *ti, console_event_t *ev);
58static void tinput_key_shift(tinput_t *ti, console_event_t *ev);
59static void tinput_key_ctrl_shift(tinput_t *ti, console_event_t *ev);
60static void tinput_key_unmod(tinput_t *ti, console_event_t *ev);
61static void tinput_pre_seek(tinput_t *ti, bool shift_held);
62static void tinput_post_seek(tinput_t *ti, bool shift_held);
63
64/** Create a new text input field. */
65tinput_t *tinput_new(void)
66{
67 tinput_t *ti;
68
69 ti = malloc(sizeof(tinput_t));
70 if (ti == NULL)
71 return NULL;
72
73 tinput_init(ti);
74 return ti;
75}
76
77/** Destroy text input field. */
78void tinput_destroy(tinput_t *ti)
79{
80 free(ti);
81}
82
83static void tinput_display_tail(tinput_t *ti, int start, int pad)
84{
85 static wchar_t dbuf[INPUT_MAX_SIZE + 1];
86 int sa, sb;
87 int i, p;
88
89 tinput_sel_get_bounds(ti, &sa, &sb);
90
91 console_goto(fphone(stdout), (ti->col0 + start) % ti->con_cols,
92 ti->row0 + (ti->col0 + start) / ti->con_cols);
93 console_set_color(fphone(stdout), COLOR_BLACK, COLOR_WHITE, 0);
94
95 p = start;
96 if (p < sa) {
97 memcpy(dbuf, ti->buffer + p, (sa - p) * sizeof(wchar_t));
98 dbuf[sa - p] = '\0';
99 printf("%ls", dbuf);
100 p = sa;
101 }
102
103 if (p < sb) {
104 fflush(stdout);
105 console_set_color(fphone(stdout), COLOR_BLACK, COLOR_RED, 0);
106 memcpy(dbuf, ti->buffer + p,
107 (sb - p) * sizeof(wchar_t));
108 dbuf[sb - p] = '\0';
109 printf("%ls", dbuf);
110 p = sb;
111 }
112
113 fflush(stdout);
114 console_set_color(fphone(stdout), COLOR_BLACK, COLOR_WHITE, 0);
115
116 if (p < ti->nc) {
117 memcpy(dbuf, ti->buffer + p,
118 (ti->nc - p) * sizeof(wchar_t));
119 dbuf[ti->nc - p] = '\0';
120 printf("%ls", dbuf);
121 }
122
123 for (i = 0; i < pad; ++i)
124 putchar(' ');
125 fflush(stdout);
126}
127
128static char *tinput_get_str(tinput_t *ti)
129{
130 return wstr_to_astr(ti->buffer);
131}
132
133static void tinput_position_caret(tinput_t *ti)
134{
135 console_goto(fphone(stdout), (ti->col0 + ti->pos) % ti->con_cols,
136 ti->row0 + (ti->col0 + ti->pos) / ti->con_cols);
137}
138
139/** Update row0 in case the screen could have scrolled. */
140static void tinput_update_origin(tinput_t *ti)
141{
142 int width, rows;
143
144 width = ti->col0 + ti->nc;
145 rows = (width / ti->con_cols) + 1;
146
147 /* Update row0 if the screen scrolled. */
148 if (ti->row0 + rows > ti->con_rows)
149 ti->row0 = ti->con_rows - rows;
150}
151
152static void tinput_insert_char(tinput_t *ti, wchar_t c)
153{
154 int i;
155 int new_width, new_height;
156
157 if (ti->nc == INPUT_MAX_SIZE)
158 return;
159
160 new_width = ti->col0 + ti->nc + 1;
161 if (new_width % ti->con_cols == 0) {
162 /* Advancing to new line. */
163 new_height = (new_width / ti->con_cols) + 1;
164 if (new_height >= ti->con_rows)
165 return; /* Disallow text longer than 1 page for now. */
166 }
167
168 for (i = ti->nc; i > ti->pos; --i)
169 ti->buffer[i] = ti->buffer[i - 1];
170
171 ti->buffer[ti->pos] = c;
172 ti->pos += 1;
173 ti->nc += 1;
174 ti->buffer[ti->nc] = '\0';
175 ti->sel_start = ti->pos;
176
177 tinput_display_tail(ti, ti->pos - 1, 0);
178 tinput_update_origin(ti);
179 tinput_position_caret(ti);
180}
181
182static void tinput_insert_string(tinput_t *ti, const char *str)
183{
184 int i;
185 int new_width, new_height;
186 int ilen;
187 wchar_t c;
188 size_t off;
189
190 ilen = min((ssize_t) str_length(str), INPUT_MAX_SIZE - ti->nc);
191 if (ilen == 0)
192 return;
193
194 new_width = ti->col0 + ti->nc + ilen;
195 new_height = (new_width / ti->con_cols) + 1;
196 if (new_height >= ti->con_rows)
197 return; /* Disallow text longer than 1 page for now. */
198
199 for (i = ti->nc - 1; i >= ti->pos; --i)
200 ti->buffer[i + ilen] = ti->buffer[i];
201
202 off = 0; i = 0;
203 while (i < ilen) {
204 c = str_decode(str, &off, STR_NO_LIMIT);
205 if (c == '\0')
206 break;
207
208 /* Filter out non-printable chars. */
209 if (c < 32)
210 c = 32;
211
212 ti->buffer[ti->pos + i] = c;
213 ++i;
214 }
215
216 ti->pos += ilen;
217 ti->nc += ilen;
218 ti->buffer[ti->nc] = '\0';
219 ti->sel_start = ti->pos;
220
221 tinput_display_tail(ti, ti->pos - ilen, 0);
222 tinput_update_origin(ti);
223 tinput_position_caret(ti);
224}
225
226static void tinput_backspace(tinput_t *ti)
227{
228 int i;
229
230 if (tinput_sel_active(ti)) {
231 tinput_sel_delete(ti);
232 return;
233 }
234
235 if (ti->pos == 0)
236 return;
237
238 for (i = ti->pos; i < ti->nc; ++i)
239 ti->buffer[i - 1] = ti->buffer[i];
240 ti->pos -= 1;
241 ti->nc -= 1;
242 ti->buffer[ti->nc] = '\0';
243 ti->sel_start = ti->pos;
244
245 tinput_display_tail(ti, ti->pos, 1);
246 tinput_position_caret(ti);
247}
248
249static void tinput_delete(tinput_t *ti)
250{
251 if (tinput_sel_active(ti)) {
252 tinput_sel_delete(ti);
253 return;
254 }
255
256 if (ti->pos == ti->nc)
257 return;
258
259 ti->pos += 1;
260 ti->sel_start = ti->pos;
261
262 tinput_backspace(ti);
263}
264
265static void tinput_seek_cell(tinput_t *ti, seek_dir_t dir, bool shift_held)
266{
267 tinput_pre_seek(ti, shift_held);
268
269 if (dir == seek_forward) {
270 if (ti->pos < ti->nc)
271 ti->pos += 1;
272 } else {
273 if (ti->pos > 0)
274 ti->pos -= 1;
275 }
276
277 tinput_post_seek(ti, shift_held);
278}
279
280static void tinput_seek_word(tinput_t *ti, seek_dir_t dir, bool shift_held)
281{
282 tinput_pre_seek(ti, shift_held);
283
284 if (dir == seek_forward) {
285 if (ti->pos == ti->nc)
286 return;
287
288 while (1) {
289 ti->pos += 1;
290
291 if (ti->pos == ti->nc)
292 break;
293
294 if (ti->buffer[ti->pos - 1] == ' ' &&
295 ti->buffer[ti->pos] != ' ')
296 break;
297 }
298 } else {
299 if (ti->pos == 0)
300 return;
301
302 while (1) {
303 ti->pos -= 1;
304
305 if (ti->pos == 0)
306 break;
307
308 if (ti->buffer[ti->pos - 1] == ' ' &&
309 ti->buffer[ti->pos] != ' ')
310 break;
311 }
312
313 }
314
315 tinput_post_seek(ti, shift_held);
316}
317
318static void tinput_seek_vertical(tinput_t *ti, seek_dir_t dir, bool shift_held)
319{
320 tinput_pre_seek(ti, shift_held);
321
322 if (dir == seek_forward) {
323 if (ti->pos + ti->con_cols <= ti->nc)
324 ti->pos = ti->pos + ti->con_cols;
325 } else {
326 if (ti->pos - ti->con_cols >= 0)
327 ti->pos = ti->pos - ti->con_cols;
328 }
329
330 tinput_post_seek(ti, shift_held);
331}
332
333static void tinput_seek_max(tinput_t *ti, seek_dir_t dir, bool shift_held)
334{
335 tinput_pre_seek(ti, shift_held);
336
337 if (dir == seek_backward)
338 ti->pos = 0;
339 else
340 ti->pos = ti->nc;
341
342 tinput_post_seek(ti, shift_held);
343}
344
345static void tinput_pre_seek(tinput_t *ti, bool shift_held)
346{
347 if (tinput_sel_active(ti) && !shift_held) {
348 /* Unselect and redraw. */
349 ti->sel_start = ti->pos;
350 tinput_display_tail(ti, 0, 0);
351 tinput_position_caret(ti);
352 }
353}
354
355static void tinput_post_seek(tinput_t *ti, bool shift_held)
356{
357 if (shift_held) {
358 /* Selecting text. Need redraw. */
359 tinput_display_tail(ti, 0, 0);
360 } else {
361 /* Shift not held. Keep selection empty. */
362 ti->sel_start = ti->pos;
363 }
364 tinput_position_caret(ti);
365}
366
367static void tinput_history_insert(tinput_t *ti, char *str)
368{
369 int i;
370
371 if (ti->hnum < HISTORY_LEN) {
372 ti->hnum += 1;
373 } else {
374 if (ti->history[HISTORY_LEN] != NULL)
375 free(ti->history[HISTORY_LEN]);
376 }
377
378 for (i = ti->hnum; i > 1; --i)
379 ti->history[i] = ti->history[i - 1];
380
381 ti->history[1] = str_dup(str);
382
383 if (ti->history[0] != NULL) {
384 free(ti->history[0]);
385 ti->history[0] = NULL;
386 }
387}
388
389static void tinput_set_str(tinput_t *ti, char *str)
390{
391 str_to_wstr(ti->buffer, INPUT_MAX_SIZE, str);
392 ti->nc = wstr_length(ti->buffer);
393 ti->pos = ti->nc;
394 ti->sel_start = ti->pos;
395}
396
397static void tinput_sel_get_bounds(tinput_t *ti, int *sa, int *sb)
398{
399 if (ti->sel_start < ti->pos) {
400 *sa = ti->sel_start;
401 *sb = ti->pos;
402 } else {
403 *sa = ti->pos;
404 *sb = ti->sel_start;
405 }
406}
407
408static bool tinput_sel_active(tinput_t *ti)
409{
410 return ti->sel_start != ti->pos;
411}
412
413static void tinput_sel_all(tinput_t *ti)
414{
415 ti->sel_start = 0;
416 ti->pos = ti->nc;
417 tinput_display_tail(ti, 0, 0);
418 tinput_position_caret(ti);
419}
420
421static void tinput_sel_delete(tinput_t *ti)
422{
423 int sa, sb;
424
425 tinput_sel_get_bounds(ti, &sa, &sb);
426 if (sa == sb)
427 return;
428
429 memmove(ti->buffer + sa, ti->buffer + sb,
430 (ti->nc - sb) * sizeof(wchar_t));
431 ti->pos = ti->sel_start = sa;
432 ti->nc -= (sb - sa);
433 ti->buffer[ti->nc] = '\0';
434
435 tinput_display_tail(ti, sa, sb - sa);
436 tinput_position_caret(ti);
437}
438
439static void tinput_sel_copy_to_cb(tinput_t *ti)
440{
441 int sa, sb;
442 char *str;
443
444 tinput_sel_get_bounds(ti, &sa, &sb);
445
446 if (sb < ti->nc) {
447 wchar_t tmp_c = ti->buffer[sb];
448 ti->buffer[sb] = '\0';
449 str = wstr_to_astr(ti->buffer + sa);
450 ti->buffer[sb] = tmp_c;
451 } else
452 str = wstr_to_astr(ti->buffer + sa);
453
454 if (str == NULL)
455 goto error;
456
457 if (clipboard_put_str(str) != EOK)
458 goto error;
459
460 free(str);
461 return;
462error:
463 return;
464 /* TODO: Give the user some warning. */
465}
466
467static void tinput_paste_from_cb(tinput_t *ti)
468{
469 char *str;
470 int rc;
471
472 rc = clipboard_get_str(&str);
473 if (rc != EOK || str == NULL)
474 return; /* TODO: Give the user some warning. */
475
476 tinput_insert_string(ti, str);
477 free(str);
478}
479
480static void tinput_history_seek(tinput_t *ti, int offs)
481{
482 int pad;
483
484 if (ti->hpos + offs < 0 || ti->hpos + offs > ti->hnum)
485 return;
486
487 if (ti->history[ti->hpos] != NULL) {
488 free(ti->history[ti->hpos]);
489 ti->history[ti->hpos] = NULL;
490 }
491
492 ti->history[ti->hpos] = tinput_get_str(ti);
493 ti->hpos += offs;
494
495 pad = ti->nc - str_length(ti->history[ti->hpos]);
496 if (pad < 0) pad = 0;
497
498 tinput_set_str(ti, ti->history[ti->hpos]);
499 tinput_display_tail(ti, 0, pad);
500 tinput_update_origin(ti);
501 tinput_position_caret(ti);
502}
503
504/** Initialize text input field.
505 *
506 * Must be called before using the field. It clears the history.
507 */
508static void tinput_init(tinput_t *ti)
509{
510 ti->hnum = 0;
511 ti->hpos = 0;
512 ti->history[0] = NULL;
513}
514
515/** Read in one line of input. */
516char *tinput_read(tinput_t *ti)
517{
518 console_event_t ev;
519 char *str;
520
521 fflush(stdout);
522
523 if (console_get_size(fphone(stdin), &ti->con_cols, &ti->con_rows) != EOK)
524 return NULL;
525 if (console_get_pos(fphone(stdin), &ti->col0, &ti->row0) != EOK)
526 return NULL;
527
528 ti->pos = ti->sel_start = 0;
529 ti->nc = 0;
530 ti->buffer[0] = '\0';
531 ti->done = false;
532
533 while (!ti->done) {
534 fflush(stdout);
535 if (!console_get_event(fphone(stdin), &ev))
536 return NULL;
537
538 if (ev.type != KEY_PRESS)
539 continue;
540
541 if ((ev.mods & KM_CTRL) != 0 &&
542 (ev.mods & (KM_ALT | KM_SHIFT)) == 0) {
543 tinput_key_ctrl(ti, &ev);
544 }
545
546 if ((ev.mods & KM_SHIFT) != 0 &&
547 (ev.mods & (KM_CTRL | KM_ALT)) == 0) {
548 tinput_key_shift(ti, &ev);
549 }
550
551 if ((ev.mods & KM_CTRL) != 0 &&
552 (ev.mods & KM_SHIFT) != 0 &&
553 (ev.mods & KM_ALT) == 0) {
554 tinput_key_ctrl_shift(ti, &ev);
555 }
556
557 if ((ev.mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
558 tinput_key_unmod(ti, &ev);
559 }
560
561 if (ev.c >= ' ') {
562 tinput_sel_delete(ti);
563 tinput_insert_char(ti, ev.c);
564 }
565 }
566
567 ti->pos = ti->nc;
568 tinput_position_caret(ti);
569 putchar('\n');
570
571 str = tinput_get_str(ti);
572 if (str_cmp(str, "") != 0)
573 tinput_history_insert(ti, str);
574
575 ti->hpos = 0;
576
577 return str;
578}
579
580static void tinput_key_ctrl(tinput_t *ti, console_event_t *ev)
581{
582 switch (ev->key) {
583 case KC_LEFT:
584 tinput_seek_word(ti, seek_backward, false);
585 break;
586 case KC_RIGHT:
587 tinput_seek_word(ti, seek_forward, false);
588 break;
589 case KC_UP:
590 tinput_seek_vertical(ti, seek_backward, false);
591 break;
592 case KC_DOWN:
593 tinput_seek_vertical(ti, seek_forward, false);
594 break;
595 case KC_X:
596 tinput_sel_copy_to_cb(ti);
597 tinput_sel_delete(ti);
598 break;
599 case KC_C:
600 tinput_sel_copy_to_cb(ti);
601 break;
602 case KC_V:
603 tinput_sel_delete(ti);
604 tinput_paste_from_cb(ti);
605 break;
606 case KC_A:
607 tinput_sel_all(ti);
608 break;
609 default:
610 break;
611 }
612}
613
614static void tinput_key_ctrl_shift(tinput_t *ti, console_event_t *ev)
615{
616 switch (ev->key) {
617 case KC_LEFT:
618 tinput_seek_word(ti, seek_backward, true);
619 break;
620 case KC_RIGHT:
621 tinput_seek_word(ti, seek_forward, true);
622 break;
623 case KC_UP:
624 tinput_seek_vertical(ti, seek_backward, true);
625 break;
626 case KC_DOWN:
627 tinput_seek_vertical(ti, seek_forward, true);
628 break;
629 default:
630 break;
631 }
632}
633
634static void tinput_key_shift(tinput_t *ti, console_event_t *ev)
635{
636 switch (ev->key) {
637 case KC_LEFT:
638 tinput_seek_cell(ti, seek_backward, true);
639 break;
640 case KC_RIGHT:
641 tinput_seek_cell(ti, seek_forward, true);
642 break;
643 case KC_UP:
644 tinput_seek_vertical(ti, seek_backward, true);
645 break;
646 case KC_DOWN:
647 tinput_seek_vertical(ti, seek_forward, true);
648 break;
649 case KC_HOME:
650 tinput_seek_max(ti, seek_backward, true);
651 break;
652 case KC_END:
653 tinput_seek_max(ti, seek_forward, true);
654 break;
655 default:
656 break;
657 }
658}
659
660static void tinput_key_unmod(tinput_t *ti, console_event_t *ev)
661{
662 switch (ev->key) {
663 case KC_ENTER:
664 case KC_NENTER:
665 ti->done = true;
666 break;
667 case KC_BACKSPACE:
668 tinput_backspace(ti);
669 break;
670 case KC_DELETE:
671 tinput_delete(ti);
672 break;
673 case KC_LEFT:
674 tinput_seek_cell(ti, seek_backward, false);
675 break;
676 case KC_RIGHT:
677 tinput_seek_cell(ti, seek_forward, false);
678 break;
679 case KC_HOME:
680 tinput_seek_max(ti, seek_backward, false);
681 break;
682 case KC_END:
683 tinput_seek_max(ti, seek_forward, false);
684 break;
685 case KC_UP:
686 tinput_history_seek(ti, +1);
687 break;
688 case KC_DOWN:
689 tinput_history_seek(ti, -1);
690 break;
691 default:
692 break;
693 }
694}
Note: See TracBrowser for help on using the repository browser.