1242 lines
30 KiB
C
1242 lines
30 KiB
C
#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);
|
|
Style sc = getstyle(cursheet, cursheet->cur);
|
|
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, sc.adjust))
|
|
{
|
|
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, sc.fform))
|
|
{
|
|
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"), _("dD)im"), _("uU)nderline"),
|
|
_("fF)oreground"), _("kBack)ground"), _("hH)ashing"),
|
|
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_DIM; break;
|
|
case 2: c = ADJUST_UNDERLINE; break;
|
|
case 3: c = ADJUST_FOREGROUND; break;
|
|
case 4: c = ADJUST_BACKGROUND; break;
|
|
case 5: c = ADJUST_HASHING; 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;
|
|
}
|
|
|
|
|
|
Key show_menu(Sheet *cursheet)
|
|
{
|
|
int c = K_INVALID;
|
|
|
|
do
|
|
{
|
|
const char* menu[] =
|
|
{ _("aA)ttributes"), _("wW)idth"), _("hH)eight"), _("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 = K_ROWHEIGHT; break;
|
|
case 3: c = do_block(cursheet); break;
|
|
case 4: c = do_file(cursheet); break;
|
|
case 5: c = K_GOTO; break;
|
|
case 6: do_shell(); c = KEY_CANCEL; break;
|
|
case 7: c = K_ABOUT; break;
|
|
case 8: 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)
|
|
{
|
|
cursheet->maxx = (size_t)COLS;
|
|
cursheet->maxy = (size_t)(LINES-1);
|
|
|
|
Key k;
|
|
do
|
|
{
|
|
update(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?")));
|
|
}
|
|
|
|
#define CHANNEL_MAX 1000
|
|
|
|
typedef short CursesColor[3];
|
|
void display_init(Sheet *cursheet, bool imp_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 (imp_redraw) typeahead(-1);
|
|
#endif
|
|
/* allocate and initialize the palette */
|
|
assert(COLORS > 0);
|
|
if ((size_t)COLORS < cursheet->max_colors) cursheet->max_colors = (size_t)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 (ColorNum 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 )
|
|
{
|
|
update(sheet);
|
|
}
|
|
|
|
static RowHgtT eff_height(Sheet *sheet, CoordT y) {
|
|
RowHgtT eff = rowheight(sheet, y, sheet->cur[Z]) / ROWHEIGHT_DENOMINATOR;
|
|
if (eff == 0) return 1;
|
|
return eff;
|
|
}
|
|
|
|
/* redraw_sheet -- draw a sheet with cell cursor */
|
|
void redraw_sheet(Sheet *sheet)
|
|
{
|
|
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(sheet, sheet->cur))
|
|
{
|
|
--(sheet->cur[X]);
|
|
assert(sheet->cur[X] >= 0);
|
|
}
|
|
/* Calculate the nearest Y offset that will show the current row */
|
|
if (sheet->cur[Y] < sheet->offy) sheet->offy = sheet->cur[Y];
|
|
else {
|
|
size_t available = sheet->maxy - 2 - (header ? 1 : 0);
|
|
CoordT newoffy = sheet->cur[Y];
|
|
size_t needed = eff_height(sheet, newoffy);
|
|
while (needed < available && newoffy > 0) {
|
|
RowHgtT prevhgt = eff_height(sheet, newoffy - 1);
|
|
if (needed + prevhgt > available) break;
|
|
needed += prevhgt;
|
|
--newoffy;
|
|
}
|
|
if (newoffy > sheet->offy) sheet->offy = newoffy;
|
|
}
|
|
/* Calculate the nearest X offset that will show the current column */
|
|
if (sheet->cur[X] < sheet->offx) sheet->offx = sheet->cur[X];
|
|
else {
|
|
size_t width = header ? 4 : 0;
|
|
bool again;
|
|
size_t col;
|
|
do
|
|
{
|
|
again = false;
|
|
col = 0;
|
|
for (CoordT x = sheet->offx;
|
|
width <= (size_t)(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 = true; }
|
|
else if ((size_t)(sheet->cur[X] - sheet->offx) >= col) {
|
|
sheet->offx++; again = true;
|
|
}
|
|
}
|
|
} while (again);
|
|
}
|
|
|
|
short curcp = 1;
|
|
init_pair(curcp, DefaultCN[FOREGROUND], COLOR_YELLOW);
|
|
|
|
if (header) {
|
|
(void)wattron(stdscr,DEF_NUMBER);
|
|
/* draw x numbers */
|
|
for (size_t width=4; width < (size_t)(sheet->maxx); ++width)
|
|
mvwaddch(stdscr, (int)(sheet->oriy), (int)(sheet->orix + width),
|
|
(chtype)(unsigned char)' ');
|
|
ColWidT width = 4;
|
|
for (CoordT x = sheet->offx; width < sheet->maxx; ++x)
|
|
{
|
|
short usecp = 0;
|
|
if (x == sheet->cur[X]) usecp = curcp;
|
|
ColWidT 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((size_t)sheet->maxx >= width);
|
|
if ((size_t)(sheet->maxx)-width < col)
|
|
buf[(size_t)(sheet->maxx)-width] = '\0';
|
|
wcolor_set(stdscr, usecp, NULL);
|
|
mvwaddstr(stdscr, (int)(sheet->oriy), (int)(sheet->orix+width), buf);
|
|
ColWidT used = strlen(buf);
|
|
while (used++ < col) waddch(stdscr, (chtype)(unsigned char)' ');
|
|
wcolor_set(stdscr, 0, NULL);
|
|
width += col;
|
|
}
|
|
|
|
/* draw y numbers */
|
|
RowHgtT height = 1;
|
|
for (CoordT y = sheet->offy; height + 1 < sheet->maxy; ++y) {
|
|
short usecp = 0;
|
|
if (y == sheet->cur[Y]) usecp = curcp;
|
|
wcolor_set(stdscr, usecp, NULL);
|
|
(void)mvwprintw(stdscr, (int)(height+sheet->oriy), (int)sheet->orix,
|
|
"%-4d", y);
|
|
RowHgtT rows = eff_height(sheet, y);
|
|
for (RowHgtT extra = 1; extra < rows; ++extra) {
|
|
mvwaddstr(stdscr, (int)(height+extra+sheet->oriy), (int)sheet->orix,
|
|
" ");
|
|
}
|
|
wcolor_set(stdscr, 0, NULL);
|
|
height += eff_height(sheet, y);
|
|
}
|
|
(void)wattroff(stdscr,DEF_NUMBER);
|
|
|
|
/* draw z number */
|
|
(void)mvwprintw(stdscr, (CoordT)(sheet->oriy), (CoordT)(sheet->orix),
|
|
"%3d", sheet->cur[Z]);
|
|
}
|
|
++curcp;
|
|
/* draw elements */
|
|
RowHgtT height = header ? 1 : 0;
|
|
for (CoordT y = sheet->offy; height + 1 < sheet->maxy; ++y) {
|
|
RowHgtT rows = eff_height(sheet, y);
|
|
ColWidT width = header ? 4 : 0;
|
|
for (CoordT x = sheet->offx;
|
|
width < sheet->maxx;
|
|
width += columnwidth(sheet, x, sheet->cur[Z]), ++x)
|
|
{
|
|
size_t size,realsize,fill, cutoff = 0;
|
|
int realx = x;
|
|
if (x == sheet->offx) {
|
|
Location fil;
|
|
fil[X] = realx;
|
|
fil[Y] = y - (header ? 1 : 0) + sheet->offy;
|
|
fil[Z] = sheet->cur[Z];
|
|
while (shadowed(sheet, fil)) {
|
|
--realx; fil[X] = realx;
|
|
cutoff += columnwidth(sheet, realx, sheet->cur[Z]);
|
|
}
|
|
}
|
|
tmp[X] = realx; tmp[Y] = y;
|
|
tmp[Z] = sheet->cur[Z];
|
|
cell = safe_cell_at(sheet, tmp);
|
|
Style sc = getstyle(sheet, tmp);
|
|
if ((size = cellwidth(sheet, tmp)))
|
|
{
|
|
if (bufsz < (size*UTF8SZ+1))
|
|
buf = realloc(buf, bufsz=(size*UTF8SZ+1));
|
|
size_t esize = size; char* ebuf = buf;
|
|
if (sc.italic) { esize -= 2; *buf = '*'; ebuf += 1; }
|
|
size_t spot = printvalue(ebuf, (esize*UTF8SZ + 1), esize, quote,
|
|
sc.fform, sc.precision, sheet, tmp);
|
|
if (sc.italic) { ebuf[spot++] = '*'; ebuf[spot] = '\0'; }
|
|
adjust(sc.adjust, buf, size);
|
|
assert(size >= cutoff);
|
|
if (width + size - cutoff >= (size_t)(sheet->maxx))
|
|
{
|
|
*(buf + cutoff + (size_t)sheet->maxx - width) = '\0';
|
|
realsize = (size_t)(sheet->maxx) - width + cutoff;
|
|
}
|
|
else realsize = size;
|
|
ms = getmarkstate(sheet);
|
|
short usecp = -1;
|
|
short hashcp = -1;
|
|
ColorNum fg = sc.aspect[FOREGROUND];
|
|
if (fg == NO_COLOR_SET) fg = DefaultCN[FOREGROUND];
|
|
ColorNum bg = sc.aspect[BACKGROUND];
|
|
if (bg == NO_COLOR_SET) bg = DefaultCN[BACKGROUND];
|
|
ColorNum hg = sc.aspect[HASHING];
|
|
if (hg == NO_COLOR_SET) hg = bg;
|
|
for (short trycp = 0; trycp < curcp; ++trycp) {
|
|
short pfg, pbg;
|
|
pair_content(trycp, &pfg, &pbg);
|
|
if (fg != pfg) continue;
|
|
if (bg == pbg) usecp = trycp;
|
|
if (hg == pbg) hashcp = trycp;
|
|
if (usecp >= 0 && hashcp >= 0) break;
|
|
}
|
|
if (usecp < 0) {
|
|
usecp = curcp;
|
|
init_pair(curcp++, fg, bg);
|
|
}
|
|
if (hashcp < 0) {
|
|
if (bg == hg) hashcp = usecp;
|
|
else {
|
|
hashcp = curcp;
|
|
init_pair(curcp++, fg, hg);
|
|
}
|
|
}
|
|
wcolor_set(stdscr, usecp, NULL);
|
|
bool invert =
|
|
(ms != UNMARKED) && loc_in_box(tmp, sheet->mark1, sheet->mark2);
|
|
if (x == sheet->cur[X] && y == sheet->cur[Y])
|
|
invert = (ms == MARKING) ? true : !invert;
|
|
if (invert) (void)wattron(stdscr,DEF_CELLCURSOR);
|
|
if (sc.dim) wattron(stdscr, A_DIM);
|
|
if (sc.bold) wattron(stdscr, A_BOLD);
|
|
if (sc.underline) wattron(stdscr, A_UNDERLINE);
|
|
short nextcp = hashcp;
|
|
chtype blank = (chtype)' ';
|
|
if (hashcp == usecp && fg != INVISIBLE_COLOR) {
|
|
(void)mvwaddstr(stdscr, (int)(height + sheet->oriy),
|
|
(int)(width + sheet->orix),
|
|
buf+cutoff);
|
|
} else { // Alternate colors to simulate hash
|
|
chtype next_char = (chtype)buf[cutoff];
|
|
if (fg == INVISIBLE_COLOR) next_char = blank;
|
|
(void)mvwaddch(stdscr, (int)(height + sheet->oriy),
|
|
(int)(width + sheet->orix), next_char);
|
|
for (size_t ix = cutoff+1; buf[ix]; ++ix) {
|
|
wcolor_set(stdscr, nextcp, NULL);
|
|
if (nextcp == hashcp) nextcp = usecp;
|
|
else nextcp = hashcp;
|
|
next_char = fg == INVISIBLE_COLOR ? blank : (chtype)buf[ix];
|
|
(void)waddch(stdscr, next_char);
|
|
}
|
|
}
|
|
for (fill=mbslen(buf+cutoff); fill<realsize; ++fill) {
|
|
if (usecp != hashcp) {
|
|
wcolor_set(stdscr, nextcp, NULL);
|
|
if (nextcp == hashcp) nextcp = usecp;
|
|
else nextcp = hashcp;
|
|
}
|
|
(void)waddch(stdscr,(chtype)(unsigned char)' ');
|
|
}
|
|
for (RowHgtT extra = 1; extra < rows; ++extra) {
|
|
mvwaddstr(stdscr,
|
|
(int)(height + extra + sheet->oriy),
|
|
(int)(width + sheet->orix),
|
|
" ");
|
|
for (fill = 1; fill < realsize; ++fill)
|
|
(void)waddch(stdscr, (chtype)(unsigned char)' ');
|
|
}
|
|
wcolor_set(stdscr, 0, NULL);
|
|
wstandend(stdscr);
|
|
}
|
|
}
|
|
height += rows;
|
|
}
|
|
|
|
/* draw contents of current element */
|
|
if (bufsz < (size_t)(sheet->maxx)*UTF8SZ + 1)
|
|
buf = realloc(buf, bufsz = (size_t)(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, -1, 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, -1, TRUNCATED_ERROR, &ic);
|
|
}
|
|
}
|
|
*mbspos(buf, (int)sheet->maxx) = 0;
|
|
|
|
(void)mvwaddstr(stdscr, (int)(sheet->oriy + sheet->maxy) - 1,
|
|
(int)(sheet->orix), buf);
|
|
for (size_t 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)
|
|
{
|
|
char *src, *dest;
|
|
|
|
assert(buf != NULL);
|
|
assert(prompt != NULL);
|
|
assert(x != (size_t*)0);
|
|
assert(offx != (size_t*)0);
|
|
|
|
(void)curs_set(1);
|
|
|
|
bool insert = true;
|
|
int mx = COLS;
|
|
CoordT promptlen = (CoordT)(mbslen(prompt)+1);
|
|
(void)mvwaddstr(stdscr,LINES-1,0,prompt); (void)waddch(stdscr,(chtype)(unsigned char)' ');
|
|
int c;
|
|
do {
|
|
/* correct offx so cursor stays visible */
|
|
if (*x < *offx) *offx = *x;
|
|
if ((CoordT)(*x - *offx) > (mx-promptlen-1))
|
|
*offx = *x - (size_t)mx + (size_t)promptlen + 1;
|
|
|
|
/* display buffer */
|
|
(void)wmove(stdscr, LINES-1, promptlen);
|
|
src = mbspos(buf, (int)(*offx));
|
|
dest = mbspos(buf, (int)(*offx) + COLS - promptlen);
|
|
int i = 0;
|
|
for (; *src && src < dest; ++i, ++src)
|
|
(void)waddch(stdscr, (chtype)(unsigned char)(*src));
|
|
if (i != mx) (void)wclrtoeol(stdscr);
|
|
|
|
/* show cursor */
|
|
(void)wmove(stdscr, LINES-1, (CoordT)(*x-*offx) + promptlen);
|
|
|
|
src = dest = mbspos(buf, (int)(*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 = !insert;
|
|
break;
|
|
|
|
/* EIC */
|
|
case KEY_EIC:
|
|
insert = false;
|
|
break;
|
|
|
|
/* control t */
|
|
case '\024':
|
|
if (*x > 0) {
|
|
dest = mbspos(src, -1);
|
|
if (*x == mbslen(buf)) {
|
|
src = dest;
|
|
dest = mbspos(src, -1);
|
|
(*x)--;
|
|
}
|
|
char *end = mbspos(src, 1);
|
|
|
|
while (src != end) {
|
|
char k = *src;
|
|
memmove(dest+1, dest, (size_t)(src-dest));
|
|
*dest = k;
|
|
src++;
|
|
dest++;
|
|
}
|
|
(*x)++;
|
|
}
|
|
break;
|
|
|
|
/* control backslash */
|
|
case '\034': {
|
|
int level;
|
|
char open = 0, close = 0, dir = 1;
|
|
|
|
switch (*dest) {
|
|
case ')': dir = -1; /* FALL THROUGH */
|
|
case '(': open = '('; close = ')'; break;
|
|
case '}': dir = -1; /* FALL THROUGH */
|
|
case '{': open = '{'; close = '}'; break;
|
|
case ']': dir = -1; /* FALL THROUGH */
|
|
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;
|
|
bool iscont = is_mbcont((unsigned char)c);
|
|
if (strlen(buf) >= (size-1)) {
|
|
if (iscont) {
|
|
dest = mbspos(src, -1);
|
|
memmove(dest, src, strlen(src)+1);
|
|
}
|
|
break;
|
|
}
|
|
if (insert || iscont) memmove(src+1, src, strlen(src)+1);
|
|
else {
|
|
if (is_mbchar((unsigned char)(*src)))
|
|
memmove(src+1, mbspos(src, 1), strlen(mbspos(src, 1))+1);
|
|
if (!*src) *(src+1) = '\0';
|
|
}
|
|
*src = (char)c;
|
|
if (!iscont) (*x)++;
|
|
break;
|
|
}
|
|
}
|
|
} while (c != K_ENTER && c != KEY_CANCEL
|
|
&& (c != K_UP || (sheet!=(Sheet*)0 && sheet->moveonly)));
|
|
|
|
if (sheet) sheet->moveonly = false;
|
|
(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)
|
|
{
|
|
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;
|
|
int c;
|
|
do
|
|
{
|
|
int x;
|
|
(void)wmove(stdscr, LINES-1, (int)promptlen);
|
|
/* correct offset so choice is visible */
|
|
if (curx <= offx) offx = curx;
|
|
else do
|
|
{
|
|
size_t width = promptlen;
|
|
x = offx;
|
|
while (x < maxx && width + mbslen(choice[x]+1) + 1 <= (size_t)COLS)
|
|
{
|
|
width += mbslen(choice[x]+1) + 1;
|
|
++x;
|
|
}
|
|
--x;
|
|
if (x < curx) ++offx;
|
|
} while (x < curx);
|
|
|
|
/* show visible choices */
|
|
size_t width = promptlen;
|
|
for (x = offx; x < maxx && width + mbslen(choice[x]+1) + 1 <= (size_t)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 < (size_t)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 - (CoordT)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 */
|
|
bool keypressed(void)
|
|
{
|
|
(void)nodelay(stdscr, TRUE);
|
|
bool result = (getch() != ERR);
|
|
nodelay(stdscr, FALSE);
|
|
return result;
|
|
}
|
|
|
|
|
|
/* wgetc */
|
|
static Key wgetc(void)
|
|
{
|
|
int 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;
|
|
|
|
/* F8, F9 or Control S, clock sheet */
|
|
case KEY_F(8):
|
|
case KEY_F(9):
|
|
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-s -- edit style expression */
|
|
case 's': return K_EDIT_STYLE_EXPR;
|
|
|
|
/* 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, size_t size, const char *argv0)
|
|
{
|
|
strncpy(buf, HELPFILE, size);
|
|
buf[size-1] = 0;
|
|
}
|