tinput.c

00001 /*
00002  * Copyright (c) 2010 Jiri Svoboda
00003  * All rights reserved.
00004  *
00005  * Redistribution and use in source and binary forms, with or without
00006  * modification, are permitted provided that the following conditions
00007  * are met:
00008  *
00009  * - Redistributions of source code must retain the above copyright
00010  *   notice, this list of conditions and the following disclaimer.
00011  * - Redistributions in binary form must reproduce the above copyright
00012  *   notice, this list of conditions and the following disclaimer in the
00013  *   documentation and/or other materials provided with the distribution.
00014  * - The name of the author may not be used to endorse or promote products
00015  *   derived from this software without specific prior written permission.
00016  *
00017  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
00018  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
00019  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
00020  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
00021  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
00022  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
00023  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
00024  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00025  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
00026  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00027  */
00028 
00029 #include <stdio.h>
00030 #include <stdlib.h>
00031 #include <str.h>
00032 #include <io/console.h>
00033 #include <io/keycode.h>
00034 #include <io/style.h>
00035 #include <io/color.h>
00036 #include <vfs/vfs.h>
00037 #include <clipboard.h>
00038 #include <macros.h>
00039 #include <errno.h>
00040 #include <assert.h>
00041 #include <bool.h>
00042 #include <tinput.h>
00043 
00045 typedef enum {
00046         seek_backward = -1,
00047         seek_forward = 1
00048 } seek_dir_t;
00049 
00050 static void tinput_init(tinput_t *);
00051 static void tinput_insert_string(tinput_t *, const char *);
00052 static void tinput_sel_get_bounds(tinput_t *, size_t *, size_t *);
00053 static bool tinput_sel_active(tinput_t *);
00054 static void tinput_sel_all(tinput_t *);
00055 static void tinput_sel_delete(tinput_t *);
00056 static void tinput_key_ctrl(tinput_t *, console_event_t *);
00057 static void tinput_key_shift(tinput_t *, console_event_t *);
00058 static void tinput_key_ctrl_shift(tinput_t *, console_event_t *);
00059 static void tinput_key_unmod(tinput_t *, console_event_t *);
00060 static void tinput_pre_seek(tinput_t *, bool);
00061 static void tinput_post_seek(tinput_t *, bool);
00062 
00064 tinput_t *tinput_new(void)
00065 {
00066         tinput_t *ti;
00067         
00068         ti = malloc(sizeof(tinput_t));
00069         if (ti == NULL)
00070                 return NULL;
00071         
00072         tinput_init(ti);
00073         return ti;
00074 }
00075 
00077 void tinput_destroy(tinput_t *ti)
00078 {
00079         free(ti);
00080 }
00081 
00082 static void tinput_display_tail(tinput_t *ti, size_t start, size_t pad)
00083 {
00084         wchar_t dbuf[INPUT_MAX_SIZE + 1];
00085         
00086         size_t sa;
00087         size_t sb;
00088         tinput_sel_get_bounds(ti, &sa, &sb);
00089         
00090         console_set_pos(fphone(stdout), (ti->col0 + start) % ti->con_cols,
00091             ti->row0 + (ti->col0 + start) / ti->con_cols);
00092         console_set_style(fphone(stdout), STYLE_NORMAL);
00093         
00094         size_t p = start;
00095         if (p < sa) {
00096                 memcpy(dbuf, ti->buffer + p, (sa - p) * sizeof(wchar_t));
00097                 dbuf[sa - p] = '\0';
00098                 printf("%ls", dbuf);
00099                 p = sa;
00100         }
00101         
00102         if (p < sb) {
00103                 fflush(stdout);
00104                 console_set_style(fphone(stdout), STYLE_SELECTED);
00105                 memcpy(dbuf, ti->buffer + p,
00106                     (sb - p) * sizeof(wchar_t));
00107                 dbuf[sb - p] = '\0';
00108                 printf("%ls", dbuf);
00109                 p = sb;
00110         }
00111         
00112         fflush(stdout);
00113         console_set_style(fphone(stdout), STYLE_NORMAL);
00114         
00115         if (p < ti->nc) {
00116                 memcpy(dbuf, ti->buffer + p,
00117                     (ti->nc - p) * sizeof(wchar_t));
00118                 dbuf[ti->nc - p] = '\0';
00119                 printf("%ls", dbuf);
00120         }
00121         
00122         for (p = 0; p < pad; p++)
00123                 putchar(' ');
00124         
00125         fflush(stdout);
00126 }
00127 
00128 static char *tinput_get_str(tinput_t *ti)
00129 {
00130         return wstr_to_astr(ti->buffer);
00131 }
00132 
00133 static void tinput_position_caret(tinput_t *ti)
00134 {
00135         console_set_pos(fphone(stdout), (ti->col0 + ti->pos) % ti->con_cols,
00136             ti->row0 + (ti->col0 + ti->pos) / ti->con_cols);
00137 }
00138 
00140 static void tinput_update_origin(tinput_t *ti)
00141 {
00142         sysarg_t width = ti->col0 + ti->nc;
00143         sysarg_t rows = (width / ti->con_cols) + 1;
00144         
00145         /* Update row0 if the screen scrolled. */
00146         if (ti->row0 + rows > ti->con_rows)
00147                 ti->row0 = ti->con_rows - rows;
00148 }
00149 
00150 static void tinput_insert_char(tinput_t *ti, wchar_t c)
00151 {
00152         if (ti->nc == INPUT_MAX_SIZE)
00153                 return;
00154         
00155         sysarg_t new_width = ti->col0 + ti->nc + 1;
00156         if (new_width % ti->con_cols == 0) {
00157                 /* Advancing to new line. */
00158                 sysarg_t new_height = (new_width / ti->con_cols) + 1;
00159                 if (new_height >= ti->con_rows) {
00160                         /* Disallow text longer than 1 page for now. */
00161                         return;
00162                 }
00163         }
00164         
00165         size_t i;
00166         for (i = ti->nc; i > ti->pos; i--)
00167                 ti->buffer[i] = ti->buffer[i - 1];
00168         
00169         ti->buffer[ti->pos] = c;
00170         ti->pos += 1;
00171         ti->nc += 1;
00172         ti->buffer[ti->nc] = '\0';
00173         ti->sel_start = ti->pos;
00174         
00175         tinput_display_tail(ti, ti->pos - 1, 0);
00176         tinput_update_origin(ti);
00177         tinput_position_caret(ti);
00178 }
00179 
00180 static void tinput_insert_string(tinput_t *ti, const char *str)
00181 {
00182         size_t ilen = min(str_length(str), INPUT_MAX_SIZE - ti->nc);
00183         if (ilen == 0)
00184                 return;
00185         
00186         sysarg_t new_width = ti->col0 + ti->nc + ilen;
00187         sysarg_t new_height = (new_width / ti->con_cols) + 1;
00188         if (new_height >= ti->con_rows) {
00189                 /* Disallow text longer than 1 page for now. */
00190                 return;
00191         }
00192         
00193         if (ti->nc > 0) {
00194                 size_t i;
00195                 for (i = ti->nc; i > ti->pos; i--)
00196                         ti->buffer[i + ilen - 1] = ti->buffer[i - 1];
00197         }
00198         
00199         size_t off = 0;
00200         size_t i = 0;
00201         while (i < ilen) {
00202                 wchar_t c = str_decode(str, &off, STR_NO_LIMIT);
00203                 if (c == '\0')
00204                         break;
00205                 
00206                 /* Filter out non-printable chars. */
00207                 if (c < 32)
00208                         c = 32;
00209                 
00210                 ti->buffer[ti->pos + i] = c;
00211                 i++;
00212         }
00213         
00214         ti->pos += ilen;
00215         ti->nc += ilen;
00216         ti->buffer[ti->nc] = '\0';
00217         ti->sel_start = ti->pos;
00218         
00219         tinput_display_tail(ti, ti->pos - ilen, 0);
00220         tinput_update_origin(ti);
00221         tinput_position_caret(ti);
00222 }
00223 
00224 static void tinput_backspace(tinput_t *ti)
00225 {
00226         if (tinput_sel_active(ti)) {
00227                 tinput_sel_delete(ti);
00228                 return;
00229         }
00230         
00231         if (ti->pos == 0)
00232                 return;
00233         
00234         size_t i;
00235         for (i = ti->pos; i < ti->nc; i++)
00236                 ti->buffer[i - 1] = ti->buffer[i];
00237         
00238         ti->pos -= 1;
00239         ti->nc -= 1;
00240         ti->buffer[ti->nc] = '\0';
00241         ti->sel_start = ti->pos;
00242         
00243         tinput_display_tail(ti, ti->pos, 1);
00244         tinput_position_caret(ti);
00245 }
00246 
00247 static void tinput_delete(tinput_t *ti)
00248 {
00249         if (tinput_sel_active(ti)) {
00250                 tinput_sel_delete(ti);
00251                 return;
00252         }
00253         
00254         if (ti->pos == ti->nc)
00255                 return;
00256         
00257         ti->pos += 1;
00258         ti->sel_start = ti->pos;
00259         
00260         tinput_backspace(ti);
00261 }
00262 
00263 static void tinput_seek_cell(tinput_t *ti, seek_dir_t dir, bool shift_held)
00264 {
00265         tinput_pre_seek(ti, shift_held);
00266         
00267         if (dir == seek_forward) {
00268                 if (ti->pos < ti->nc)
00269                         ti->pos += 1;
00270         } else {
00271                 if (ti->pos > 0)
00272                         ti->pos -= 1;
00273         }
00274         
00275         tinput_post_seek(ti, shift_held);
00276 }
00277 
00278 static void tinput_seek_word(tinput_t *ti, seek_dir_t dir, bool shift_held)
00279 {
00280         tinput_pre_seek(ti, shift_held);
00281         
00282         if (dir == seek_forward) {
00283                 if (ti->pos == ti->nc)
00284                         return;
00285                 
00286                 while (true) {
00287                         ti->pos += 1;
00288                         
00289                         if (ti->pos == ti->nc)
00290                                 break;
00291                         
00292                         if ((ti->buffer[ti->pos - 1] == ' ') &&
00293                             (ti->buffer[ti->pos] != ' '))
00294                                 break;
00295                 }
00296         } else {
00297                 if (ti->pos == 0)
00298                         return;
00299                 
00300                 while (true) {
00301                         ti->pos -= 1;
00302                         
00303                         if (ti->pos == 0)
00304                                 break;
00305                         
00306                         if (ti->buffer[ti->pos - 1] == ' ' &&
00307                             ti->buffer[ti->pos] != ' ')
00308                                 break;
00309                 }
00310         
00311         }
00312         
00313         tinput_post_seek(ti, shift_held);
00314 }
00315 
00316 static void tinput_seek_vertical(tinput_t *ti, seek_dir_t dir, bool shift_held)
00317 {
00318         tinput_pre_seek(ti, shift_held);
00319         
00320         if (dir == seek_forward) {
00321                 if (ti->pos + ti->con_cols <= ti->nc)
00322                         ti->pos = ti->pos + ti->con_cols;
00323         } else {
00324                 if (ti->pos >= ti->con_cols)
00325                         ti->pos = ti->pos - ti->con_cols;
00326         }
00327         
00328         tinput_post_seek(ti, shift_held);
00329 }
00330 
00331 static void tinput_seek_max(tinput_t *ti, seek_dir_t dir, bool shift_held)
00332 {
00333         tinput_pre_seek(ti, shift_held);
00334         
00335         if (dir == seek_backward)
00336                 ti->pos = 0;
00337         else
00338                 ti->pos = ti->nc;
00339         
00340         tinput_post_seek(ti, shift_held);
00341 }
00342 
00343 static void tinput_pre_seek(tinput_t *ti, bool shift_held)
00344 {
00345         if ((tinput_sel_active(ti)) && (!shift_held)) {
00346                 /* Unselect and redraw. */
00347                 ti->sel_start = ti->pos;
00348                 tinput_display_tail(ti, 0, 0);
00349                 tinput_position_caret(ti);
00350         }
00351 }
00352 
00353 static void tinput_post_seek(tinput_t *ti, bool shift_held)
00354 {
00355         if (shift_held) {
00356                 /* Selecting text. Need redraw. */
00357                 tinput_display_tail(ti, 0, 0);
00358         } else {
00359                 /* Shift not held. Keep selection empty. */
00360                 ti->sel_start = ti->pos;
00361         }
00362         
00363         tinput_position_caret(ti);
00364 }
00365 
00366 static void tinput_history_insert(tinput_t *ti, char *str)
00367 {
00368         if (ti->hnum < HISTORY_LEN) {
00369                 ti->hnum += 1;
00370         } else {
00371                 if (ti->history[HISTORY_LEN] != NULL)
00372                         free(ti->history[HISTORY_LEN]);
00373         }
00374         
00375         size_t i;
00376         for (i = ti->hnum; i > 1; i--)
00377                 ti->history[i] = ti->history[i - 1];
00378         
00379         ti->history[1] = str_dup(str);
00380         
00381         if (ti->history[0] != NULL) {
00382                 free(ti->history[0]);
00383                 ti->history[0] = NULL;
00384         }
00385 }
00386 
00387 static void tinput_set_str(tinput_t *ti, char *str)
00388 {
00389         str_to_wstr(ti->buffer, INPUT_MAX_SIZE, str);
00390         ti->nc = wstr_length(ti->buffer);
00391         ti->pos = ti->nc;
00392         ti->sel_start = ti->pos;
00393 }
00394 
00395 static void tinput_sel_get_bounds(tinput_t *ti, size_t *sa, size_t *sb)
00396 {
00397         if (ti->sel_start < ti->pos) {
00398                 *sa = ti->sel_start;
00399                 *sb = ti->pos;
00400         } else {
00401                 *sa = ti->pos;
00402                 *sb = ti->sel_start;
00403         }
00404 }
00405 
00406 static bool tinput_sel_active(tinput_t *ti)
00407 {
00408         return (ti->sel_start != ti->pos);
00409 }
00410 
00411 static void tinput_sel_all(tinput_t *ti)
00412 {
00413         ti->sel_start = 0;
00414         ti->pos = ti->nc;
00415         tinput_display_tail(ti, 0, 0);
00416         tinput_position_caret(ti);
00417 }
00418 
00419 static void tinput_sel_delete(tinput_t *ti)
00420 {
00421         size_t sa;
00422         size_t sb;
00423         
00424         tinput_sel_get_bounds(ti, &sa, &sb);
00425         if (sa == sb)
00426                 return;
00427         
00428         memmove(ti->buffer + sa, ti->buffer + sb,
00429             (ti->nc - sb) * sizeof(wchar_t));
00430         
00431         ti->pos = ti->sel_start = sa;
00432         ti->nc -= (sb - sa);
00433         ti->buffer[ti->nc] = '\0';
00434         
00435         tinput_display_tail(ti, sa, sb - sa);
00436         tinput_position_caret(ti);
00437 }
00438 
00439 static void tinput_sel_copy_to_cb(tinput_t *ti)
00440 {
00441         size_t sa;
00442         size_t sb;
00443         
00444         tinput_sel_get_bounds(ti, &sa, &sb);
00445         
00446         char *str;
00447         
00448         if (sb < ti->nc) {
00449                 wchar_t tmp_c = ti->buffer[sb];
00450                 ti->buffer[sb] = '\0';
00451                 str = wstr_to_astr(ti->buffer + sa);
00452                 ti->buffer[sb] = tmp_c;
00453         } else
00454                 str = wstr_to_astr(ti->buffer + sa);
00455         
00456         if (str == NULL)
00457                 goto error;
00458         
00459         if (clipboard_put_str(str) != EOK)
00460                 goto error;
00461         
00462         free(str);
00463         return;
00464         
00465 error:
00466         /* TODO: Give the user some kind of warning. */
00467         return;
00468 }
00469 
00470 static void tinput_paste_from_cb(tinput_t *ti)
00471 {
00472         char *str;
00473         int rc = clipboard_get_str(&str);
00474         
00475         if ((rc != EOK) || (str == NULL)) {
00476                 /* TODO: Give the user some kind of warning. */
00477                 return;
00478         }
00479         
00480         tinput_insert_string(ti, str);
00481         free(str);
00482 }
00483 
00484 static void tinput_history_seek(tinput_t *ti, int offs)
00485 {
00486         if (offs >= 0) {
00487                 if (ti->hpos + offs > ti->hnum)
00488                         return;
00489         } else {
00490                 if (ti->hpos < (size_t) -offs)
00491                         return;
00492         }
00493         
00494         if (ti->history[ti->hpos] != NULL) {
00495                 free(ti->history[ti->hpos]);
00496                 ti->history[ti->hpos] = NULL;
00497         }
00498         
00499         ti->history[ti->hpos] = tinput_get_str(ti);
00500         ti->hpos += offs;
00501         
00502         int pad = (int) ti->nc - str_length(ti->history[ti->hpos]);
00503         if (pad < 0)
00504                 pad = 0;
00505         
00506         tinput_set_str(ti, ti->history[ti->hpos]);
00507         tinput_display_tail(ti, 0, pad);
00508         tinput_update_origin(ti);
00509         tinput_position_caret(ti);
00510 }
00511 
00516 static void tinput_init(tinput_t *ti)
00517 {
00518         ti->hnum = 0;
00519         ti->hpos = 0;
00520         ti->history[0] = NULL;
00521 }
00522 
00533 int tinput_read(tinput_t *ti, char **dstr)
00534 {
00535         fflush(stdout);
00536         if (console_get_size(fphone(stdin), &ti->con_cols, &ti->con_rows) != EOK)
00537                 return EIO;
00538         
00539         if (console_get_pos(fphone(stdin), &ti->col0, &ti->row0) != EOK)
00540                 return EIO;
00541         
00542         ti->pos = 0;
00543         ti->sel_start = 0;
00544         ti->nc = 0;
00545         ti->buffer[0] = '\0';
00546         ti->done = false;
00547         ti->exit_clui = false;
00548         
00549         while (!ti->done) {
00550                 fflush(stdout);
00551                 
00552                 console_event_t ev;
00553                 if (!console_get_event(fphone(stdin), &ev))
00554                         return EIO;
00555                 
00556                 if (ev.type != KEY_PRESS)
00557                         continue;
00558                 
00559                 if (((ev.mods & KM_CTRL) != 0) &&
00560                     ((ev.mods & (KM_ALT | KM_SHIFT)) == 0))
00561                         tinput_key_ctrl(ti, &ev);
00562                 
00563                 if (((ev.mods & KM_SHIFT) != 0) &&
00564                     ((ev.mods & (KM_CTRL | KM_ALT)) == 0))
00565                         tinput_key_shift(ti, &ev);
00566                 
00567                 if (((ev.mods & KM_CTRL) != 0) &&
00568                     ((ev.mods & KM_SHIFT) != 0) &&
00569                     ((ev.mods & KM_ALT) == 0))
00570                         tinput_key_ctrl_shift(ti, &ev);
00571                 
00572                 if ((ev.mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0)
00573                         tinput_key_unmod(ti, &ev);
00574                 
00575                 if (ev.c >= ' ') {
00576                         tinput_sel_delete(ti);
00577                         tinput_insert_char(ti, ev.c);
00578                 }
00579         }
00580         
00581         if (ti->exit_clui)
00582                 return ENOENT;
00583         
00584         ti->pos = ti->nc;
00585         tinput_position_caret(ti);
00586         putchar('\n');
00587         
00588         char *str = tinput_get_str(ti);
00589         if (str_cmp(str, "") != 0)
00590                 tinput_history_insert(ti, str);
00591         
00592         ti->hpos = 0;
00593         
00594         *dstr = str;
00595         return EOK;
00596 }
00597 
00598 static void tinput_key_ctrl(tinput_t *ti, console_event_t *ev)
00599 {
00600         switch (ev->key) {
00601         case KC_LEFT:
00602                 tinput_seek_word(ti, seek_backward, false);
00603                 break;
00604         case KC_RIGHT:
00605                 tinput_seek_word(ti, seek_forward, false);
00606                 break;
00607         case KC_UP:
00608                 tinput_seek_vertical(ti, seek_backward, false);
00609                 break;
00610         case KC_DOWN:
00611                 tinput_seek_vertical(ti, seek_forward, false);
00612                 break;
00613         case KC_X:
00614                 tinput_sel_copy_to_cb(ti);
00615                 tinput_sel_delete(ti);
00616                 break;
00617         case KC_C:
00618                 tinput_sel_copy_to_cb(ti);
00619                 break;
00620         case KC_V:
00621                 tinput_sel_delete(ti);
00622                 tinput_paste_from_cb(ti);
00623                 break;
00624         case KC_A:
00625                 tinput_sel_all(ti);
00626                 break;
00627         case KC_Q:
00628                 /* Signal libary client to quit interactive loop. */
00629                 ti->done = true;
00630                 ti->exit_clui = true;
00631                 break;
00632         default:
00633                 break;
00634         }
00635 }
00636 
00637 static void tinput_key_ctrl_shift(tinput_t *ti, console_event_t *ev)
00638 {
00639         switch (ev->key) {
00640         case KC_LEFT:
00641                 tinput_seek_word(ti, seek_backward, true);
00642                 break;
00643         case KC_RIGHT:
00644                 tinput_seek_word(ti, seek_forward, true);
00645                 break;
00646         case KC_UP:
00647                 tinput_seek_vertical(ti, seek_backward, true);
00648                 break;
00649         case KC_DOWN:
00650                 tinput_seek_vertical(ti, seek_forward, true);
00651                 break;
00652         default:
00653                 break;
00654         }
00655 }
00656 
00657 static void tinput_key_shift(tinput_t *ti, console_event_t *ev)
00658 {
00659         switch (ev->key) {
00660         case KC_LEFT:
00661                 tinput_seek_cell(ti, seek_backward, true);
00662                 break;
00663         case KC_RIGHT:
00664                 tinput_seek_cell(ti, seek_forward, true);
00665                 break;
00666         case KC_UP:
00667                 tinput_seek_vertical(ti, seek_backward, true);
00668                 break;
00669         case KC_DOWN:
00670                 tinput_seek_vertical(ti, seek_forward, true);
00671                 break;
00672         case KC_HOME:
00673                 tinput_seek_max(ti, seek_backward, true);
00674                 break;
00675         case KC_END:
00676                 tinput_seek_max(ti, seek_forward, true);
00677                 break;
00678         default:
00679                 break;
00680         }
00681 }
00682 
00683 static void tinput_key_unmod(tinput_t *ti, console_event_t *ev)
00684 {
00685         switch (ev->key) {
00686         case KC_ENTER:
00687         case KC_NENTER:
00688                 ti->done = true;
00689                 break;
00690         case KC_BACKSPACE:
00691                 tinput_backspace(ti);
00692                 break;
00693         case KC_DELETE:
00694                 tinput_delete(ti);
00695                 break;
00696         case KC_LEFT:
00697                 tinput_seek_cell(ti, seek_backward, false);
00698                 break;
00699         case KC_RIGHT:
00700                 tinput_seek_cell(ti, seek_forward, false);
00701                 break;
00702         case KC_HOME:
00703                 tinput_seek_max(ti, seek_backward, false);
00704                 break;
00705         case KC_END:
00706                 tinput_seek_max(ti, seek_forward, false);
00707                 break;
00708         case KC_UP:
00709                 tinput_history_seek(ti, 1);
00710                 break;
00711         case KC_DOWN:
00712                 tinput_history_seek(ti, -1);
00713                 break;
00714         default:
00715                 break;
00716         }
00717 }

Generated on Thu Jun 2 07:45:47 2011 for HelenOS/USB by  doxygen 1.4.7