/* * window.c Very portable window routines. * Currently this code is used in _both_ the BBS * system and minicom. * * This file is part of the minicom communications package, * Copyright 1991-1996 Miquel van Smoorenburg. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * 01.01.98 dickey@clark.net: fix for a history window closing bug * fmg 8/20/97: Added support for Search of History Buffer */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "port.h" #include "minicom.h" #include "intl.h" /* Status line code on/off. */ #define ST_LINE 1 /* fmg 2/20/94 macros - Length of Macros */ #ifndef MAC_LEN #define MAC_LEN 257 #endif /* Don't want to include all header stuff for three prototypes from sysdep.c */ #ifdef __STDC__ int setcbreak(int); int wxgetch(void); void getrowcols(int *rows, int *cols); #else int setcbreak(); int wxgetch(); void getrowcols(); #endif #ifndef BBS #include "config.h" #endif #define BUFFERSIZE 2048 #define swap(x, y) { int d = (x); (x) = (y); (y) = d; } /* Terminal capabilities */ static const char *CM, *IS, *RS, *AC, *EA; static const char *ME, *SE, *UE, *AE; static const char *AS, *MB, *MD, *MR, *SO, *US; static const char *CE, *Al, *Dl, *AL, *DL; static const char *CS, *SF, *SR, *VB, *BL; static const char *VE, *VI, *KS, *KE; static const char *CD, *CL, *IC, *DC; static const char *CR, *NL; #ifdef ST_LINE static const char *TS, *FS, *DS; #endif /* Special characters */ static char D_UL; static char D_HOR; static char D_UR; static char D_LL; static char D_VER; static char D_LR; static char S_UL; static char S_HOR; static char S_UR; static char S_LL; static char S_VER; static char S_LR; static char _bufstart[BUFFERSIZE]; static char *_bufpos = _bufstart; static char *_buffend; static ELM *gmap; static char curattr = -1; static char curcolor = -1; static int curx = -1; static int cury = -1; static int _intern = 0; static int _curstype = CNORMAL; static int _has_am = 0; static int _mv_standout = 0; static ELM oldc; static int sflag = 0; int useattr = 1; int dirflush = 1; int usecolor = 0; WIN *stdwin; /* * The following is an external pointer to the termcap info. * If it's NOT zero then the main program has already * read the termcap for us. No sense in doing it twice. */ char *_tptr = NULL; int screen_ibmpc = 0; int screen_iso = 0; int w_init = 0; int use_status = 0; /* Turned on in main() */ /* Standard vt100 map (ac capability) */ static const char *def_ac = "+\273,\253aaffggjjkkllmmnnooqqssttuuvvwwxx"; #ifdef DEBUG /* * Debug to stdout */ int debug(char *s, ...) { char lala[80]; va_list ap; va_start(ap, s); vsnprintf(lala, sizeof(lala), s, ap); va_end(ap); write(2, lala, strlen(lala)); return 0; } #endif /* ===== Low level routines ===== */ /* * Flush the screen buffer */ void mc_wflush(void) { int todo, done; todo = _bufpos - _bufstart; _bufpos = _bufstart; while (todo > 0) { done = write(1, _bufpos, todo); if (done > 0) { todo -= done; _bufpos += done; } if (done < 0 && errno != EINTR) break; } _bufpos = _bufstart; } /* * Output a raw character to the screen */ static int outchar(int c) { *_bufpos++ = c; if (_bufpos >= _buffend) mc_wflush(); return 0; } /* * Output a raw string to the screen. */ static void outstr(const char *s) { tputs(s, 1, outchar); } /* * Turn off all attributes */ static void _attroff(void) { if (ME) outstr(ME); else { if (SE) outstr(SE); if (UE) outstr(UE); } if (AE) outstr(AE); } /* * Turn some attributes on */ static void _attron(char attr) { if (attr & XA_REVERSE && MR) outstr(MR); if (attr & XA_BOLD && MD) outstr(MD); if (attr & XA_STANDOUT && SO) outstr(SO); if (attr & XA_UNDERLINE && US) outstr(US); if (attr & XA_BLINK && MB) outstr(MB); if (attr & XA_ALTCHARSET && AS) outstr(AS); } /* * Set the colors */ static void _colson(char color) { char buf[12]; sprintf(buf, "\033[%d;%dm", COLFG(color) + 30, COLBG(color) + 40); outstr(buf); } /* * Set global attributes, if different. */ static void _setattr(char attr, char color) { if (!useattr) return; if (!usecolor) { curcolor = color; if (attr == curattr) return; curattr = attr; _attroff(); _attron(attr); return; } if (attr == curattr && color == curcolor) return; _attroff(); _colson(color); _attron(attr); curattr = attr; curcolor = color; } /* * Goto (x, y) in stdwin */ static void _gotoxy(int x, int y) { int oldattr = -1; #ifdef ST_LINE int tmp; /* Sanity check. */ if (x >= COLS || y > LINES || (x == curx && y == cury)) return; if (use_status) { /* Leaving status line? */ if (cury == LINES && y < cury) { outstr(FS); /* Re-set attributes. */ tmp = curattr; curattr = -1; _setattr(tmp, curcolor); outstr(tgoto(CM, x, y)); curx = x; cury = y; return; } /* Writing on status line? */ else if (y == LINES) { /* From normal screen? */ if (cury < y) { outstr(tgoto(TS, x, x)); curx = x; cury = y; /* Set the right attributes. */ tmp = curattr; curattr = -1; _setattr(tmp, curcolor); return; } } } #else /* Sanity check. */ if (x >= COLS || y >= LINES || (x == curx && y == cury)) { # if 0 if (x >= COLS || y >= LINES) fprintf(stderr, "OOPS: (x, y) == (%d, %d)\n", COLS, LINES); # endif return; } #endif if (!_mv_standout && curattr != XA_NORMAL) { oldattr = curattr; _setattr(XA_NORMAL, curcolor); } if (CR != NULL && y == cury && x == 0) outstr(CR); #if 0 /* Hmm, sometimes NL only works in the first column */ else if (NL != NULL && x == curx && y == cury + 1) outstr(NL); #else else if (NL != NULL && x == 0 && x == curx && y == cury + 1) outstr(NL); #endif else if (BC != NULL && y == cury && x == curx - 1) outstr(BC); else outstr(tgoto(CM, x, y)); curx = x; cury = y; if (oldattr != -1) _setattr(oldattr, curcolor); } /* * Write a character in stdwin at x, y with attr & color * 'doit' can be -1: only write to screen, not to memory * 0: only write to memory, not to screen * 1: write to both screen and memory */ static void _write(wchar_t c, int doit, int x, int y, char attr, char color) { ELM *e; /* If the terminal has automatic margins, we can't write to the * last line, last character. After scrolling, this "invisible" * character is automatically restored. */ if (_has_am && y >= LINES - 1 && x >= COLS - 1) { doit = 0; sflag = 1; oldc.value = c; oldc.attr = attr; oldc.color = color; } #ifdef ST_LINE if (x < COLS && y <= LINES) #else if (x < COLS && y < LINES) #endif { if (doit != 0) { static int x0 = -1, y0 = -1, c0 = 0; static char attr0, color0; if (x!=x0+1 || y!=y0 || attr!=attr0 || color!=color0 || !(c0&128)) { _gotoxy(x, y); _setattr(attr, color); } x0 = x; y0 = y; attr0 = attr; color0 = color; c0 = c; if ((attr & XA_ALTCHARSET) != 0) outchar((char)c); else { char buf[MB_LEN_MAX]; size_t i, len; len = one_wctomb(buf, c); for (i = 0; i < len; i++) outchar(buf[i]); } curx++; } if (doit >= 0) { e = &gmap[x + y * COLS]; e->value = c; e->attr = attr; e->color = color; } } } /* * Set cursor type. */ static void _cursor(int type) { _curstype = type; if (type == CNORMAL && VE != NULL) outstr(VE); if (type == CNONE && VE != NULL && VI != NULL) outstr(VI); } /* ==== High level routines ==== */ #if 0 /* This code is functional, but not yet used. * It might be one day.... */ /* * Resize a window */ void mc_wresize(WIN *win, int lines, int cols) { int x, y; ELM *oldmap, *newmap, *e, *n; if ((newmap = malloc((lines + 1) * cols * sizeof(ELM))) == NULL) return; if (win == stdwin) oldmap = gmap; else oldmap = win->map; for (y = 0; y < lines; y++) for (x = 0; x < cols; x++) { n = &newmap[y + x * cols]; if (x < win->xs && y < win->ys) { e = &oldmap[y + x * COLS]; n->value = e->value; n->color = e->color; n->attr = e->attr; } else { n->value = ' '; n->color = win->color; n->attr = win->attr; } } if (win->sy2 == win->y2) win->sy2 = win->y1 + lines - 1; win->y2 = win->y1 + lines - 1; win->ys = lines; win->xs = cols; free(oldmap); if (win == stdwin) { gmap = newmap; LINES = lines; COLS = cols; } else win->map = newmap; } #endif /* * Create a new window. */ WIN *mc_wopen(int x1, int y1, int x2, int y2, int border, int attr, int fg, int bg, int direct, int histlines, int doclr) { WIN *w; ELM *e; int bytes; int x, y; int color; int offs; int xattr; if ((w = malloc(sizeof(WIN))) == NULL) return w; offs = (border != BNONE); if (!screen_ibmpc && AS) xattr = attr | XA_ALTCHARSET; else xattr = attr; if (x1 > x2) swap(x1, x2); if (y1 > y2) swap(y1, y2); if (x1 < offs) x1 = offs; if (y1 < offs) y1 = offs; #if 0 if (x2 >= COLS - offs) x2 = COLS - offs - 1; if (y2 >= LINES - offs) y2 = LINES - offs - 1; #endif w->xs = x2 - x1 + 1; w->ys = y2 - y1 + 1; w->x1 = x1; w->x2 = x2; w->y1 = w->sy1 = y1; w->y2 = w->sy2 = y2; w->doscroll = 1; w->border = border; w->cursor = CNORMAL; w->attr = attr; w->autocr = 1; w->wrap = 1; color = w->color = COLATTR(fg, bg); w->curx = 0; w->cury = 0; w->o_curx = curx; w->o_cury = cury; w->o_attr = curattr; w->o_color = curcolor; w->o_cursor = _curstype; w->direct = direct; if (border != BNONE) { x1--; x2++; y1--; y2++; } /* Store whatever we are overlapping */ bytes = (y2 - y1 + 1) * (x2 - x1 + 1) * sizeof(ELM) + 100; if ((e = malloc(bytes)) == NULL) { free(w); return NULL; } w->map = e; /* How many bytes is one line */ bytes = (x2 - x1 + 1) * sizeof(ELM); /* Loop */ for (y = y1; y <= y2; y++) { memcpy(e, gmap + COLS * y + x1, bytes); e += (x2 - x1 + 1); } /* Do we want history? */ w->histline = w->histlines = 0; w->histbuf = NULL; if (histlines) { /* Reserve some memory. */ bytes = w->xs * histlines * sizeof(ELM); if ((w->histbuf = malloc(bytes)) == NULL) { free(w->map); free(w); return NULL; } w->histlines = histlines; /* Clear the history buf. */ e = w->histbuf; for (y = 0; y < w->xs * histlines; y++) { e->value = ' '; e->attr = attr; e->color = color; e++; } } /* And draw the window */ if (border) { _write(border == BSINGLE ? S_UL : D_UL, w->direct, x1, y1, xattr, color); for (x = x1 + 1; x < x2; x++) _write(border == BSINGLE ? S_HOR : D_HOR, w->direct, x, y1, xattr, color); _write(border == BSINGLE ? S_UR : D_UR, w->direct, x2, y1, xattr, color); for (y = y1 + 1; y < y2; y++) { _write(border == BSINGLE ? S_VER : D_VER, w->direct, x1, y, xattr, color); for (x = x1 + 1; x < x2; x++) _write(' ', w->direct, x, y, attr, color); _write(border == BSINGLE ? S_VER : D_VER, w->direct, x2, y, xattr, color); } _write(border == BSINGLE ? S_LL : D_LL, w->direct, x1, y2, xattr, color); for (x = x1 + 1; x < x2; x++) _write(border == BSINGLE ? S_HOR : D_HOR, w->direct, x, y2, xattr, color); _write(border == BSINGLE ? S_LR : D_LR, w->direct, x2, y2, xattr, color); if (w->direct) _gotoxy(x1 + 1, y1 + 1); } else if (doclr) mc_winclr(w); mc_wcursor(w, CNORMAL); if (w->direct) mc_wflush(); return w; } /* * Close a window. */ void mc_wclose(WIN *win, int replace) { ELM *e; int x, y; if (!win) return; if (win == stdwin) { win_end(); return; } e = win->map; if (win->border) { win->x1--; win->x2++; win->y1--; win->y2++; } mc_wcursor(win, win->o_cursor); if (replace) { for (y = win->y1; y <= win->y2; y++) { /* temporal way to avoid 'half-character' problem */ /* in multibyte characters such as Japanese -- from here */ ELM *g; g = gmap + (y * stdwin->xs); for (x = 0 ; x < win->x1; x++) { _write(g->value, 1, x, y, g->attr, g->color); g++; } /* to here */ for (x = win->x1; x <= win->x2; x++) { _write(e->value, 1, x, y, e->attr, e->color); e++; } } _gotoxy(win->o_curx, win->o_cury); _setattr(win->o_attr, win->o_color); } free(win->map); if (win->histbuf) free(win->histbuf); free(win); /* 1.1.98 dickey@clark.net */ mc_wflush(); } static int oldx, oldy; static int ocursor; /* * Clear screen & restore keyboard modes */ void mc_wleave(void) { oldx = curx; oldy = cury; ocursor = _curstype; setcbreak(0); /* Normal */ _gotoxy(0, LINES - 1); _setattr(XA_NORMAL, COLATTR(WHITE, BLACK)); _cursor(CNORMAL); if (CL != NULL) outstr(CL); else outstr("\n"); #ifdef ST_LINE if (DS) outstr(DS); #endif if (KE != NULL) outstr(KE); if (RS != NULL) outstr(RS); mc_wflush(); } void mc_wreturn(void) { int x, y; ELM *e; curattr = -1; curcolor = -1; setcbreak(1); /* Cbreak, no echo */ if (IS != NULL) outstr(IS); /* Initialization string */ if (EA != NULL) outstr(EA); /* Graphics init. */ if (KS != NULL) outstr(KS); /* Keypad mode */ _gotoxy(0, 0); _cursor(ocursor); e = gmap; for (y = 0; y value, -1, x, y, e->attr, e->color); e++; } } _gotoxy(oldx, oldy); mc_wflush(); } /* * Redraw the whole window. */ void mc_wredraw(WIN *w, int newdirect) { int minx, maxx, miny, maxy; ELM *e; int x, y; int addcnt; minx = w->x1; maxx = w->x2; miny = w->y1; maxy = w->y2; addcnt = stdwin->xs - w->xs; if (w->border) { minx--; maxx++; miny--; maxy++; addcnt -= 2; } _gotoxy(minx, miny); _cursor(CNONE); e = gmap + (miny * stdwin->xs) + minx; for (y = miny; y <= maxy; y++) { for(x = minx; x <= maxx; x++) { _write(e->value, -1, x, y, e->attr, e->color); e++; } e += addcnt; } _gotoxy(w->x1 + w->curx, w->y1 + w->cury); _cursor(w->cursor); mc_wflush(); w->direct = newdirect; } /* * Clear to end of line, low level. */ static int _wclreol(WIN *w) { int x; int doit = 1; int y; y = w->cury + w->y1; if (w->direct && (w->x2 == COLS - 1) && CE) { _gotoxy(w->curx + w->x1, y); _setattr(w->attr, w->color); outstr(CE); doit = 0; } for (x = w->curx + w->x1; x <= w->x2; x++) { _write(' ', (w->direct && doit) ? 1 : 0, x, y, w->attr, w->color); } return doit; } /* * Scroll a window. */ void mc_wscroll(WIN *win, int dir) { ELM *e, *f; char *src, *dst; int x, y; int doit = 1; int ocurx, fs = 0, len; int phys_scr = 0; /* * If the window *is* the physical screen, we can scroll very simple. * This improves performance on slow screens (eg ATARI ST) dramatically. */ if (win->direct && SF != NULL && (dir == S_UP || SR != NULL) && (LINES == win->sy2 - win->sy1 + 1)) { doit = 0; phys_scr = 1; _setattr(win->attr, win->color); if (dir == S_UP) { _gotoxy(0, LINES - 1); outstr(SF); } else { _gotoxy(0, 0); outstr(SR); } } /* * If the window is as wide as the physical screen, we can * scroll it with insert/delete line (or set scroll region - vt100!) */ else if (win->direct && win->xs == COLS && ((CS != NULL && SF != NULL && SR != NULL) || (Dl != NULL && Al != NULL))) { doit = 0; phys_scr = 1; _setattr(win->attr, win->color); if (CS != NULL && SF != NULL && SR != NULL) { /* Scrolling Region */ /* If the scroll region we want to initialize already is as * big as the physical screen, we don't _have_ to * initialize it. */ if (win->sy2 == LINES - 1 && win->sy1 == 0) fs = 1; if (!fs) { outstr(tgoto(CS, win->sy2, win->sy1)); cury = 0; } if (dir == S_UP) { _gotoxy(0, win->sy2); outstr(SF); } else { _gotoxy(0, win->sy1); outstr(SR); } if (!fs) { outstr(tgoto(CS, LINES - 1, 0)); cury = 0; } _gotoxy(0, win->sy2); } else { /* Use insert/delete line */ if (dir == S_UP) { _gotoxy(0, win->sy1); outstr(Dl); _gotoxy(0, win->sy2); outstr(Al); } else { _gotoxy(0, win->sy2); outstr(Dl); _gotoxy(0, win->sy1); outstr(Al); } } } /* If a terminal has automatic margins, we can't write * to the lower right. After scrolling we have to restore * the non-visible character that is now visible. */ if (sflag && win->sy2 == (LINES - 1) && win->sy1 != win->sy2) { if (dir == S_UP) { _write(oldc.value, 1, COLS - 1, LINES - 2, oldc.attr, oldc.color); } sflag = 0; } ocurx = win->curx; /* If this window has a history buf, see if we want to use it. */ if (win->histbuf && dir == S_UP && win->sy2 == win->y2 && win->sy1 == win->y1) { /* Calculate screen buffer */ e = gmap + win->y1 * COLS + win->x1; /* Calculate history buffer */ f = win->histbuf + (win->xs * win->histline); /* Copy line from screen to history buffer */ memcpy((char *)f, (char *)e, win->xs * sizeof(ELM)); /* Position the next line in the history buffer */ win->histline++; if (win->histline >= win->histlines) win->histline = 0; } /* If the window is screen-wide and has no border, there * is a much simpler & FASTER way of scrolling the memory image !! */ if (phys_scr) { len = (win->sy2 - win->sy1) * win->xs * sizeof(ELM); if (dir == S_UP) { dst = (char *)&gmap[0]; /* First line */ src = (char *)&gmap[win->xs]; /* Second line */ win->cury = win->sy2 - win->y1; } else { src = (char *)&gmap[0]; /* First line */ dst = (char *)&gmap[win->xs]; /* Second line */ win->cury = win->sy1 - win->y1; } /* memmove copies len bytes from src to dst, even if the * objects overlap. */ fflush(stdout); #ifdef _SYSV memcpy((char *)dst, (char *)src, len); #else # ifdef _BSD43 bcopy((char *)src, (char *)dst, len); # else memmove((char *)dst, (char *)src, len); # endif #endif } else { /* Now scroll the memory image. */ if (dir == S_UP) { for (y = win->sy1 + 1; y <= win->sy2; y++) { e = gmap + y * COLS + win->x1; for (x = win->x1; x <= win->x2; x++) { _write(e->value, win->direct && doit, x, y - 1, e->attr, e->color); e++; } } win->curx = 0; win->cury = win->sy2 - win->y1; if (doit) _wclreol(win); } else { for (y = win->sy2 - 1; y >= win->sy1; y--) { e = gmap + y * COLS + win->x1; for (x = win->x1; x <= win->x2; x++) { _write(e->value, win->direct && doit, x, y + 1, e->attr, e->color); e++; } } win->curx = 0; win->cury = win->sy1 - win->y1; if (doit) _wclreol(win); } } win->curx = ocurx; if (!doit) for (x = win->x1; x <= win->x2; x++) _write(' ', 0, x, win->y1 + win->cury, win->attr, win->color); if (!_intern && win->direct) _gotoxy(win->x1 + win->curx, win->y1 + win->cury); if (dirflush && !_intern && win->direct) mc_wflush(); } /* * Locate the cursor in a window. */ void mc_wlocate(WIN *win, int x, int y) { if (x < 0) x = 0; if (y < 0) y = 0; if (x >= win->xs) x = win->xs - 1; if (y >= win->ys) y = win->ys - 1; win->curx = x; win->cury = y; if (win->direct) _gotoxy(win->x1 + x, win->y1 + y); if (dirflush) mc_wflush(); } /* * Print a character in a window. */ void mc_wputc(WIN *win, wchar_t c) { int mv = 0; switch(c) { case '\r': win->curx = 0; mv++; break; case '\b': if (win->curx == 0) break; win->curx--; mv++; break; case '\007': mc_wbell(); break; case '\t': do { mc_wputc(win, ' '); /* Recursion! */ } while (win->curx % 8); break; case '\n': if (win->autocr) win->curx = 0; /* FALLTHRU */ default: /* See if we need to scroll/move. (vt100 behaviour!) */ if (c == '\n' || (win->curx >= win->xs && win->wrap)) { if (c != '\n') win->curx = 0; win->cury++; mv++; if (win->cury == win->sy2 - win->y1 + 1) { if (win->doscroll) mc_wscroll(win, S_UP); else win->cury = win->sy1 - win->y1; } if (win->cury >= win->ys) win->cury = win->ys - 1; } /* Now write the character. */ if (c != '\n') { if (!win->wrap && win->curx >= win->xs) c = '>'; _write(c, win->direct, win->curx + win->x1, win->cury + win->y1, win->attr, win->color); if (++win->curx >= win->xs && !win->wrap) { win->curx--; curx = 0; /* Force to move */ mv++; } } break; } if (mv && win->direct) _gotoxy(win->x1 + win->curx, win->y1 + win->cury); if (win->direct && dirflush && !_intern) mc_wflush(); } /* Draw one line in a window */ void mc_wdrawelm(WIN *w, int y, ELM *e) { int x; /* MARK updated 02/17/94 - Fixes bug, to do all 80 cols, not 79 cols */ for (x = w->x1; x <= w->x2; x++) { _write(e->value, w->direct, x, y + w->y1, e->attr, e->color); /*y + w->y1, XA_NORMAL, e->color);*/ e++; } } /* * fmg 8/20/97 * 'accumulate' one line of ELM's into a string * WHY: need this in search function to see if line contains search pattern */ void mc_wdrawelm_var(WIN *w, ELM *e, wchar_t *buf) { int x, c = 0; /* MARK updated 02/17/94 - Fixes bug, to do all 80 cols, not 79 cols */ for (x = w->x1; x <= w->x2; x++) { buf[c++] = e->value; e++; } } /* * fmg 8/20/97 * 'draw' one line of ELM's in a window INVERTED (text-mode-wise) * WHY: need this in search function to see if line contains search pattern */ void mc_wdrawelm_inverse(WIN *w, int y, ELM *e) { int x; /* MARK updated 02/17/94 - Fixes bug, to do all 80 cols, not 79 cols */ /* filipg 8/19/97: this will BOLD-up the line */ /* first position */ x = w->x1; _write(e->value, w->direct, x, y + w->y1, XA_NORMAL, e->color); e++; /* everything in the middle will be BLINK */ for (x = w->x1 + 1; x <= w->x2 - 1; x++) { _write(e->value, w->direct, x, y + w->y1, XA_BOLD, WHITE); e++; } /* last position */ x = w->x2; _write(e->value, w->direct, x, y + w->y1, XA_NORMAL, e->color); } /* * Print a string in a window. */ void mc_wputs(WIN *win, const char *s) { _intern = 1; while (*s) { wchar_t wc; s += one_mbtowc(&wc, s, MB_LEN_MAX); mc_wputc(win, wc); } if (dirflush && win->direct) mc_wflush(); _intern = 0; } /* * Print a formatted string in a window. * Should return string length - but who cares. */ int mc_wprintf(WIN *win, const char *fmt, ...) { char buf[160]; va_list va; va_start(va, fmt); vsnprintf(buf, sizeof(buf), fmt, va); va_end(va); mc_wputs(win, buf); return 0; } /* * Sound a bell. */ void mc_wbell(void) { if (BL != NULL) outstr(BL); else if (VB != NULL) outstr(VB); else outchar('\007'); mc_wflush(); } /* * Set cursor type. */ void mc_wcursor(WIN *win, int type) { win->cursor = type; if (win->direct) { _cursor(type); if (dirflush) mc_wflush(); } } void mc_wtitle(WIN *w, int pos, const char *s) { int x = 0; if (w->border == BNONE) return; if (pos == TLEFT) x = w->x1; if (pos == TRIGHT) x = w->x2 - mbswidth(s) - 1; if (pos == TMID) x = w->x1 + (w->xs - mbswidth(s)) / 2 - 1; if (x < w->x1) x = w->x1; if (x < w->x2) _write('[', w->direct, x++, w->y1 - 1, w->attr, w->color); while (*s && x <= w->x2) { wchar_t wc; s += one_mbtowc(&wc, s, MB_LEN_MAX); _write(wc, w->direct, x++, w->y1 - 1, w->attr, w->color); } if (x <= w->x2) _write(']', w->direct, x++, w->y1 - 1, w->attr, w->color); if (w->direct) { _gotoxy(w->x1 + w->curx, w->y1 + w->cury); if (dirflush) mc_wflush(); } } /* ==== Menu Functions ==== */ /* * Change attributes of one line of a window. */ void mc_wcurbar(WIN *w, int y, int attr) { ELM *e; int x; y += w->y1; e = gmap + y * COLS + w->x1; /* If we can't do reverse, just put a '>' in front of * the line. We only support XA_NORMAL & XA_REVERSE. */ if (!useattr || MR == NULL) { if (attr & XA_REVERSE) x = '>'; else x = ' '; _write(x, w->direct, w->x1, y, attr, e->color); } else { for (x = w->x1; x <= w->x2; x++) { _write(e->value, w->direct, x, y, attr, e->color); e++; } } if ((VI == NULL || _curstype == CNORMAL) && w->direct) _gotoxy(w->x1, y); if (w->direct) mc_wflush(); } /* * mc_wselect - select one of many choices. */ int mc_wselect(int x, int y, const char *const *choices, void (*const *funlist)(void), const char *title, int attr, int fg, int bg) { const char *const *a = choices; unsigned int len = 0; int count = 0; int cur = 0; int c; WIN *w; int high_on = XA_REVERSE | attr; int high_off = attr; /* first count how many, and max. width. */ while (*a != NULL) { count++; if (mbswidth(_(*a)) > len) len = mbswidth(_(*a)); a++; } if (title != NULL && mbswidth(title) + 2 > len) len = mbswidth(title) + 2; if (attr & XA_REVERSE) { high_on = attr & ~XA_REVERSE; high_off = attr; } if ((w = mc_wopen(x, y, x + len + 2, y + count - 1, BDOUBLE, attr, fg, bg, 0, 0, 0)) == NULL) return -1; mc_wcursor(w, CNONE); if (title != NULL) mc_wtitle(w, TMID, title); for (c = 0; c < count; c++) mc_wprintf(w, " %s%s", _(choices[c]), c == count - 1 ? "" : "\n"); mc_wcurbar(w, cur, high_on); mc_wredraw(w, 1); while (1) { while ((c = wxgetch()) != 27 && c != '\n' && c!= '\r' && c != ' ') { if (c == K_UP || c == K_DN || c == 'j' || c == 'k' || c == K_HOME || c == K_END) mc_wcurbar(w, cur, high_off); switch (c) { case K_UP: case 'k': cur--; if (cur < 0) cur = count - 1; break; case K_DN: case 'j': cur++; if (cur >= count) cur = 0; break; case K_HOME: cur = 0; break; case K_END: cur = count - 1; break; } if (c == K_UP || c == K_DN || c == 'j' || c == 'k' || c == K_HOME || c == K_END) mc_wcurbar(w, cur, high_on); } mc_wcursor(w, CNORMAL); if (c == ' ' || c == 27) { mc_wclose(w, 1); return 0; } if (funlist == NULL || funlist[cur] == NULL) { mc_wclose(w, 1); return cur + 1; } (*funlist[cur])(); mc_wcursor(w, CNONE); } } /* ==== Clearing functions ==== */ /* * Clear characters. */ void mc_wclrch(WIN *w, int n) { int x, y, x_end; y = w->cury + w->y1; x = x_end = w->curx + w->x1; x_end += n - 1; if (x_end > w->x2) x_end = w->x2; if (w->direct) _gotoxy(w->x1, y); for ( ; x <= x_end; x++) _write(' ', w->direct, x, y, w->attr, w->color); if (w->direct && dirflush) mc_wflush(); } /* * Clear entire line. */ void mc_wclrel(WIN *w) { int ocurx = w->curx; w->curx = 0; _wclreol(w); w->curx = ocurx; mc_wlocate(w, ocurx, w->cury); } /* * Clear to end of line. */ void mc_wclreol(WIN *w) { if (_wclreol(w) && w->direct) _gotoxy(w->x1 + w->curx, w->y1 + w->cury); if (dirflush) mc_wflush(); } /* * Clear to begin of line */ void mc_wclrbol(WIN *w) { int x, y, n; y = w->cury + w->y1; if (w->direct) _gotoxy(w->x1, y); n = w->x1 + w->curx; if (n > w->x2) n = w->x2; for (x = w->x1; x <= n; x++) _write(' ', w->direct, x, y, w->attr, w->color); if (w->direct) { _gotoxy(n, y); if (dirflush) mc_wflush(); } } /* * Clear to end of screen */ void mc_wclreos(WIN *w) { int y; int ocurx, ocury; ocurx = w->curx; ocury = w->cury; w->curx = 0; for (y = w->cury + 1; y <= w->y2 - w->y1; y++) { w->cury = y; _wclreol(w); } w->curx = ocurx; w->cury = ocury; if (_wclreol(w) && w->direct) _gotoxy(w->x1 + w->curx, w->y1 + w->cury); if (dirflush && w->direct) mc_wflush(); } /* * Clear to begin of screen. */ void mc_wclrbos(WIN *w) { int ocurx, ocury; int y; ocurx = w->curx; ocury = w->cury; w->curx = 0; for (y = 0; y < ocury; y++) { w->cury = y; _wclreol(w); } w->curx = ocurx; w->cury = ocury; mc_wclrbol(w); } /* * Clear a window. */ void mc_winclr(WIN *w) { int y; int olddir = w->direct; ELM *e, *f; int i; int m; /* If this window has history, save the image. */ if (w->histbuf) { /* MARK updated 02/17/95 - Scan backwards from the bottom of the */ /* window for the first non-empty line. We should save all other */ /* blank lines inside screen, since some nice BBS ANSI menus */ /* contains them for cosmetic purposes or as separators. */ for (m = w->y2; m >= w->y1; m--) { /* Start of this line in the global map. */ e = gmap + m * COLS + w->x1; /* Quick check to see if line is empty. */ for (i = 0; i < w->xs; i++) if (e[i].value != ' ') break; if (i != w->xs) break; /* Non empty line */ } /* Copy window into history buffer line-by-line. */ for (y = w->y1; y <= m; y++) { /* Start of this line in the global map. */ e = gmap + y * COLS + w->x1; /* Now copy this line. */ f = w->histbuf + (w->xs * w->histline); /* History buffer */ memcpy((char *)f, (char *)e, w->xs * sizeof(ELM)); w->histline++; if (w->histline >= w->histlines) w->histline = 0; } } _setattr(w->attr, w->color); w->curx = 0; if (CL && w->y1 == 0 && w->y2 == LINES-1 && w->x1 == 0 && w->x2 == COLS-1) { w->direct = 0; curx = 0; cury = 0; outstr(CL); } for (y = w->ys - 1; y >= 0; y--) { w->cury = y; _wclreol(w); } w->direct = olddir; _gotoxy(w->x1, w->y1); if (dirflush) mc_wflush(); } void mc_clear_window_simple(WIN *w) { int x = 0, y = 0; _colson(us->color); _gotoxy(0, 0); for (; y <= w->ys; ++y) for (; x <= w->xs; ++x) _write(' ', 1, x, y, XA_NORMAL, us->color); _gotoxy(0, 0); } /* ==== Insert / Delete functions ==== */ void mc_winsline(WIN *w) { int osy1, osy2; osy1 = w->sy1; osy2 = w->sy2; w->sy1 = w->y1 + w->cury; w->sy2 = w->y2; if (w->sy1 < osy1) w->sy1 = osy1; if (w->sy2 > osy2) w->sy2 = osy2; mc_wscroll(w, S_DOWN); w->sy1 = osy1; w->sy2 = osy2; } void mc_wdelline(WIN *w) { int osy1, osy2; int ocury; ocury = w->cury; osy1 = w->sy1; osy2 = w->sy2; w->sy1 = w->y1 + w->cury; w->sy2 = w->y2; if (w->sy1 < osy1) w->sy1 = osy1; if (w->sy2 > osy2) w->sy2 = osy2; _intern = 1; mc_wscroll(w, S_UP); _intern = 0; mc_wlocate(w, 0, ocury); w->sy1 = osy1; w->sy2 = osy2; } /* * Insert a space at cursor position. */ void mc_winschar2(WIN *w, wchar_t c, int move) { int y; int x; int doit = 1; ELM *buf, *e; int len, odir; int oldx; /* See if we need to insert. */ if (c == 0 || wcschr(L"\r\n\t\b\007", c) || w->curx >= w->xs - 1) { mc_wputc(w, c); return; } odir = w->direct; if (w->xs == COLS && IC != NULL) { /* We can use the insert character capability. */ if (w->direct) outstr(IC); /* No need to draw the character if it's a space. */ if (c == ' ') w->direct = 0; /* We don't need to draw the new line at all. */ doit = 0; } /* Get the rest of line into buffer */ y = w->y1 + w->cury; x = w->x1 + w->curx; oldx = w->curx; len = w->xs - w->curx; buf = malloc(sizeof(ELM) * len); if (!buf) return; /* Umm... */ memcpy(buf, gmap + COLS * y + x, sizeof(ELM) * len); /* Now, put the new character on screen. */ mc_wputc(w, c); if (!move) w->curx = oldx; /* Write buffer to screen */ e = buf; for (++x; x <= w->x2; x++) { _write(e->value, doit && w->direct, x, y, e->attr, e->color); e++; } free(buf); w->direct = odir; mc_wlocate(w, w->curx, w->cury); } void mc_winschar(WIN *w) { mc_winschar2(w, ' ', 0); } /* * Delete character under the cursor. */ void mc_wdelchar(WIN *w) { int x, y; int doit = 1; ELM *e; x = w->x1 + w->curx; y = w->y1 + w->cury; if (w->direct && w->xs == COLS && DC != NULL) { /*_gotoxy(x - 1, y);*/ _gotoxy(x, y); outstr(DC); doit = 0; } e = gmap + y * COLS + x + 1; for (; x < w->x2; x++) { _write(e->value, doit && w->direct, x, y, e->attr, e->color); e++; } _write(' ', doit && w->direct, x, y, w->attr, w->color); mc_wlocate(w, w->curx, w->cury); } /* ============= Support: edit a line on the screen. ============ */ /* Redraw the line we are editing. */ static void lredraw(WIN *w, int x, int y, wchar_t *s, int len) { int i, f; i = 0; mc_wlocate(w, x, y); for (f = 0; f < len; f++) { if (s[f] == 0) i++; mc_wputc(w, i ? ' ' : s[f]); } } #if MAC_LEN > 256 # define BUFLEN MAC_LEN #else # define BUFLEN 256 #endif /* mc_wgetwcs - edit one line in a window. */ int mc_wgetwcs(WIN *w, wchar_t *s, int linelen, int maxlen) { int c; int idx; int offs = 0; int f, st = 0, i; wchar_t buf[BUFLEN]; int quit = 0; int once = 1; int x, y, r; int direct = dirflush; int delete = 1; x = w->curx; y = w->cury; i = w->xs - x; if (linelen >= i - 1) linelen = i - 1; /* We assume the line has already been drawn on the screen. */ if ((idx = wcslen(s)) > linelen) idx = linelen; wcsncpy(buf, s, ARRAY_SIZE(buf)); mc_wlocate(w, x + idx, y); dirflush = 0; mc_wflush(); while (!quit) { if (once) { c = K_END; once--; } else { c = wxgetch(); if (c > 255 || c == K_BS || c == K_DEL) delete = 0; } switch(c) { case '\r': case '\n': st = 0; quit = 1; break; case K_ESC: /* Exit without changing. */ mc_wlocate(w, x, y); lredraw(w, x, y, s, linelen); mc_wflush(); st = -1; quit = 1; break; case K_HOME: /* Home */ r = offs > 0; offs = 0; idx = 0; if (r) lredraw(w, x, y, buf, linelen); mc_wlocate(w, x, y); mc_wflush(); break; case K_END: /* End of line. */ idx = wcslen(buf); r = 0; while (idx - offs - 3 > linelen) { r = 1; offs += 4; } if (r) lredraw(w, x, y, buf + offs, linelen); mc_wlocate(w, x + idx - offs, y); mc_wflush(); break; case K_LT: /* Cursor left. */ case K_BS: /* Backspace is first left, then DEL. */ if (idx == 0) break; idx--; if (idx < offs) { offs -= 4; /*if (c == K_LT) FIXME? */ lredraw(w, x, y, buf + offs, linelen); } if (c == K_LT) { mc_wlocate(w, x + idx - offs, y); mc_wflush(); break; } /* FALLTHRU */ case K_DEL: /* Delete character under cursor. */ if (buf[idx] == 0) break; for (f = idx; buf[f]; f++) buf[f] = buf[f+1]; lredraw(w, x + idx - offs, y, buf + idx, linelen - (idx - offs)); mc_wlocate(w, x + idx - offs, y); mc_wflush(); break; case K_RT: if (buf[idx] == 0) break; idx++; if (idx - offs > linelen) { offs += 4; lredraw(w, x, y, buf + offs, linelen); } mc_wlocate(w, x + idx - offs, y); mc_wflush(); break; default: /* If delete == 1, delete the buffer. */ if (delete) { if ((i = wcslen(buf)) > linelen) i = linelen; buf[0] = 0; idx = 0; offs = 0; mc_wlocate(w, x, y); for (f = 0; f < i; f++) mc_wputc(w, ' '); delete = 0; } /* Insert character at cursor position. */ if (c < 32 || c > 255) break; f = wcslen(buf) + 2; if (f >= maxlen) break; while (f > idx) { buf[f] = buf[f-1]; f--; } buf[idx] = c; if (idx - offs >= linelen) { offs += 4; lredraw(w, x, y, buf + offs, linelen); } else lredraw(w, x + idx - offs, y, buf + idx, linelen - (idx - offs)); idx++; mc_wlocate(w, x + idx - offs, y); mc_wflush(); break; } } if (st == 0) wcscpy(s, buf); dirflush = direct; return st; } /* mc_wgets - edit one line in a window. */ int mc_wgets(WIN *w, char *s, int linelen, int maxlen) { int st; wchar_t buf[BUFLEN]; size_t i; char *sptr; sptr = s; for (i = 0; *sptr != 0 && i < ARRAY_SIZE(buf) - 1; i++) sptr += one_mbtowc(buf + i, sptr, MB_LEN_MAX); buf[i] = 0; st = mc_wgetwcs(w, buf, linelen, maxlen); if (st == 0) { sptr = s; for (i = 0; buf[i] != 0; i++) { char tmp[MB_LEN_MAX]; size_t l; /* This truncates data; too bad */ l = one_wctomb(tmp, buf[i]); if (sptr + l >= s + maxlen) break; memcpy(sptr, tmp, l); sptr += l; } *sptr = 0; } return st; } /* ==== Initialization code ==== */ static char tbuf[1024]; static char cbuf[2048]; /* Map AC characters. */ static int acmap(int c) { const char *p; for (p = AC; *p; p += 2) if (*p == c) return *++p; return '.'; } /* * Initialize the window system */ #ifdef BBS /* Code for the BBS system.. */ int win_init(char *term, int lines) { int fg = WHITE; int bg = BLACK; int attr = XA_NORMAL; #else /* Code for other applications (minicom!) */ int win_init(int fg, int bg, int attr) { char *term; #endif static WIN _stdwin; int f, olduseattr; if (w_init) return 0; #ifndef BBS if ((term = getenv("TERM")) == NULL) { fprintf(stderr, _("Environment variable TERM not set\n")); return -1; } #endif switch ((f = tgetent(cbuf, term))) { case 0: fprintf(stderr, _("No termcap entry for %s\n"), term); return -1 ; case -1: fprintf(stderr, _("No termcap database present!\n")); return -1 ; default: break; } _tptr = tbuf; if ((CM = tgetstr("cm", &_tptr)) == NULL) { fprintf(stderr, _("No cursor motion capability (cm)\n")); return -1; } LINES = COLS = 0; getrowcols(&LINES, &COLS); #ifdef BBS LINES = lines; #endif if (LINES == 0 && (LINES = tgetnum("li")) <= 0) { fprintf(stderr, _("Number of terminal lines unknown\n")); return -1; } if (COLS == 0 && (COLS = tgetnum("co")) <= 0) { fprintf(stderr, _("Number of terminal columns unknown\n")); return -1; } /* Terminal Capabilities */ ME = tgetstr("me", &_tptr); SE = tgetstr("se", &_tptr); UE = tgetstr("ue", &_tptr); AS = tgetstr("as", &_tptr); AE = tgetstr("ae", &_tptr); MB = tgetstr("mb", &_tptr); MD = tgetstr("md", &_tptr); MR = tgetstr("mr", &_tptr); SO = tgetstr("so", &_tptr); US = tgetstr("us", &_tptr); CE = tgetstr("ce", &_tptr); Al = tgetstr("al", &_tptr); Dl = tgetstr("dl", &_tptr); AL = tgetstr("AL", &_tptr); DL = tgetstr("DL", &_tptr); CS = tgetstr("cs", &_tptr); SF = tgetstr("sf", &_tptr); SR = tgetstr("sr", &_tptr); VB = tgetstr("vb", &_tptr); BL = tgetstr("bl", &_tptr); VE = tgetstr("ve", &_tptr); VI = tgetstr("vi", &_tptr); IS = tgetstr("is", &_tptr); RS = tgetstr("rs", &_tptr); KS = tgetstr("ks", &_tptr); KE = tgetstr("ke", &_tptr); CD = tgetstr("cd", &_tptr); CL = tgetstr("cl", &_tptr); IC = tgetstr("ic", &_tptr); DC = tgetstr("dc", &_tptr); BC = tgetstr("bc", &_tptr); CR = tgetstr("cr", &_tptr); NL = tgetstr("nl", &_tptr); AC = tgetstr("ac", &_tptr); EA = tgetstr("eA", &_tptr); #ifdef ST_LINE TS = tgetstr("ts", &_tptr); FS = tgetstr("fs", &_tptr); DS = tgetstr("ds", &_tptr); #endif if (MR == NULL) MR = SO; /* Try standout */ if (MR == NULL) MR = US; /* Try underline */ if (MR == NULL) MR = MD; /* Try bold */ if (SF == NULL) SF = "\n"; if (AC == NULL || *AC == 0) AC = def_ac; /* Standard vt100 mappings. */ /* cr and nl are often not defined but result in great optimization. * I only hope that minicom does not break on terminals where this * really does not work.. */ if (CR == NULL) CR = "\r"; if (NL == NULL) NL = "\n"; #ifdef ST_LINE /* See if we can use the status line. */ if (!tgetflag("hs") || !tgetflag("es") || !TS || !FS) use_status = 0; #else use_status = 0; #endif if (IS != NULL) outstr(IS); /* Initialization string */ /* Reset attributes */ olduseattr = useattr; useattr = 1; _setattr(XA_NORMAL, COLATTR(WHITE, BLACK)); useattr = olduseattr; /* No reverse? don't use attributes at all. */ if (MR == NULL) useattr = 0; /* If we have the "ug" flag, don't allow attributes to be displayed. */ if (tgetnum("ug") > 0) useattr = 0; _has_am = tgetflag("am"); _mv_standout = tgetflag("ms"); if (tgetflag("bs")) { if (BC == NULL) BC = "\b"; } else BC = NULL; /* Special IBM box-drawing characters */ D_UL = 201; D_HOR = 205; D_UR = 187; D_LL = 200; D_VER = 186; D_LR = 188; S_UL = 218; S_HOR = 240; S_UR = 191; S_LL = 192; S_VER = 179; S_LR = 217; if (AS != NULL && !screen_ibmpc) { /* Try to find AC mappings. */ D_UL = S_UL = acmap('l'); D_HOR = S_HOR = acmap('q'); D_UR = S_UR = acmap('k'); D_LL = S_LL = acmap('m'); D_VER = S_VER = acmap('x'); D_LR = S_LR = acmap('j'); } if (screen_iso) { /* Try to find AC mappings. */ D_UL = S_UL = '+'; D_HOR = S_HOR = '-'; D_UR = S_UR = '+'; D_LL = S_LL = '+'; D_VER = S_VER = '|'; D_LR = S_LR = '+'; } /* Memory for global map */ if ((gmap = malloc(sizeof(ELM) * (LINES + 1) * COLS)) == NULL) { fprintf(stderr, _("Not enough memory\n")); return -1; }; _buffend = _bufstart + BUFFERSIZE; /* Initialize stdwin */ stdwin = &_stdwin; stdwin->wrap = 1; stdwin->cursor = CNORMAL; stdwin->autocr = 1; stdwin->doscroll = 1; stdwin->x1 = 0; stdwin->sy1 = stdwin->y1 = 0; stdwin->x2 = COLS - 1; stdwin->sy2 = stdwin->y2 = LINES - 1; stdwin->xs = COLS; stdwin->ys = LINES; stdwin->attr = attr; stdwin->color = COLATTR(fg, bg); stdwin->direct = 1; stdwin->histbuf = NULL; if (EA != NULL) outstr(EA); /* Graphics init. */ if (KS != NULL) outstr(KS); /* Keypad mode */ setcbreak(1); /* Cbreak, no echo */ mc_winclr(stdwin); w_init = 1; return 0; } void win_end(void) { if (gmap == NULL || w_init == 0) return; setcbreak(0); /* Reset */ stdwin->attr = XA_NORMAL; stdwin->color = COLATTR(WHITE, BLACK); _setattr(stdwin->attr, stdwin->color); mc_winclr(stdwin); #ifdef ST_LINE if (DS) outstr(DS); #endif mc_wcursor(stdwin, CNORMAL); if (KE != NULL) outstr(KE); if (RS != NULL) outstr(RS); else if (IS != NULL) outstr(IS); mc_wflush(); free(gmap); gmap = NULL; stdwin = NULL; w_init = 0; }