source: mainline/uspace/lib/clui/tinput.c@ 554debd

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 554debd was 9f1362d4, checked in by Martin Decky <martin@…>, 15 years ago

console output improvements

  • define new generic styles (STYLE_INVERTED for inverted print and STYLE_SELECTION for selections), use them primarily instead of specifying colors or RGBs
  • use console_set_style(fphone(stdout), STYLE_NORMAL) as the correct mean for reseting console settings (instead of specifying conrete hardcoded colors)
  • rename console_goto() to console_set_pos() (consistency with console_get_pos())
  • use semantically correct unsigned types for console sizes and cursor positions (instead of signed types)
  • use unsigned types for sizes and positions in libclui
  • top: nicer screen redrawing (do not use console_clear() which causes flickering, but repaint the screen properly — not entirely finished yet)
  • initialize mouse pointer coordinates (so the mouse cursor does not behave erratic after boot, unfortunatelly this does not solve ticket #223)
  • Property mode set to 100644
File size: 14.9 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#include <tinput.h>
43
44/** Seek direction */
45typedef enum {
46 seek_backward = -1,
47 seek_forward = 1
48} seek_dir_t;
49
50static void tinput_init(tinput_t *);
51static void tinput_insert_string(tinput_t *, const char *);
52static void tinput_sel_get_bounds(tinput_t *, size_t *, size_t *);
53static bool tinput_sel_active(tinput_t *);
54static void tinput_sel_all(tinput_t *);
55static void tinput_sel_delete(tinput_t *);
56static void tinput_key_ctrl(tinput_t *, console_event_t *);
57static void tinput_key_shift(tinput_t *, console_event_t *);
58static void tinput_key_ctrl_shift(tinput_t *, console_event_t *);
59static void tinput_key_unmod(tinput_t *, console_event_t *);
60static void tinput_pre_seek(tinput_t *, bool);
61static void tinput_post_seek(tinput_t *, bool);
62
63/** Create a new text input field. */
64tinput_t *tinput_new(void)
65{
66 tinput_t *ti;
67
68 ti = malloc(sizeof(tinput_t));
69 if (ti == NULL)
70 return NULL;
71
72 tinput_init(ti);
73 return ti;
74}
75
76/** Destroy text input field. */
77void tinput_destroy(tinput_t *ti)
78{
79 free(ti);
80}
81
82static void tinput_display_tail(tinput_t *ti, size_t start, size_t pad)
83{
84 wchar_t dbuf[INPUT_MAX_SIZE + 1];
85
86 size_t sa;
87 size_t sb;
88 tinput_sel_get_bounds(ti, &sa, &sb);
89
90 console_set_pos(fphone(stdout), (ti->col0 + start) % ti->con_cols,
91 ti->row0 + (ti->col0 + start) / ti->con_cols);
92 console_set_style(fphone(stdout), STYLE_NORMAL);
93
94 size_t p = start;
95 if (p < sa) {
96 memcpy(dbuf, ti->buffer + p, (sa - p) * sizeof(wchar_t));
97 dbuf[sa - p] = '\0';
98 printf("%ls", dbuf);
99 p = sa;
100 }
101
102 if (p < sb) {
103 fflush(stdout);
104 console_set_style(fphone(stdout), STYLE_SELECTED);
105 memcpy(dbuf, ti->buffer + p,
106 (sb - p) * sizeof(wchar_t));
107 dbuf[sb - p] = '\0';
108 printf("%ls", dbuf);
109 p = sb;
110 }
111
112 fflush(stdout);
113 console_set_style(fphone(stdout), STYLE_NORMAL);
114
115 if (p < ti->nc) {
116 memcpy(dbuf, ti->buffer + p,
117 (ti->nc - p) * sizeof(wchar_t));
118 dbuf[ti->nc - p] = '\0';
119 printf("%ls", dbuf);
120 }
121
122 for (p = 0; p < pad; p++)
123 putchar(' ');
124
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_set_pos(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 ipcarg_t width = ti->col0 + ti->nc;
143 ipcarg_t rows = (width / ti->con_cols) + 1;
144
145 /* Update row0 if the screen scrolled. */
146 if (ti->row0 + rows > ti->con_rows)
147 ti->row0 = ti->con_rows - rows;
148}
149
150static void tinput_insert_char(tinput_t *ti, wchar_t c)
151{
152 if (ti->nc == INPUT_MAX_SIZE)
153 return;
154
155 ipcarg_t new_width = ti->col0 + ti->nc + 1;
156 if (new_width % ti->con_cols == 0) {
157 /* Advancing to new line. */
158 ipcarg_t new_height = (new_width / ti->con_cols) + 1;
159 if (new_height >= ti->con_rows) {
160 /* Disallow text longer than 1 page for now. */
161 return;
162 }
163 }
164
165 size_t i;
166 for (i = ti->nc; i > ti->pos; i--)
167 ti->buffer[i] = ti->buffer[i - 1];
168
169 ti->buffer[ti->pos] = c;
170 ti->pos += 1;
171 ti->nc += 1;
172 ti->buffer[ti->nc] = '\0';
173 ti->sel_start = ti->pos;
174
175 tinput_display_tail(ti, ti->pos - 1, 0);
176 tinput_update_origin(ti);
177 tinput_position_caret(ti);
178}
179
180static void tinput_insert_string(tinput_t *ti, const char *str)
181{
182 size_t ilen = min(str_length(str), INPUT_MAX_SIZE - ti->nc);
183 if (ilen == 0)
184 return;
185
186 ipcarg_t new_width = ti->col0 + ti->nc + ilen;
187 ipcarg_t new_height = (new_width / ti->con_cols) + 1;
188 if (new_height >= ti->con_rows) {
189 /* Disallow text longer than 1 page for now. */
190 return;
191 }
192
193 if (ti->nc > 0) {
194 size_t i;
195 for (i = ti->nc; i > ti->pos; i--)
196 ti->buffer[i + ilen - 1] = ti->buffer[i - 1];
197 }
198
199 size_t off = 0;
200 size_t i = 0;
201 while (i < ilen) {
202 wchar_t c = str_decode(str, &off, STR_NO_LIMIT);
203 if (c == '\0')
204 break;
205
206 /* Filter out non-printable chars. */
207 if (c < 32)
208 c = 32;
209
210 ti->buffer[ti->pos + i] = c;
211 i++;
212 }
213
214 ti->pos += ilen;
215 ti->nc += ilen;
216 ti->buffer[ti->nc] = '\0';
217 ti->sel_start = ti->pos;
218
219 tinput_display_tail(ti, ti->pos - ilen, 0);
220 tinput_update_origin(ti);
221 tinput_position_caret(ti);
222}
223
224static void tinput_backspace(tinput_t *ti)
225{
226 if (tinput_sel_active(ti)) {
227 tinput_sel_delete(ti);
228 return;
229 }
230
231 if (ti->pos == 0)
232 return;
233
234 size_t i;
235 for (i = ti->pos; i < ti->nc; i++)
236 ti->buffer[i - 1] = ti->buffer[i];
237
238 ti->pos -= 1;
239 ti->nc -= 1;
240 ti->buffer[ti->nc] = '\0';
241 ti->sel_start = ti->pos;
242
243 tinput_display_tail(ti, ti->pos, 1);
244 tinput_position_caret(ti);
245}
246
247static void tinput_delete(tinput_t *ti)
248{
249 if (tinput_sel_active(ti)) {
250 tinput_sel_delete(ti);
251 return;
252 }
253
254 if (ti->pos == ti->nc)
255 return;
256
257 ti->pos += 1;
258 ti->sel_start = ti->pos;
259
260 tinput_backspace(ti);
261}
262
263static void tinput_seek_cell(tinput_t *ti, seek_dir_t dir, bool shift_held)
264{
265 tinput_pre_seek(ti, shift_held);
266
267 if (dir == seek_forward) {
268 if (ti->pos < ti->nc)
269 ti->pos += 1;
270 } else {
271 if (ti->pos > 0)
272 ti->pos -= 1;
273 }
274
275 tinput_post_seek(ti, shift_held);
276}
277
278static void tinput_seek_word(tinput_t *ti, seek_dir_t dir, bool shift_held)
279{
280 tinput_pre_seek(ti, shift_held);
281
282 if (dir == seek_forward) {
283 if (ti->pos == ti->nc)
284 return;
285
286 while (true) {
287 ti->pos += 1;
288
289 if (ti->pos == ti->nc)
290 break;
291
292 if ((ti->buffer[ti->pos - 1] == ' ') &&
293 (ti->buffer[ti->pos] != ' '))
294 break;
295 }
296 } else {
297 if (ti->pos == 0)
298 return;
299
300 while (true) {
301 ti->pos -= 1;
302
303 if (ti->pos == 0)
304 break;
305
306 if (ti->buffer[ti->pos - 1] == ' ' &&
307 ti->buffer[ti->pos] != ' ')
308 break;
309 }
310
311 }
312
313 tinput_post_seek(ti, shift_held);
314}
315
316static void tinput_seek_vertical(tinput_t *ti, seek_dir_t dir, bool shift_held)
317{
318 tinput_pre_seek(ti, shift_held);
319
320 if (dir == seek_forward) {
321 if (ti->pos + ti->con_cols <= ti->nc)
322 ti->pos = ti->pos + ti->con_cols;
323 } else {
324 if (ti->pos >= ti->con_cols)
325 ti->pos = ti->pos - ti->con_cols;
326 }
327
328 tinput_post_seek(ti, shift_held);
329}
330
331static void tinput_seek_max(tinput_t *ti, seek_dir_t dir, bool shift_held)
332{
333 tinput_pre_seek(ti, shift_held);
334
335 if (dir == seek_backward)
336 ti->pos = 0;
337 else
338 ti->pos = ti->nc;
339
340 tinput_post_seek(ti, shift_held);
341}
342
343static void tinput_pre_seek(tinput_t *ti, bool shift_held)
344{
345 if ((tinput_sel_active(ti)) && (!shift_held)) {
346 /* Unselect and redraw. */
347 ti->sel_start = ti->pos;
348 tinput_display_tail(ti, 0, 0);
349 tinput_position_caret(ti);
350 }
351}
352
353static void tinput_post_seek(tinput_t *ti, bool shift_held)
354{
355 if (shift_held) {
356 /* Selecting text. Need redraw. */
357 tinput_display_tail(ti, 0, 0);
358 } else {
359 /* Shift not held. Keep selection empty. */
360 ti->sel_start = ti->pos;
361 }
362
363 tinput_position_caret(ti);
364}
365
366static void tinput_history_insert(tinput_t *ti, char *str)
367{
368 if (ti->hnum < HISTORY_LEN) {
369 ti->hnum += 1;
370 } else {
371 if (ti->history[HISTORY_LEN] != NULL)
372 free(ti->history[HISTORY_LEN]);
373 }
374
375 size_t i;
376 for (i = ti->hnum; i > 1; i--)
377 ti->history[i] = ti->history[i - 1];
378
379 ti->history[1] = str_dup(str);
380
381 if (ti->history[0] != NULL) {
382 free(ti->history[0]);
383 ti->history[0] = NULL;
384 }
385}
386
387static void tinput_set_str(tinput_t *ti, char *str)
388{
389 str_to_wstr(ti->buffer, INPUT_MAX_SIZE, str);
390 ti->nc = wstr_length(ti->buffer);
391 ti->pos = ti->nc;
392 ti->sel_start = ti->pos;
393}
394
395static void tinput_sel_get_bounds(tinput_t *ti, size_t *sa, size_t *sb)
396{
397 if (ti->sel_start < ti->pos) {
398 *sa = ti->sel_start;
399 *sb = ti->pos;
400 } else {
401 *sa = ti->pos;
402 *sb = ti->sel_start;
403 }
404}
405
406static bool tinput_sel_active(tinput_t *ti)
407{
408 return (ti->sel_start != ti->pos);
409}
410
411static void tinput_sel_all(tinput_t *ti)
412{
413 ti->sel_start = 0;
414 ti->pos = ti->nc;
415 tinput_display_tail(ti, 0, 0);
416 tinput_position_caret(ti);
417}
418
419static void tinput_sel_delete(tinput_t *ti)
420{
421 size_t sa;
422 size_t sb;
423
424 tinput_sel_get_bounds(ti, &sa, &sb);
425 if (sa == sb)
426 return;
427
428 memmove(ti->buffer + sa, ti->buffer + sb,
429 (ti->nc - sb) * sizeof(wchar_t));
430
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 size_t sa;
442 size_t sb;
443
444 tinput_sel_get_bounds(ti, &sa, &sb);
445
446 char *str;
447
448 if (sb < ti->nc) {
449 wchar_t tmp_c = ti->buffer[sb];
450 ti->buffer[sb] = '\0';
451 str = wstr_to_astr(ti->buffer + sa);
452 ti->buffer[sb] = tmp_c;
453 } else
454 str = wstr_to_astr(ti->buffer + sa);
455
456 if (str == NULL)
457 goto error;
458
459 if (clipboard_put_str(str) != EOK)
460 goto error;
461
462 free(str);
463 return;
464
465error:
466 /* TODO: Give the user some kind of warning. */
467 return;
468}
469
470static void tinput_paste_from_cb(tinput_t *ti)
471{
472 char *str;
473 int rc = clipboard_get_str(&str);
474
475 if ((rc != EOK) || (str == NULL)) {
476 /* TODO: Give the user some kind of warning. */
477 return;
478 }
479
480 tinput_insert_string(ti, str);
481 free(str);
482}
483
484static void tinput_history_seek(tinput_t *ti, int offs)
485{
486 if (offs >= 0) {
487 if (ti->hpos + offs > ti->hnum)
488 return;
489 } else {
490 if (ti->hpos < (size_t) -offs)
491 return;
492 }
493
494 if (ti->history[ti->hpos] != NULL) {
495 free(ti->history[ti->hpos]);
496 ti->history[ti->hpos] = NULL;
497 }
498
499 ti->history[ti->hpos] = tinput_get_str(ti);
500 ti->hpos += offs;
501
502 int pad = (int) ti->nc - str_length(ti->history[ti->hpos]);
503 if (pad < 0)
504 pad = 0;
505
506 tinput_set_str(ti, ti->history[ti->hpos]);
507 tinput_display_tail(ti, 0, pad);
508 tinput_update_origin(ti);
509 tinput_position_caret(ti);
510}
511
512/** Initialize text input field.
513 *
514 * Must be called before using the field. It clears the history.
515 */
516static void tinput_init(tinput_t *ti)
517{
518 ti->hnum = 0;
519 ti->hpos = 0;
520 ti->history[0] = NULL;
521}
522
523/** Read in one line of input.
524 *
525 * @param ti Text input.
526 * @param dstr Place to save pointer to new string.
527 *
528 * @return EOK on success
529 * @return ENOENT if user requested abort
530 * @return EIO if communication with console failed
531 *
532 */
533int tinput_read(tinput_t *ti, char **dstr)
534{
535 fflush(stdout);
536 if (console_get_size(fphone(stdin), &ti->con_cols, &ti->con_rows) != EOK)
537 return EIO;
538
539 if (console_get_pos(fphone(stdin), &ti->col0, &ti->row0) != EOK)
540 return EIO;
541
542 ti->pos = 0;
543 ti->sel_start = 0;
544 ti->nc = 0;
545 ti->buffer[0] = '\0';
546 ti->done = false;
547 ti->exit_clui = false;
548
549 while (!ti->done) {
550 fflush(stdout);
551
552 console_event_t ev;
553 if (!console_get_event(fphone(stdin), &ev))
554 return EIO;
555
556 if (ev.type != KEY_PRESS)
557 continue;
558
559 if (((ev.mods & KM_CTRL) != 0) &&
560 ((ev.mods & (KM_ALT | KM_SHIFT)) == 0))
561 tinput_key_ctrl(ti, &ev);
562
563 if (((ev.mods & KM_SHIFT) != 0) &&
564 ((ev.mods & (KM_CTRL | KM_ALT)) == 0))
565 tinput_key_shift(ti, &ev);
566
567 if (((ev.mods & KM_CTRL) != 0) &&
568 ((ev.mods & KM_SHIFT) != 0) &&
569 ((ev.mods & KM_ALT) == 0))
570 tinput_key_ctrl_shift(ti, &ev);
571
572 if ((ev.mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0)
573 tinput_key_unmod(ti, &ev);
574
575 if (ev.c >= ' ') {
576 tinput_sel_delete(ti);
577 tinput_insert_char(ti, ev.c);
578 }
579 }
580
581 if (ti->exit_clui)
582 return ENOENT;
583
584 ti->pos = ti->nc;
585 tinput_position_caret(ti);
586 putchar('\n');
587
588 char *str = tinput_get_str(ti);
589 if (str_cmp(str, "") != 0)
590 tinput_history_insert(ti, str);
591
592 ti->hpos = 0;
593
594 *dstr = str;
595 return EOK;
596}
597
598static void tinput_key_ctrl(tinput_t *ti, console_event_t *ev)
599{
600 switch (ev->key) {
601 case KC_LEFT:
602 tinput_seek_word(ti, seek_backward, false);
603 break;
604 case KC_RIGHT:
605 tinput_seek_word(ti, seek_forward, false);
606 break;
607 case KC_UP:
608 tinput_seek_vertical(ti, seek_backward, false);
609 break;
610 case KC_DOWN:
611 tinput_seek_vertical(ti, seek_forward, false);
612 break;
613 case KC_X:
614 tinput_sel_copy_to_cb(ti);
615 tinput_sel_delete(ti);
616 break;
617 case KC_C:
618 tinput_sel_copy_to_cb(ti);
619 break;
620 case KC_V:
621 tinput_sel_delete(ti);
622 tinput_paste_from_cb(ti);
623 break;
624 case KC_A:
625 tinput_sel_all(ti);
626 break;
627 case KC_Q:
628 /* Signal libary client to quit interactive loop. */
629 ti->done = true;
630 ti->exit_clui = true;
631 break;
632 default:
633 break;
634 }
635}
636
637static void tinput_key_ctrl_shift(tinput_t *ti, console_event_t *ev)
638{
639 switch (ev->key) {
640 case KC_LEFT:
641 tinput_seek_word(ti, seek_backward, true);
642 break;
643 case KC_RIGHT:
644 tinput_seek_word(ti, seek_forward, true);
645 break;
646 case KC_UP:
647 tinput_seek_vertical(ti, seek_backward, true);
648 break;
649 case KC_DOWN:
650 tinput_seek_vertical(ti, seek_forward, true);
651 break;
652 default:
653 break;
654 }
655}
656
657static void tinput_key_shift(tinput_t *ti, console_event_t *ev)
658{
659 switch (ev->key) {
660 case KC_LEFT:
661 tinput_seek_cell(ti, seek_backward, true);
662 break;
663 case KC_RIGHT:
664 tinput_seek_cell(ti, seek_forward, true);
665 break;
666 case KC_UP:
667 tinput_seek_vertical(ti, seek_backward, true);
668 break;
669 case KC_DOWN:
670 tinput_seek_vertical(ti, seek_forward, true);
671 break;
672 case KC_HOME:
673 tinput_seek_max(ti, seek_backward, true);
674 break;
675 case KC_END:
676 tinput_seek_max(ti, seek_forward, true);
677 break;
678 default:
679 break;
680 }
681}
682
683static void tinput_key_unmod(tinput_t *ti, console_event_t *ev)
684{
685 switch (ev->key) {
686 case KC_ENTER:
687 case KC_NENTER:
688 ti->done = true;
689 break;
690 case KC_BACKSPACE:
691 tinput_backspace(ti);
692 break;
693 case KC_DELETE:
694 tinput_delete(ti);
695 break;
696 case KC_LEFT:
697 tinput_seek_cell(ti, seek_backward, false);
698 break;
699 case KC_RIGHT:
700 tinput_seek_cell(ti, seek_forward, false);
701 break;
702 case KC_HOME:
703 tinput_seek_max(ti, seek_backward, false);
704 break;
705 case KC_END:
706 tinput_seek_max(ti, seek_forward, false);
707 break;
708 case KC_UP:
709 tinput_history_seek(ti, 1);
710 break;
711 case KC_DOWN:
712 tinput_history_seek(ti, -1);
713 break;
714 default:
715 break;
716 }
717}
Note: See TracBrowser for help on using the repository browser.