diff options
author | piaip <piaip@63ad8ddf-47c3-0310-b6dd-a9e9d9715204> | 2009-09-29 01:59:20 +0800 |
---|---|---|
committer | piaip <piaip@63ad8ddf-47c3-0310-b6dd-a9e9d9715204> | 2009-09-29 01:59:20 +0800 |
commit | c78d877d45496115e90c3584b063fd2bc72303b9 (patch) | |
tree | 2e855c83f3d3e4b3637644d0a60a2431e071b17e /mbbsd/vtuikit.c | |
parent | c0dd6835cc9f376ad6f195bad2daacff12b5efd9 (diff) | |
download | pttbbs-c78d877d45496115e90c3584b063fd2bc72303b9.tar pttbbs-c78d877d45496115e90c3584b063fd2bc72303b9.tar.gz pttbbs-c78d877d45496115e90c3584b063fd2bc72303b9.tar.bz2 pttbbs-c78d877d45496115e90c3584b063fd2bc72303b9.tar.lz pttbbs-c78d877d45496115e90c3584b063fd2bc72303b9.tar.xz pttbbs-c78d877d45496115e90c3584b063fd2bc72303b9.tar.zst pttbbs-c78d877d45496115e90c3584b063fd2bc72303b9.zip |
* refine kbd stack and move to common/bbs
* rename visio to vtuikit
git-svn-id: http://opensvn.csie.org/pttbbs/trunk/pttbbs@4888 63ad8ddf-47c3-0310-b6dd-a9e9d9715204
Diffstat (limited to 'mbbsd/vtuikit.c')
-rw-r--r-- | mbbsd/vtuikit.c | 1190 |
1 files changed, 1190 insertions, 0 deletions
diff --git a/mbbsd/vtuikit.c b/mbbsd/vtuikit.c new file mode 100644 index 00000000..9dbcdf55 --- /dev/null +++ b/mbbsd/vtuikit.c @@ -0,0 +1,1190 @@ +/* $Id$ */ +#include "vtkbd.h" +#include "bbs.h" + +/* + * vtuikit.c (was: visio.c) + * piaip's new implementation of virtual terminal user interface toolkits + * + * This is not the original visio.c from Maple3. + * We just borrowed its file name and few API names/prototypes + * then re-implemented everything from scratch :) + * + * We will try to keep the API behavior similiar (to help porting) + * but won't stick to it. + * Maybe at the end only 'vmsg' and 'vmsgf' will still be compatible.... + * + * m3 visio = (ptt) vtuikit+vtkbd+screen/term. + * + * Author: Hung-Te Lin (piaip), April 2008. + * + * Copyright (c) 2008-2009 Hung-Te Lin <piaip@csie.ntu.edu.tw> + * Distributed under BSD license (GPL compatible). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * To add API here, please... + * (1) name the API in prefix of 'v'. + * (2) use only screen.c APIs. + * (3) take care of wide screen and DBCS. + * (4) utilize the colos in vtuikit.h, and name asa VCLR_* (vtuikit color) + */ + +// ---- DEFINITION --------------------------------------------------- +#define MAX_COL (t_columns-1) +#define SAFE_MAX_COL (MAX_COL-1) +#define VBUFLEN (ANSILINELEN) + +// this is a special strlen to speed up processing. +// warning: x MUST be #define x "msg". +// otherwise you need to use real strlen. +#define MACROSTRLEN(x) (sizeof(x)-1) + +// ---- UTILITIES ---------------------------------------------------- +inline void +outnc(int n, unsigned char c) +{ + while (n-- > 0) + outc(c); +} + +inline void +nblank(int n) +{ + outnc(n, ' '); +} + +static inline void +fillns(int n, const char *s) +{ + while (n > 0 && *s) + outc(*s++), n--; + if (n > 0) + outnc(n, ' '); +} + +static inline void +fillns_ansi(int n, const char *s) +{ + int d = strat_ansi(n, s); + if (d < 0) { + outs(s); nblank(-d); + } else { + outns(s, d); + } +} + +// ---- VREF API -------------------------------------------------- + +/** + * vscr_save(): 傳回目前畫面的備份物件。 + */ +VREFSCR +vscr_save(void) +{ + // TODO optimize memory allocation someday. + screen_backup_t *o = (screen_backup_t*)malloc(sizeof(screen_backup_t)); + assert(o); + scr_dump(o); + return o; +} + +/** + * vscr_restore(obj): 使用並刪除畫面的備份物件。 + */ +void +vscr_restore(VREFSCR obj) +{ + screen_backup_t *o = (screen_backup_t*)obj; + if (o) + { + scr_restore(o); + memset(o, 0, sizeof(screen_backup_t)); + free(o); + } +} + +/** + * vcur_save(): 傳回目前游標的備份物件。 + */ +VREFCUR +vcur_save(void) +{ + // XXX 偷懶不 new object 了, pointer 夠大 + int y, x; + VREFCUR v; + getyx(&y, &x); + v = ((unsigned short)y << 16) | (unsigned short)x; + return v; +} + +/** + * vcur_restore(obj): 使用並刪除游標的備份物件。 + */ +void +vcur_restore(VREFCUR o) +{ + int y, x; + y = (unsigned int)(o); + x = (unsigned short)(y & 0xFFFF); + y = (unsigned short)(y >> 16); + move(y, x); +} + +// ---- LOW LEVEL API ----------------------------------------------- + +/** + * prints(fmt, ...): 使用 outs/outc 輸出並格式化字串。 + */ +void +prints(const char *fmt,...) +{ + va_list args; + char buff[VBUFLEN]; + + va_start(args, fmt); + vsnprintf(buff, sizeof(buff), fmt, args); + va_end(args); + + outs(buff); +} + +/** + * mvprints(int y, int x, fmt, ...): 使用 mvouts 輸出並格式化字串。 + */ +void +mvprints(int y, int x, const char *fmt, ...) +{ + va_list args; + char buff[VBUFLEN]; + + va_start(args, fmt); + vsnprintf(buff, sizeof(buff), fmt, args); + va_end(args); + + mvouts(x, y, buff); +} + +/** + * mvouts(y, x, str): = mvaddstr + */ +void +mvouts(int y, int x, const char *str) +{ + move(y, x); + clrtoeol(); + SOLVE_ANSI_CACHE(); + outs(str); +} + +/** + * vfill(n, flags, s): 印出並填滿 n 個字元的空間 + * + * @param n space to occupy + * @param flags VFILL_* parameters + * @param s string to display + */ +void +vfill(int n, int flags, const char *s) +{ + // warning: flag determination must take care of default values. + char has_ansi = ((flags & VFILL_HAS_ANSI) || (*s == ESC_CHR)); + char has_border = !(flags & VFILL_NO_BORDER); + + if (n < 1) + return; + + // quick return + if (!*s) + { + nblank(n); + return; + } + + // calculate border size (always draw because n > 0) + if (has_border) + n--; + + if (n > 0) + { + if (flags & VFILL_RIGHT_ALIGN) + { + // right-align + int l = has_ansi ? strlen_noansi(s) : strlen(s); + + if (l >= n) // '=' prevents blanks + l = n; + else { + nblank(n - l); + n = l; + } + // leave the task to left-align + } + + // left-align + if (has_ansi) + fillns_ansi(n, s); + else + fillns(n, s); + } + + // print border if required + if (has_border) + outc(' '); + + // close fill. + if (has_ansi) + outs(ANSI_RESET); +} + +/** + * vfill(n, flags, fmt, ...): 使用 vfill 輸出並格式化字串。 + * + * @param n space to occupy + * @param flags VFILL_* parameters + * @param fmt string to display + */ +void +vfillf(int n, int flags, const char *s, ...) +{ + va_list args; + char buff[VBUFLEN]; + + va_start(args, s); + vsnprintf(buff, sizeof(buff), s, args); + va_end(args); + + vfill(n, flags, s); +} + +/** + * vpad(n, pattern): 填滿 n 個字元 (使用的格式為 pattern) + * + * @param n 要填滿的字元數 (無法填滿時會使用空白填補) + * @param pattern 填充用的字串 + */ +inline void +vpad(int n, const char *pattern) +{ + int len = strlen(pattern); + // assert(len > 0); + + while (n >= len) + { + outs(pattern); + n -= len; + } + if (n) nblank(n); +} + +/** + * vgety(): 取得目前所在位置的行數 + * + * 考慮到 ANSI 系統,getyx() 較為少用且危險。 + * vgety() 安全而明確。 + */ +inline int +vgety(void) +{ + int y, x; + getyx(&y, &x); + return y; +} + +// ---- HIGH LEVEL API ----------------------------------------------- + +/** + * vshowmsg(s): 在底部印出指定訊息或單純的暫停訊息 + * + * @param s 指定訊息。 NULL: 任意鍵繼續。 s: 若有 \t 則後面字串靠右 (若無則顯示任意鍵) + */ +void +vshowmsg(const char *msg) +{ + int w = SAFE_MAX_COL; + move(b_lines, 0); clrtoeol(); + + if (!msg) + { + // print default message in middle + outs(VCLR_PAUSE_PAD); + outc(' '); // initial one space + + // VMSG_PAUSE MUST BE A #define STRING. + w -= MACROSTRLEN(VMSG_PAUSE); // strlen_noansi(VMSG_PAUSE); + w--; // initial space + vpad(w/2, VMSG_PAUSE_PAD); + outs(VCLR_PAUSE); + outs(VMSG_PAUSE); + outs(VCLR_PAUSE_PAD); + vpad(w - w/2, VMSG_PAUSE_PAD); + } else { + // print in left, with floating (if \t exists) + char *pfloat = strchr(msg, '\t'); + int szfloat = 0; + int nmsg = 0; + + // print prefix + w -= MACROSTRLEN(VMSG_MSG_PREFIX); // strlen_noansi(VMSG_MSG_PREFIX); + outs(VCLR_MSG); + outs(VMSG_MSG_PREFIX); + + // if have float, calculate float size + if (pfloat) { + nmsg = pfloat - msg; + w -= strlen_noansi(msg) -1; // -1 for \t + pfloat ++; // skip \t + szfloat = strlen_noansi(pfloat); + } else { + pfloat = VMSG_MSG_FLOAT; + szfloat = MACROSTRLEN(VMSG_MSG_FLOAT); // strlen_noansi() + w -= strlen_noansi(msg) + szfloat; + } + + // calculate if we can display float + if (w < 0) + { + w += szfloat; + szfloat = 0; + } + + // print msg body + if (nmsg) + outns(msg, nmsg); + else + outs(msg); + + // print padding for floats + if (w > 0) + nblank(w); + + // able to print float? + if (szfloat) + { + outs(VCLR_MSG_FLOAT); + outs(pfloat); + } + } + + // safe blank + outs(" " ANSI_RESET); +} + +/** + * vans(s): 在底部印出訊息與小輸入欄,並傳回使用者的輸入(轉為小寫)。 + * + * @param s 指定訊息,見 vshowmsg + */ +int +vans(const char *msg) +{ + char buf[3]; + + move(b_lines, 0); + clrtoeol(); SOLVE_ANSI_CACHE(); + outs(msg); + vgets(buf, sizeof(buf), VGET_LOWERCASE); + return (unsigned char)buf[0]; +} + +/** + * vansf(s, ...): 在底部印出訊息與小輸入欄,並傳回使用者的輸入(轉為小寫)。 + * + * @param s 指定訊息,見 vshowmsg + */ +int +vansf(const char *fmt, ...) +{ + char msg[VBUFLEN]; + va_list ap; + va_start(ap, fmt); + vsnprintf(msg, sizeof(msg), fmt, ap); + va_end(ap); + + return vans(msg); +} + +/** + * vmsg(s): 在底部印出指定訊息或單純的暫停訊息,並傳回使用者的按鍵。 + * + * @param s 指定訊息,見 vshowmsg + */ +int +vmsg(const char *msg) +{ + int i = 0; + + vshowmsg(msg); + + // wait for key + do { + i = igetch(); + } while( i == 0 ); + + // clear message bar + move(b_lines, 0); + clrtoeol(); + + return i; +} + + +/** + * vmsgf(s, ...): 格式化輸出暫停訊息並呼叫 vmsg)。 + * + * @param s 格式化的訊息 + */ +int +vmsgf(const char *fmt,...) +{ + char msg[VBUFLEN]; + va_list ap; + va_start(ap, fmt); + vsnprintf(msg, sizeof(msg), fmt, ap); + va_end(ap); + + return vmsg(msg); +} + +/** + * vbarf(s, ...): 格式化輸出左右對齊的字串 (MAX_COL) + * + * @param s 格式化字串 (\t 後的內容會對齊右端) + */ +void +vbarf(const char *s, ...) +{ + char msg[VBUFLEN], *s2; + va_list ap; + va_start(ap, s); + vsnprintf(msg, sizeof(msg), s, ap); + va_end(ap); + + s2 = strchr(msg, '\t'); + if (s2) *s2++ = 0; + else s2 = ""; + + return vbarlr(msg, s2); +} + +/** + * vbarlr(l, r): 左右對齊畫滿螢幕 (MAX_COL) + * 注意: 目前的實作自動認定游標已在行開頭。 + * + * @param l 靠左對齊的字串 (可含 ANSI 碼) + * @param r 靠右對齊的字串 (可含 ANSI 碼,後面不會補空白) + */ +void +vbarlr(const char *l, const char *r) +{ + // TODO strlen_noansi 跑兩次... 其實 l 可以邊 output 邊算。 + int szl = strlen_noansi(l), + szr = strlen_noansi(r); + + // assume we are already in (y, 0) + clrtoeol(); + outs(l); + szl = MAX_COL - szl; + + if (szl > szr) + { + nblank(szl - szr); + szl = szr; + } + + if (szl == szr) + outs(r); + else if (szl > 0) + nblank(szl); + + outs(ANSI_RESET); +} + +void +vs_rectangle_simple(int l, int t, int r, int b) +{ + int ol = l; + + assert( l + 4 <= r && + t + 2 <= b); + + // draw top line + move(t++, ol); l = ol+2; + outs("┌"); + while (l < r-2) { outs("─"); l+= 2; } + outs("┐"); + if (l+2 < r) outs(" "); + + while (t < b) + { + move(t++, ol); l = ol+2; + outs("│"); + // while (l < r-2) { outs(" "); l+= 2; } + l += (r-l-1)/2*2; + move_ansi(t-1, l); + outs("│"); + if (l+2 < r) outs(" "); + } + + // draw bottom line + move(t++, ol); l = ol+2; + outs("└"); + while (l < r-2) { outs("─"); l+= 2; } + outs("┘"); + if (l+2 < r) outs(" "); +} + +// ---- THEMED FORMATTING OUTPUT ------------------------------------- + +/** + * vs_header(title, mid, right): 清空螢幕並輸出完整標題 (MAX_COL) + * + * @param title: 靠左的主要標題,不會被切斷。 + * @param mid: 置中說明,可被切齊。 + * @param right: 靠右的說明,空間夠才會顯示。 + */ +void +vs_header(const char *title, const char *mid, const char *right) +{ + int w = MAX_COL; + int szmid = mid ? strlen(mid) : 0; + int szright = right ? strlen(right) : 0; + + clear(); + outs(VCLR_HEADER); + + if (title) + { + outs(VMSG_HDR_PREFIX); + outs(title); + outs(VMSG_HDR_POSTFIX); + w -= MACROSTRLEN(VMSG_HDR_PREFIX) + MACROSTRLEN(VMSG_HDR_POSTFIX); + w -= strlen(title); + } + + // determine if we can display right message, and + // if if we need to truncate mid. + if (szmid + szright > w) + szright = 0; + + if (szmid >= w) + szmid = w; + else { + int l = (MAX_COL-szmid)/2; + l -= (MAX_COL-w); + if (l > 0) + nblank(l), w -= l; + } + + if (szmid) { + outs(VCLR_HEADER_MID); + outns(mid, szmid); + outs(VCLR_HEADER); + } + nblank(w - szmid); + + if (szright) { + outs(VCLR_HEADER_RIGHT); + outs(right); + } + outs(ANSI_RESET "\n"); +} + +/** + * vs_hdr(title): 清空螢幕並輸出簡易的標題 + * + * @param title + */ +void +vs_hdr(const char *title) +{ + clear(); + outs(VCLR_HDR VMSG_HDR_PREFIX); + outs(title); + outs(VMSG_HDR_POSTFIX ANSI_RESET "\n"); +} + +/** + * vs_footer(caption, msg): 在螢幕底部印出格式化的 caption msg (不可含 ANSI 碼) + * + * @param caption 左邊的分類字串 + * @param msg 訊息字串, \t 後文字靠右、最後面會自動留一個空白。 + */ +void +vs_footer(const char *caption, const char *msg) +{ + int i = 0; + move(b_lines, 0); clrtoeol(); + + if (caption) + { + outs(VCLR_FOOTER_CAPTION); + outs(caption); + i += (*caption == ESC_CHR) ? + strlen_noansi(caption) : + strlen(caption); + } + + if (!msg) msg = ""; + outs(VCLR_FOOTER); + + while (*msg && i < SAFE_MAX_COL) + { + if (*msg == '(') + { + outs(VCLR_FOOTER_QUOTE); + } + else if (*msg == '\t') + { + // if we don't have enough space, ignore whole. + int l = strlen(++msg); + if (i + l > SAFE_MAX_COL) break; + l = SAFE_MAX_COL - l - i; + nblank(l); + i += l; + continue; + } + outc(*msg); i++; + if (*msg == ')') + outs(VCLR_FOOTER); + msg ++; + } + nblank(SAFE_MAX_COL-i); + outc(' '); + outs(ANSI_RESET); +} + +/** + * vs_cols_layout(cols, ws, n): 依據 cols (大小 n) 的定義計算適合的行寬於 ws + */ + +void +vs_cols_layout(const VCOL *cols, VCOLW *ws, int n) +{ + int i, tw; + VCOLPRI pri1 = cols[0].pri; + memset(ws, 0, sizeof(VCOLW) * n); + + // first run, calculate minimal size + for (i = 0, tw = 0; i < n; i++) + { + // drop any trailing if required + if (tw + cols[i].minw > MAX_COL) + break; + ws[i] = cols[i].minw; + tw += ws[i]; + } + + if (tw < MAX_COL) { + // calculate highest priorities + // (pri1 already set to col[0].pri) + for (i = 1; i < n; i++) + { + if (cols[i].pri > pri1) + pri1 = cols[i].pri; + } + } + + // try to iterate through all. + while (tw < MAX_COL) { + char run = 0; + + // also update pri2 here for next run. + VCOLPRI pri2 = cols[0].pri; + + for (i = 0; i < n; i++) + { + // if reach max, skip. + if (ws[i] >= cols[i].maxw) + continue; + + // lower priority, update pri2 and skip. + if (cols[i].pri < pri1) + { + if (cols[i].pri > pri2) + pri2 = cols[i].pri; + continue; + } + + // now increase fields + ws[i] ++; + if (++tw >= MAX_COL) break; + run ++; + } + + // if no more fields... + if (!run) { + if (pri1 <= pri2) // no more priorities + break; + pri1 = pri2; // try lower priority. + } + } +} + +/** + * vs_cols: 依照已經算好的欄位大小進行輸出 + */ +void +vs_cols(const VCOL *cols, const VCOLW *ws, int n, ...) +{ + int i = 0, w = 0; + char *s = NULL; + + va_list ap; + va_start(ap, n); + + for (i = 0; i < n; i++, cols++, ws++) + { + int flags = 0; + s = va_arg(ap, char*); + + // quick check input. + if (!s) + { + s = ""; + } + w = *ws; + + if (cols->attr) + outs(cols->attr); + + // build vfill flag + if (cols->flags.right_align) flags |= VFILL_RIGHT_ALIGN; + if (cols->flags.usewhole) flags |= VFILL_NO_BORDER; + + vfill(w, flags, s); + + if (cols->attr) + outs(ANSI_RESET); + } + va_end(ap); + + // end line + outs(ANSI_RESET "\n"); +} + +//////////////////////////////////////////////////////////////////////// +// DBCS Aware Helpers +//////////////////////////////////////////////////////////////////////// + +#ifdef DBCSAWARE +# define CHKDBCSTRAIL(_buf,_i) (ISDBCSAWARE() && DBCS_Status(_buf, _i) == DBCS_TRAILING) +#else // !DBCSAWARE +# define CHKDBCSTRAIL(buf,i) (0) +#endif // !DBCSAWARE + +//////////////////////////////////////////////////////////////////////// +// History Helpers +//////////////////////////////////////////////////////////////////////// +// +#define IH_MAX_ENTRIES (12) // buffer size = approx. 1k +#define IH_MIN_SIZE (2) // only keep string >= 2 bytes + +typedef struct { + int icurr; // current retrival pointer + int iappend; // new location to append + char buf[IH_MAX_ENTRIES][STRLEN]; +} InputHistory; + +static InputHistory ih; // everything intialized to zero. + +int +InputHistoryExists(const char *s) +{ + int i = 0; + + for (i = 0; i < IH_MAX_ENTRIES; i++) + if (strcmp(s, ih.buf[i]) == 0) + return i+1; + + return 0; +} + +int +InputHistoryAdd(const char *s) +{ + int i = 0; + int l = strlen(s); + + if (l < IH_MIN_SIZE) + return 0; + + i = InputHistoryExists(s); + if (i > 0) // found + { + i--; // i points to valid index + assert(i < IH_MAX_ENTRIES); + + // change order: just delete it. + ih.buf[i][0] = 0; + } + + // now append s. + strlcpy(ih.buf[ih.iappend], s, sizeof(ih.buf[ih.iappend])); + ih.iappend ++; + ih.iappend %= IH_MAX_ENTRIES; + ih.icurr = ih.iappend; + + return 1; +} + +static void +InputHistoryDelta(char *s, int sz, int d) +{ + int i, xcurr = 0; + for (i = 1; i <= IH_MAX_ENTRIES; i++) + { + xcurr = (ih.icurr+ IH_MAX_ENTRIES + d*i)%IH_MAX_ENTRIES; + if (ih.buf[xcurr][0]) + { + ih.icurr = xcurr; + + // copy buffer + strlcpy(s, ih.buf[ih.icurr], sz); + + // DBCS safe + i = strlen(s); + if (DBCS_Status(s, i) == DBCS_TRAILING) + s[i-1] = 0; + break; + } + } +} + +void +InputHistoryPrev(char *s, int sz) +{ + InputHistoryDelta(s, sz, -1); +} + +void +InputHistoryNext(char *s, int sz) +{ + InputHistoryDelta(s, sz, +1); +} + +//////////////////////////////////////////////////////////////////////// +// vget*: mini editbox +//////////////////////////////////////////////////////////////////////// + +static inline int +_vgetcbhandler(VGET_FCALLBACK cbptr, int *pabort, int c, VGET_RUNTIME *prt, void *instance) +{ + if (!cbptr) + return 0; + + switch(cbptr(c, prt, instance)) + { + case VGETCB_NONE: + assert( prt->icurr >= 0 && prt->icurr <= prt->iend && prt->iend <= prt->len ); + return 0; + + case VGETCB_NEXT: + assert( prt->icurr >= 0 && prt->icurr <= prt->iend && prt->iend <= prt->len ); + return 1; + + case VGETCB_END: + assert( prt->icurr >= 0 && prt->icurr <= prt->iend && prt->iend <= prt->len ); + *pabort = 1; + return 1; + + case VGETCB_ABORT: + assert( prt->icurr >= 0 && prt->icurr <= prt->iend && prt->iend <= prt->len ); + *pabort = 1; + prt->icurr = 0; + prt->iend = 0; + prt->buf[0] = 0; + return 1; + } + assert(0); // shall never reach here + return 0; +} + +int +vgetstring(char *_buf, int len, int flags, const char *defstr, const VGET_CALLBACKS *pcbs, void *instance) +{ + // rt.iend points to NUL address, and + // rt.icurr points to cursor. + int line, col; + int abort = 0; + int c; + char ismsgline = 0; + + // callback + VGET_CALLBACKS cb = {NULL}; + + // always use internal buffer to prevent temporary input issue. + char buf[STRLEN] = ""; // zero whole. + + // runtime structure + VGET_RUNTIME rt = { buf, len > STRLEN ? STRLEN : len }; + + // it is wrong to design input with larger buffer + // than STRLEN. Although we support large screen, + // inputting huge line will just make troubles... + if (len > STRLEN) len = STRLEN; + assert(len <= sizeof(buf) && len >= 2); + + // memset(buf, 0, len); + if (defstr && *defstr) + { + strlcpy(buf, defstr, len); + strip_ansi(buf, buf, STRIP_ALL); // safer... + rt.icurr = rt.iend = strlen(buf); + } + + // setup callbacks + if (pcbs) + cb = *pcbs; + + getyx(&line, &col); // now (line,col) is the beginning of our new fields. + + // XXX be compatible with traditional... + if (line == b_lines - msg_occupied) + ismsgline = 1; + + if (ismsgline) + msg_occupied ++; + + // main loop + while (!abort) + { + if (!(flags & VGET_NOECHO)) + { + // print current buffer + move(line, col); + clrtoeol(); + SOLVE_ANSI_CACHE(); + + if (!(flags & VGET_TRANSPARENT)) + outs(VCLR_INPUT_FIELD); // change color to prompt fields + + vfill(len, 0, buf); + + if (!(flags & VGET_TRANSPARENT)) + outs(ANSI_RESET); + + // move to cursor position + move(line, col+rt.icurr); + } else { + // to simulate the "clrtoeol" behavior... + // XXX make this call only once? or not? + clrtoeol(); + } + c = vkey(); + + // callback 1: peek + if (_vgetcbhandler(cb.peek, &abort, c, &rt, instance)) + continue; + + // standard key bindings + switch(c) { + // history navigation + case KEY_DOWN: case Ctrl('N'): + c = KEY_DOWN; + // let UP do the magic. + case KEY_UP: case Ctrl('P'): + if ((flags & VGET_NOECHO) || + (flags & VGET_DIGITS)) + { + bell(); + continue; + } + + // NOECHO is already checked... + if (!InputHistoryExists(buf)) + InputHistoryAdd(buf); + + if (c == KEY_DOWN) + InputHistoryNext(buf, len); + else + InputHistoryPrev(buf, len); + + rt.icurr = rt.iend = strlen(buf); + break; + + // exiting keys + case KEY_ENTER: + abort = 1; + break; + + case Ctrl('C'): + rt.icurr = rt.iend = 0; + buf[0] = 0; + buf[1] = c; + abort = 1; + break; + + // standard navigation + case KEY_HOME: case Ctrl('A'): + rt.icurr = 0; + break; + + case KEY_END: case Ctrl('E'): + rt.icurr = rt.iend; + break; + + case KEY_LEFT: case Ctrl('B'): + if (rt.icurr > 0) + rt.icurr--; + else + bell(); + if (rt.icurr > 0 && CHKDBCSTRAIL(buf, rt.icurr)) + rt.icurr--; + break; + + case KEY_RIGHT: case Ctrl('F'): + if (rt.icurr < rt.iend) + rt.icurr++; + else + bell(); + if (rt.icurr < rt.iend && CHKDBCSTRAIL(buf, rt.icurr)) + rt.icurr++; + break; + + // editing keys + case KEY_DEL: case Ctrl('D'): + if (rt.icurr+1 < rt.iend && CHKDBCSTRAIL(buf, rt.icurr+1)) { + // kill next one character. + memmove(buf+rt.icurr, buf+rt.icurr+1, rt.iend-rt.icurr); + rt.iend--; + } + if (rt.icurr < rt.iend) { + // kill next one character. + memmove(buf+rt.icurr, buf+rt.icurr+1, rt.iend-rt.icurr); + rt.iend--; + } + break; + + case KEY_BS: + if (rt.icurr > 0) { + // kill previous one charracter. + memmove(buf+rt.icurr-1, buf+rt.icurr, rt.iend-rt.icurr+1); + rt.icurr--; rt.iend--; + } else + bell(); + if (rt.icurr > 0 && CHKDBCSTRAIL(buf, rt.icurr)) { + // kill previous one charracter. + memmove(buf+rt.icurr-1, buf+rt.icurr, rt.iend-rt.icurr+1); + rt.icurr--; rt.iend--; + } + break; + + case Ctrl('Y'): + rt.icurr = 0; + // reuse Ctrl-K code + case Ctrl('K'): + rt.iend = rt.icurr; + buf[rt.iend] = 0; + break; + + // defaults + default: + + // content filter + if (c < ' ' || c >= 0xFF) + { + bell(); continue; + } + if ((flags & VGET_DIGITS) && + ( !isascii(c) || !isdigit(c))) + { + bell(); continue; + } + if (flags & VGET_LOWERCASE) + { + if (!isascii(c)) + { + bell(); continue; + } + c = tolower(c); + } + if (flags & VGET_ASCII_ONLY) + { + if (!isprint(c)) + { + bell(); continue; + } + } + + // size check + if(rt.iend+1 >= len) + { + bell(); continue; + } + + // prevent incomplete DBCS + if (c > 0x80 && num_in_buf() && + len - rt.iend < 3) // we need 3 for DBCS+NUL. + { + drop_input(); + bell(); + continue; + } + + // callback 2: data + if (_vgetcbhandler(cb.data, &abort, c, &rt, instance)) + continue; + + // size check again, due to data callback. + if(rt.iend+1 >= len) + { + bell(); continue; + } + + // add one character. + memmove(buf+rt.icurr+1, buf+rt.icurr, rt.iend-rt.icurr+1); + buf[rt.icurr++] = c; + rt.iend++; + + // callback 3: post + if (_vgetcbhandler(cb.post, &abort, c, &rt, instance)) + continue; + + break; + } + } + + assert(rt.iend >= 0 && rt.iend < len); + buf[rt.iend] = 0; + + // final filtering + if (rt.iend && (flags & VGET_LOWERCASE)) + buf[0] = tolower(buf[0]); + + // save the history except password mode + if (buf[0] && !(flags & VGET_NOECHO)) + InputHistoryAdd(buf); + + // copy buffer! + memcpy(_buf, buf, len); + + // XXX update screen display + if (ismsgline) + msg_occupied --; + + /* because some code then outs so change new line.*/ + move(line+1, 0); + + return rt.iend; +} + +int +vgets(char *buf, int len, int flags) +{ + return vgetstr(buf, len, flags, ""); +} + +int +vgetstr(char *buf, int len, int flags, const char *defstr) +{ + return vgetstring(buf, len, flags, defstr, NULL, NULL); +} + |