teapot-spreadsheet/src/display.c

1140 lines
26 KiB
C

#ifndef NO_POSIX_SOURCE
#undef _POSIX_SOURCE
#define _POSIX_SOURCE 1
#undef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 2
#endif
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
#include <ctype.h>
#include <limits.h>
#ifdef ENABLE_UTF8
#include <ncursesw/curses.h>
#else
#include <curses.h>
#endif
#include <errno.h>
#include <pwd.h>
#include <termios.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
extern char *strdup(const char* s);
#include <string.h>
#include <unistd.h>
#ifdef NEED_BCOPY
#define memmove(dst,src,len) bcopy(src,dst,len)
#endif
#ifdef OLD_REALLOC
#define realloc(s,l) myrealloc(s,l)
#endif
#ifdef DMALLOC
#include "dmalloc.h"
#endif
#include "complete.h"
#include "default.h"
#include "display.h"
#include "eval.h"
#include "main.h"
#include "misc.h"
#include "sheet.h"
#include "utf8.h"
static Key wgetc(void);
/* redraw -- redraw whole screen */
static void redraw(void)
{
(void)touchwin(curscr);
(void)wrefresh(curscr);
}
/* do_attribute -- set cell attributes */
static int do_attribute(Sheet *cursheet)
{
int c;
Cell *cc = curcell(cursheet);
do
{
MarkState ms = getmarkstate(cursheet);
const char* prompt = _("Block attribute");
if (ms == UNMARKED) prompt = _("Cell attribute");
const char *mainmenu[] =
{ _("jJ)ustify"), _("fF)loats"), _("tT)ypeface"),
_("mM)isc"), _("lL)abel"), _("kLock)"), NULL
};
c = line_menu(prompt, mainmenu, 0);
/* There is a magic number "5" in the next line, presumably it represents
the selection of the lock attribute in the list of items above, in which
lock does indeed appear at index 5
*/
if (ms == UNMARKED && c != 5 && locked(cc))
line_msg(_("Cell attribute:"), _("Cell is locked"));
else
{
switch (c)
{
case -2: case -1: c = KEY_CANCEL; break;
case 0:
{
const char *justifymenu[] =
{ _("lL)eft"), _("rR)ight"), _("cC)entered"), NULL };
switch (c = line_menu(prompt, justifymenu, getadjust(cc)))
{
case -2: case -1: c = K_INVALID; break;
case 0: c = ADJUST_LEFT; break;
case 1: c = ADJUST_RIGHT; break;
case 2: c = ADJUST_CENTER; break;
default: assert(0);
}
break;
}
case 1:
{
const char *floatmenu[] =
{ _("dD)ecimal"), _("sS)cientific"), _("cC)ompact"),
_("hH)exact"), _("pP)recision"), NULL
};
switch (c = line_menu(prompt, floatmenu, getfltformat(cc)))
{
case -2: case -1: c = K_INVALID; break;
case 0: c = ADJUST_DECIMAL; break;
case 1: c = ADJUST_SCIENTIFIC; break;
case 2: c = ADJUST_COMPACT; break;
case 3: c = ADJUST_HEXACT; break;
case 4: c = ADJUST_PRECISION; break;
default: assert(0);
}
break;
}
case 2:
{
const char *typemenu[] =
{ _("bB)old"), _("uU)nderline"), NULL };
switch (c = line_menu(prompt, typemenu, 0))
{
case -2: case -1: c = K_INVALID; break;
case 0: c = ADJUST_BOLD; break;
case 1: c = ADJUST_UNDERLINE; break;
default: assert(0);
}
break;
}
case 3:
{
const char *miscmenu[] =
{ _("sS)hadow"), _("iI)gnore"),
_("oO)utput special characters"), NULL
};
switch (c = line_menu(prompt, miscmenu, 0))
{
case -2: case -1: c = K_INVALID; break;
case 0: c = ADJUST_SHADOW; break;
case 1: c = ADJUST_IGNORE; break;
case 2: c = ADJUST_TRANSPARENT; break;
default: assert(0);
}
break;
}
case 4: c = ADJUST_LABEL; break;
case 5: c = ADJUST_LOCK; break;
default: assert(0);
}
}
} while (c == K_INVALID);
if (c == KEY_CANCEL) c = K_INVALID;
return c;
}
/* do_file -- file menu */
static int do_file(Sheet *cursheet)
{
int c = 0;
do
{
const char *menu[] =
{ _("lL)oad"), _("sS)ave"), _("nN)ame"), NULL };
switch (c = line_menu(_("File:"), menu, 0))
{
case -2:
case -1: c = KEY_CANCEL; break;
case 0: c = K_LOADMENU; break;
case 1: c = K_SAVEMENU; break;
case 2: c = K_NAME; break;
default: assert(0);
}
} while (c == K_INVALID);
if (c == KEY_CANCEL) c = K_INVALID;
return c;
}
/* do_shell -- spawn a shell */
static int do_shell(void)
{
pid_t pid;
struct sigaction interrupt;
refresh();
interrupt.sa_flags=0;
sigemptyset(&interrupt.sa_mask);
interrupt.sa_handler=SIG_IGN;
sigaction(SIGINT,&interrupt,(struct sigaction *)0);
sigaction(SIGQUIT,&interrupt,(struct sigaction *)0);
switch (pid=fork())
{
/* -1 */
case -1: line_msg(_("Spawn sub shell"),strerror(errno)); break;
/* 0 */
case 0:
{
const char *shell;
if ((shell=getenv("SHELL"))==(const char*)0)
{
struct passwd *pwd;
if ((pwd=getpwuid(getuid()))==(struct passwd*)0)
{
shell="/bin/sh";
}
else
{
shell=pwd->pw_shell;
}
}
line_msg((const char*)0,_("Sub shell started"));
move(LINES-1,0);
curs_set(1);
refresh();
reset_shell_mode();
puts("\n");
interrupt.sa_handler=SIG_DFL;
sigaction(SIGINT,&interrupt,(struct sigaction *)0);
sigaction(SIGQUIT,&interrupt,(struct sigaction *)0);
execl(shell,shell,(const char*)0);
exit(127);
break;
}
/* default */
default:
{
pid_t r;
int status;
while ((r=wait(&status))!=-1 && r!=pid);
reset_prog_mode();
interrupt.sa_handler=SIG_DFL;
sigaction(SIGINT,&interrupt,(struct sigaction *)0);
sigaction(SIGQUIT,&interrupt,(struct sigaction *)0);
clear();
refresh();
curs_set(0);
redraw();
}
}
return -1;
}
/* do_block -- block menu */
static int do_block(Sheet *cursheet)
{
int c = 0;
do
{
const char* block[] =
{ _("ecle)ar"), _("iI)nsert"), _("dD)elete"), _("mM)ove"),
_("cC)opy"), _("fF)ill"), _("sS)ort"), _("rMir)ror"), NULL
};
switch (c = line_menu(_("Block menu:"), block, 0))
{
case -2:
case -1: c = KEY_CANCEL; break;
case 0: c = BLOCK_CLEAR; break;
case 1: c = BLOCK_INSERT; break;
case 2: c = BLOCK_DELETE; break;
case 3: c = BLOCK_MOVE; break;
case 4: c = BLOCK_COPY; break;
case 5: c = BLOCK_FILL; break;
case 6: c = BLOCK_SORT; break;
case 7: c = BLOCK_MIRROR; break;
}
} while (c == K_INVALID);
if (c == KEY_CANCEL) c = K_INVALID;
return c;
}
int show_menu(Sheet *cursheet)
{
int c = K_INVALID;
do
{
const char* menu[] =
{ _("aA)ttributes"), _("wW)idth"), _("bB)lock"), _("fF)ile"),
_("gG)oto"), _("sS)hell"), _("vV)ersion"), _("qQ)uit"), NULL
};
switch (c=line_menu(_("Main menu:"),menu,0))
{
case -2:
case -1: c = KEY_CANCEL; break;
case 0: c = do_attribute(cursheet); break;
case 1: c = K_COLWIDTH; break;
case 2: c = do_block(cursheet); break;
case 3: c = do_file(cursheet); break;
case 4: c = K_GOTO; break;
case 5: do_shell(); c = KEY_CANCEL; break;
case 6: c = K_ABOUT; break;
case 7: c = K_QUIT; break;
default: assert(0);
}
} while (c == K_INVALID);
if (c == KEY_CANCEL) c = K_INVALID;
return c;
}
/* do_bg -- background teapot */
static void do_bg(void)
{
struct termios t;
if (tcgetattr(0,&t)==0 && t.c_cc[VSUSP]!=_POSIX_VDISABLE)
{
line_msg((const char*)0,_("Teapot stopped"));
move(LINES-1,0);
curs_set(1);
refresh();
reset_shell_mode();
puts("\n");
kill(getpid(),SIGSTOP);
clear();
refresh();
reset_prog_mode();
curs_set(0);
}
else line_msg((const char*)0,_("The susp character is undefined"));
}
void display_main(Sheet *cursheet)
{
Key k;
int quit = 0;
cursheet->maxx=COLS;
cursheet->maxy=LINES-1;
do
{
quit = 0;
redraw_sheet(cursheet);
k=wgetc();
wmove(stdscr,LINES-1,0);
wclrtoeol(stdscr);
switch ((int)k)
{
case KEY_SUSPEND:
case '\032': do_bg(); k = K_INVALID; break;
case '\014': redraw(); k = K_INVALID; break;
case KEY_F(0):
case KEY_F(10): k = show_menu(cursheet); break;
}
} while (k == K_INVALID || !do_sheetcmd(cursheet,k,0) || doanyway(cursheet,_("Sheet modified, leave anyway?"))!=1);
}
#define CHANNEL_MAX 1000
typedef short CursesColor[3];
void display_init(Sheet *cursheet, int always_redraw)
{
initscr();
start_color();
init_color(DefaultCN[BACKGROUND], CHANNEL_MAX, CHANNEL_MAX, CHANNEL_MAX);
assume_default_colors(COLOR_BLACK, DefaultCN[BACKGROUND]);
if (debug_level > 1)
printf("Terminal has colors: %d, #colors:%d, #pairs:%d",
has_colors(), COLORS, COLOR_PAIRS);
curs_set(0);
noecho();
raw();
nonl();
keypad(stdscr,TRUE);
clear();
refresh();
#ifdef HAVE_TYPEAHEAD
if (always_redraw) typeahead(-1);
#endif
/* allocate and initialize the palette */
if (COLORS < cursheet->max_colors) { cursheet->max_colors = COLORS; }
cursheet->palette = (void *)malloc(cursheet->max_colors*sizeof(CursesColor));
memset(cursheet->palette, '\0', cursheet->max_colors*sizeof(CursesColor));
CursesColor *palt = (CursesColor *)(cursheet->palette);
for (size_t i = 0; i <= DefaultCN[BACKGROUND]; ++i)
(void)color_content(i, &(palt[i][0]), &(palt[i][1]), &(palt[i][2]));
}
void display_end(Sheet* sheet)
{
curs_set(1);
echo();
noraw();
refresh();
endwin();
free(sheet->palette);
}
void redraw_cell(Sheet *sheet, const Location at )
{
redraw_sheet(sheet);
}
/* redraw_sheet -- draw a sheet with cell cursor */
void redraw_sheet(Sheet *sheet)
{
int width,col,x,y,again;
char pbuf[80];
char *buf=malloc(128);
size_t bufsz=128;
const char *label;
Location tmp;
Cell *cell;
MarkState ms;
char *err;
char moveonly;
assert(sheet!=(Sheet*)0);
assert(IN_OCTANT(sheet->cur));
assert(sheet->offx>=0);
assert(sheet->offy>=0);
/* correct offsets to keep cursor visible */
while (shadowed(curcell(sheet)))
{
--(sheet->cur[X]);
assert(sheet->cur[X] >= 0);
}
if (sheet->cur[Y] - sheet->offy > (sheet->maxy - 2 - header))
sheet->offy = sheet->cur[Y] - sheet->maxy + 2 + header;
if (sheet->cur[Y] < sheet->offy) sheet->offy = sheet->cur[Y];
if (sheet->cur[X] < sheet->offx) sheet->offx = sheet->cur[X];
do
{
again=0;
for (width = 4*header, x = sheet->offx, col=0;
width <= sheet->maxx;
width += columnwidth(sheet, x, sheet->cur[Z]), ++x, ++col);
--col;
sheet->width = col;
if (sheet->cur[X] != sheet->offx)
{
if (col==0) { ++sheet->offx; again=1; }
else if (sheet->cur[X] - sheet->offx >= col) {
++sheet->offx; again=1;
}
}
} while (again);
unsigned short curcp = 1;
init_pair(curcp, DefaultCN[FOREGROUND], COLOR_YELLOW);
if (header) {
(void)wattron(stdscr,DEF_NUMBER);
/* draw x numbers */
for (width=4; width < sheet->maxx; ++width)
mvwaddch(stdscr, 0+sheet->oriy, sheet->orix+width,
(chtype)(unsigned char)' ');
for (width=4, x=sheet->offx; width<sheet->maxx; width+=col,++x)
{
unsigned short usecp = 0;
if (x == sheet->cur[X]) usecp = curcp;
col = columnwidth(sheet, x, sheet->cur[Z]);
if (bufsz<(size_t)(col*UTF8SZ+1)) buf=realloc(buf,bufsz=(size_t)(col*UTF8SZ+1));
snprintf(buf,bufsz,"%d",x);
if (mbslen(buf)>col) {
buf[col-1]='$';
buf[col]='\0';
}
adjust(CENTER,buf,(size_t)col);
assert(sheet->maxx>=width);
if ((sheet->maxx-width)<col) buf[sheet->maxx-width]='\0';
wcolor_set(stdscr, usecp, NULL);
mvwaddstr(stdscr,sheet->oriy,sheet->orix+width,buf);
wcolor_set(stdscr, 0, NULL);
}
/* draw y numbers */
for (y=1; y<(sheet->maxy-1); ++y) {
unsigned short usecp = 0;
int realy = y-1+sheet->offy;
if (realy == sheet->cur[Y]) usecp = curcp;
wcolor_set(stdscr, usecp, NULL);
(void)mvwprintw(stdscr,sheet->oriy+y,sheet->orix,"%-4d",y-1+sheet->offy);
wcolor_set(stdscr, 0, NULL);
}
(void)wattroff(stdscr,DEF_NUMBER);
/* draw z number */
(void)mvwprintw(stdscr, sheet->oriy, sheet->orix, "%3d", sheet->cur[Z]);
}
++curcp;
/* draw elements */
for (y=header; y<sheet->maxy-1; ++y)
for (width = 4*header, x = sheet->offx;
width < sheet->maxx;
width += columnwidth(sheet, x, sheet->cur[Z]),++x)
{
size_t size,realsize,fill,cutoff;
int realx;
realx = x;
cutoff = 0;
if (x == sheet->offx)
while (SHADOWEDC(sheet,realx,y-header+sheet->offy,sheet->cur[Z]))
{
--realx;
cutoff+=columnwidth(sheet, realx, sheet->cur[Z]);
}
tmp[X] = realx; tmp[Y] = y - header + sheet->offy;
tmp[Z] = sheet->cur[Z]; cell = safe_cell_at(sheet, tmp);
if ((size = cellwidth(sheet, tmp)))
{
bool invert;
if (bufsz < (size*UTF8SZ+1))
buf = realloc(buf, bufsz=(size*UTF8SZ+1));
printvalue(buf, (size*UTF8SZ + 1), size, quote, getfltformat(cell),
getprecision(cell), sheet, tmp);
adjust(getadjust(cell), buf, size);
assert(size>=cutoff);
if (width+((int)(size-cutoff)) >= sheet->maxx)
{
*(buf+cutoff+sheet->maxx-width)='\0';
realsize=sheet->maxx-width+cutoff;
}
else realsize=size;
ms = getmarkstate(sheet);
invert =
(ms != UNMARKED) && loc_in_box(tmp, sheet->mark1, sheet->mark2);
if (x == sheet->cur[X] && (y-header+sheet->offy) == sheet->cur[Y])
invert = (ms == MARKING) ? true : !invert;
if (invert) (void)wattron(stdscr,DEF_CELLCURSOR);
if (isbold(cell)) wattron(stdscr,A_BOLD);
if (underlined(cell)) wattron(stdscr,A_UNDERLINE);
(void)mvwaddstr(stdscr,sheet->oriy+y,sheet->orix+width,buf+cutoff);
for (fill=mbslen(buf+cutoff); fill<realsize; ++fill)
(void)waddch(stdscr,(chtype)(unsigned char)' ');
wstandend(stdscr);
}
}
/* draw contents of current element */
if (bufsz < (unsigned int)(sheet->maxx*UTF8SZ+1))
buf = realloc(buf,bufsz=(sheet->maxx*UTF8SZ+1));
label = getlabel(curcell(sheet));
assert(label != (const char*)0);
moveonly = sheet->moveonly ? *_("V") : *_("E");
if (*label=='\0')
sprintf(pbuf, "%c @(%d,%d,%d)=", moveonly,
sheet->cur[X], sheet->cur[Y], sheet->cur[Z]);
else sprintf(pbuf, "%c @(%s)=", moveonly, label);
(void)strncpy(buf,pbuf,bufsz);
buf[bufsz-1] = 0;
if ((err=geterror(sheet,sheet->cur)) != (const char*)0)
{
(void)strncpy(buf, err, bufsz);
free(err);
}
else
{
cell = curcell(sheet);
Token bc = gettok(cell, BASE_CONT);
printtok(buf+strlen(buf), bufsz-strlen(buf), 0, QUOTE_STRING,
FLT_COMPACT, 0, TRUNCATED_ERROR, &bc);
Token ic = gettok(cell, ITER_CONT);
if (ic.type != EMPTY && mbslen(buf) < (size_t)(sheet->maxx+1-4))
{
strcat(buf," -> ");
printtok(buf+strlen(buf), bufsz-strlen(buf), 0, QUOTE_STRING,
FLT_COMPACT, 0, TRUNCATED_ERROR, &ic);
}
}
*mbspos(buf, sheet->maxx) = 0;
(void)mvwaddstr(stdscr,sheet->oriy+sheet->maxy-1,sheet->orix,buf);
for (col=mbslen(buf); col<sheet->maxx; ++col) (void)waddch(stdscr,' ');
}
/* line_file -- line editor function for file name entry */
const char *line_file(const char *file, const char *pattern, const char *title, int create)
{
static char buf[PATH_MAX] = "";
int rc;
size_t dummy1 = 0, dummy2 = 0;
if (file) strncpy(buf, file, sizeof(buf));
buf[sizeof(buf)-1] = 0;
rc = line_edit((Sheet*)0, buf, sizeof(buf), title, &dummy1, &dummy2);
if (rc < 0) return NULL;
return buf;
}
/* line_edit -- line editor function */
int line_edit(Sheet *sheet, char *buf, size_t size, const char *prompt, size_t *x, size_t *offx)
{
size_t promptlen;
char *src, *dest;
int i,mx,my,insert;
chtype c;
assert(buf!=(char*)0);
assert(prompt!=(char*)0);
assert(x!=(size_t*)0);
assert(offx!=(size_t*)0);
(void)curs_set(1);
mx=COLS;
my=LINES;
promptlen=mbslen(prompt)+1;
(void)mvwaddstr(stdscr,LINES-1,0,prompt); (void)waddch(stdscr,(chtype)(unsigned char)' ');
insert=1;
do {
/* correct offx to cursor stays visible */
if (*x<*offx) *offx=*x;
if ((*x-*offx)>(mx-promptlen-1)) *offx=*x-mx+promptlen+1;
/* display buffer */
(void)wmove(stdscr,LINES-1,(int)promptlen);
src = mbspos(buf, *offx);
dest = mbspos(buf, *offx+COLS-promptlen);
for (; *src && src < dest; src++) (void)waddch(stdscr,(chtype)(unsigned char)(*src));
if (i!=mx) (void)wclrtoeol(stdscr);
/* show cursor */
(void)wmove(stdscr,LINES-1,(int)(*x-*offx+promptlen));
src = dest = mbspos(buf, *x);
c=wgetc();
if (sheet!=(Sheet*)0 && sheet->moveonly) switch (c) {
/* ^o -- switch back to line editor */
case '\t':
case '\017': sheet->moveonly=0; break;
/* v -- insert value of current cell */
case 'v': {
char valbuf[1024];
printvalue(valbuf, sizeof(valbuf), 0, QUOTE_STRING,
FLT_COMPACT, 0, sheet, sheet->cur);
if (strlen(buf)+strlen(valbuf) >= (size-1)) break;
(void)memmove(src+strlen(valbuf), src, strlen(src));
(void)memcpy(src, valbuf, strlen(valbuf));
(*x) += mbslen(valbuf);
break;
}
/* p -- insert position of current cell */
case 'p': {
char valbuf[1024];
sprintf(valbuf, "(%d,%d,%d)",
sheet->cur[X], sheet->cur[Y], sheet->cur[Z]);
if (strlen(buf)+strlen(valbuf) >= (size-1)) break;
(void)memmove(src+strlen(valbuf), src, strlen(src));
(void)memcpy(src, valbuf, strlen(valbuf));
(*x) += mbslen(valbuf);
break;
}
/* default -- move around in sheet */
default:
(void)do_sheetcmd(sheet,c,1);
redraw_sheet(sheet);
break;
} else switch (c) {
/* UP */
case K_UP: break;
/* LEFT */
case K_LEFT: if (*x > 0) (*x)--; break;
/* RIGHT */
case K_RIGHT: if (*x < mbslen(buf)) (*x)++; break;
/* BACKSPACE */
case K_BACKSPACE:
if (*x > 0) {
memmove(mbspos(src, -1), src, strlen(src)+1);
(*x)--;
}
break;
/* C-i -- file name completion */
case '\t':
completefile(buf, src, size);
break;
/* DC */
case K_DC:
src = mbspos(dest, 1);
if (*x < strlen(buf)) memmove(dest, src, strlen(src)+1);
break;
/* HOME */
case K_HOME:
*x = 0;
break;
/* END */
case K_END:
*x = mbslen(buf);
break;
/* IC */
case KEY_IC:
insert=1-insert;
break;
/* EIC */
case KEY_EIC:
insert=0;
break;
/* control t */
case '\024':
if (*x > 0) {
char c, *end;
dest = mbspos(src, -1);
if (*x == mbslen(buf)) {
src = dest;
dest = mbspos(src, -1);
(*x)--;
}
end = mbspos(src, 1);
while (src != end) {
c = *src;
memmove(dest+1, dest, src-dest);
*dest = c;
src++;
dest++;
}
(*x)++;
}
break;
/* control backslash */
case '\034': {
int level;
char open = 0, close = 0, dir = 1;
switch (*dest) {
case ')': dir = -1;
case '(': open = '('; close = ')'; break;
case '}': dir = -1;
case '{': open = '{'; close = '}'; break;
case ']': dir = -1;
case '[': open = '['; close = ']'; break;
default: break;
}
level = dir;
while (*dest && level) {
dest += dir;
if (*dest == open) level--;
else if (*dest == close) level++;
}
if (!level) *x = mbslen(buf)-mbslen(dest);
break;
}
/* DL */
case KEY_DL:
*src = '\0';
break;
/* control o */
case '\017':
if (sheet!=(Sheet*)0) sheet->moveonly=1;
break;
/* default */
default:
if (((unsigned int)c) < ' ' || ((unsigned int)c) >= 256) break;
if (strlen(buf) >= (size-1)) {
if (is_mbcont(c)) {
dest = mbspos(src, -1);
memmove(dest, src, strlen(src)+1);
}
break;
}
if (insert || is_mbcont(c)) memmove(src+1, src, strlen(src)+1);
else {
if (is_mbchar(*src)) memmove(src+1, mbspos(src, 1), strlen(mbspos(src, 1))+1);
if (!*src) *(src+1) = '\0';
}
*src = (char)c;
if (!is_mbcont(c)) (*x)++;
break;
}
} while (c != K_ENTER && c != KEY_CANCEL && (c != K_UP || (sheet!=(Sheet*)0 && sheet->moveonly)));
if (sheet) sheet->moveonly=0;
(void)curs_set(0);
(void)wmove(stdscr,LINES-1,0);
(void)wclrtoeol(stdscr);
switch (c) {
case KEY_CANCEL: return -1;
case K_UP: return -2;
default: return 0;
}
}
/* line_ok -- one line yes/no menu */
int line_ok(const char *prompt, int curx)
{
assert(curx == 0 || curx == 1);
const char* menu[] = { _("nN)o"), _("yY)es"), NULL };
return line_menu(prompt, menu, curx);
}
/* line_binary -- two choices with cancel */
int line_binary(const char *prompt, const char* op1, const char* op2, int curx)
{
int result;
assert(curx == 0 || curx == 1);
const char* menu[] = { _("cC)ancel"), op1, op2, NULL };
return line_menu(prompt, menu, curx+1) - 1;
}
/* line_menu -- one line menu */
/* Notes */
/*
The choices are terminated by the last element having a (const char*)str
field. Each item can be chosen by tolower(*str) or by the key stored in
the c field. Use a space as first character of str, if you only want the
function key to work.
*/
int line_menu(const char *prompt, const char **choice, int curx)
{
assert(prompt != NULL);
assert(choice != (const char **)0);
assert(curx >= 0);
mvwaddstr(stdscr,LINES-1,0,prompt);
size_t promptlen = mbslen(prompt);
int maxx = 0;
while (choice[maxx] != NULL) ++maxx;
int offx = 0;
chtype c;
do
{
int x, width;
(void)wmove(stdscr, LINES-1, (int)promptlen);
/* correct offset so choice is visible */
if (curx <= offx) offx = curx;
else do
{
width = promptlen;
x = offx;
while (x < maxx && width + ((int)mbslen(choice[x]+1)) + 1 <= COLS)
{
width += (int)(mbslen(choice[x]+1)) + 1;
++x;
}
--x;
if (x < curx) ++offx;
} while (x < curx);
/* show visible choices */
for (width = promptlen, x = offx;
x < maxx && width + ((int)mbslen(choice[x]+1)) + 1 <= COLS;
width += mbslen(choice[x]+1) + 1, ++x)
{
(void)waddch(stdscr, (chtype)(unsigned char)' ');
if (x == curx) (void)wattron(stdscr, DEF_MENU);
(void)waddstr(stdscr, (char*)(choice[x]+1));
if (x == curx) (void)wattroff(stdscr,DEF_MENU);
}
if (width < COLS) (void)wclrtoeol(stdscr);
switch (c = wgetc())
{
/* KEY_LEFT -- move to previous item */
case K_BACKSPACE:
case K_LEFT: if (curx > 0) --curx; else curx = maxx-1; break;
/* Space, Tab, KEY_RIGHT -- move to next item */
case ' ':
case '\t':
case K_RIGHT: if (curx < (maxx-1)) ++curx; else curx = 0; break;
/* default -- search choice keys */
default:
{
for (int i = 0; i < maxx; ++i)
if (c < 256 && tolower(c) == choice[i][0])
{
c = K_ENTER;
curx = i;
}
}
}
}
while (c != K_ENTER && c != K_DOWN && c != KEY_CANCEL && c != K_UP);
(void)wmove(stdscr, LINES-1, 0);
(void)wclrtoeol(stdscr);
switch (c)
{
case KEY_CANCEL: return -1;
case K_UP: return -2;
default: return curx;
}
}
/* line_msg -- one line message which will be cleared by someone else */
void line_msg(const char *prompt, const char *msg)
{
int width;
assert(msg!=(const char*)0);
if (!*msg) msg = _("Use F0, F10 or / for menu");
if (!batch)
{
width=1;
mvwaddch(stdscr,LINES-1,0,(chtype)(unsigned char)'[');
if (prompt!=(const char*)0)
{
for (; width<COLS && *prompt!='\0'; ++width,++prompt) (void)waddch(stdscr,(chtype)(unsigned char)(*prompt));
if (width<COLS) { (void)waddch(stdscr,(chtype)(unsigned char)' '); ++width; }
}
for (; width<COLS && *msg!='\0'; ++width,++msg) (void)waddch(stdscr,(chtype)(unsigned char)(*msg));
if (width<COLS) (void)waddch(stdscr,(chtype)(unsigned char)']');
if (width+1<COLS) (void)wclrtoeol(stdscr);
}
else
{
if (prompt) fprintf(stderr,_("line %u: %s %s\n"),batchln,prompt,msg);
else fprintf(stderr,_("line %u: %s\n"),batchln,msg);
exit(1);
}
}
void show_text(const char *text)
{
int i;
char *end, *stripped;
stripped = striphtml(text);
text = stripped-1;
while (text) {
(void)clear();
for (i = 0; i < LINES-2 && text; i++) {
end = strchr(++text, '\n');
if (*text == '\f') break;
if (end) *end = 0;
(void)move(i,(COLS-mbslen(text))/2);
(void)addstr(text);
text = end;
}
(void)move(i+1, (COLS-29)/2); (void)addstr(_("[ Press any key to continue ]"));
(void)refresh();
(void)getch();
}
free(stripped);
}
/* keypressed -- get keypress, if there is one */
int keypressed(void)
{
(void)nodelay(stdscr,TRUE);
if (getch()==ERR)
{
(void)nodelay(stdscr,FALSE);
return 0;
}
else
{
(void)nodelay(stdscr,FALSE);
return 1;
}
}
/* wgetc */
static Key wgetc(void)
{
chtype c;
doupdate();
refresh();
switch (c=wgetch(stdscr))
{
/* LEFT */
case KEY_LEFT:
case '\02': return K_LEFT;
/* RIGHT */
case KEY_RIGHT:
case '\06': return K_RIGHT;
/* UP */
case KEY_UP:
case '\020': return K_UP;
/* DOWN */
case KEY_DOWN:
case '\016': return K_DOWN;
/* BACKSPACE */
case KEY_BACKSPACE:
case '\010': return K_BACKSPACE;
/* DC */
case KEY_DC:
case '\04':
case '\177': return K_DC;
/* CANCEL */
case '\03':
case '\07': return KEY_CANCEL;
/* ENTER */
case KEY_ENTER:
case '\r':
case '\n': return K_ENTER;
/* HOME */
case KEY_HOME:
case '\01': return K_HOME;
/* END */
case KEY_END:
case '\05': return K_END;
/* DL */
case '\013': return KEY_DL;
/* NPAGE */
case KEY_NPAGE:
case '\026': return K_NPAGE;
/* PPAGE */
case KEY_PPAGE: return K_PPAGE;
/* Control Y, copy */
case '\031': return K_COPY;
/* Control R, recalculate sheet */
case '\022': return K_RECALC;
/* Control S, clock sheet */
case '\023': return K_CLOCK;
/* Control X, get one more key */
case '\030':
{
switch (wgetch(stdscr))
{
/* C-x < -- BPAGE */
case KEY_PPAGE:
case '<': return K_BPAGE;
/* C-x > -- FPAGE */
case KEY_NPAGE:
case '>': return K_FPAGE;
/* C-x C-c -- QUIT */
case '\03': return K_QUIT;
/* C-x C-s -- SAVE */
case '\023': return K_SAVE;
/* C-x C-r -- LOAD */
case '\022': return K_LOAD;
/* default -- INVALID, general invalid value */
default: return K_INVALID;
}
}
/* ESC, get one more key */
case '\033':
{
switch (wgetch(stdscr))
{
/* M-v -- PPAGE */
case 'v': return K_PPAGE;
/* M-Enter -- MENTER */
case KEY_ENTER:
case '\r':
case '\n': return K_MENTER;
/* M-z -- SAVEQUIT */
case 'z': return K_SAVEQUIT;
/* default -- INVALID, general invalid value */
default: return K_INVALID;
}
}
/* _("Load sheet file format:") */
case KEY_F(2): return K_LOADMENU;
/* _("Save sheet file format:") */
case KEY_F(3): return K_SAVEMENU;
/* default */
default: return c;
}
}
void find_helpfile(char *buf, int size, const char *argv0)
{
strncpy(buf, HELPFILE, size);
buf[size-1] = 0;
}