Synchronet now requires the libarchive development package (e.g. libarchive-dev on Debian-based Linux distros, libarchive.org for more info) to build successfully.

uifc32.c 87.2 KB
Newer Older
1
/* Modern implementation of UIFC (user interface) library based on uifc.c */
2
// vi: tabstop=4
3

4
/* $Id: uifc32.c,v 1.268 2020/08/16 20:37:08 rswindell Exp $ */
rswindell's avatar
rswindell committed
5

6 7 8 9
/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
10
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
 *																			*
 * This library is free software; you can redistribute it and/or			*
 * modify it under the terms of the GNU Lesser General Public License		*
 * as published by the Free Software Foundation; either version 2			*
 * of the License, or (at your option) any later version.					*
 * See the GNU Lesser General Public License for more details: lgpl.txt or	*
 * http://www.fsf.org/copyleft/lesser.html									*
 *																			*
 * Anonymous FTP access to the most recent released source is available at	*
 * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
 *																			*
 * Anonymous CVS access to the development source and modification history	*
 * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
 *     (just hit return, no password is necessary)							*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * You are encouraged to submit any modifications (preferably in Unix diff	*
 * format) via e-mail to mods@synchro.net									*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

#ifdef __unix__
	#include <stdio.h>
	#include <unistd.h>
	#ifdef __QNX__
		#include <strings.h>
	#endif
    #define mswait(x) delay(x)
#elif defined(_WIN32)
	#include <share.h>
	#include <windows.h>
	#define mswait(x) Sleep(x)
#endif
49
#include <genwrap.h>	// for alloca()
50
#include <datewrap.h>	// localtime_r()
rswindell's avatar
rswindell committed
51
#include "xpprintf.h"
52

53
#include "ciolib.h"
54
#include "uifc.h"
55
#define MAX_GETSTR	5120
56 57 58 59 60 61

#define BLINK	128

static int   cursor;
static char* helpfile=0;
static uint  helpline=0;
deuce's avatar
deuce committed
62
static size_t blk_scrn_len;
63 64 65
static struct vmem_cell *blk_scrn;
static struct vmem_cell *tmp_buffer;
static struct vmem_cell *tmp_buffer2;
66 67 68 69
static win_t sav[MAX_BUFS];
static uifcapi_t* api;

/* Prototypes */
70
static int   uprintf(int x, int y, unsigned attr, char *fmt,...);
71 72
static void  bottomline(int line);
static char  *utimestr(time_t *intime);
deuce's avatar
deuce committed
73
static void  help(void);
74
static int   ugetstr(int left, int top, int width, char *outstr, int max, long mode, int *lastkey);
75
static void  timedisplay(BOOL force);
76 77 78 79 80 81 82 83

/* API routines */
static void uifcbail(void);
static int  uscrn(char *str);
static int  ulist(int mode, int left, int top, int width, int *dflt, int *bar
	,char *title, char **option);
static int  uinput(int imode, int left, int top, char *prompt, char *str
	,int len ,int kmode);
rswindell's avatar
rswindell committed
84 85 86 87
static int  umsg(char *str);
static int  umsgf(char *fmt, ...);
static BOOL confirm(char *fmt, ...);
static BOOL deny(char *fmt, ...);
88 89 90 91 92 93 94 95 96 97
static void upop(char *str);
static void sethelp(int line, char* file);
static void showbuf(int mode, int left, int top, int width, int height, char *title
	, char *hbuf, int *curp, int *barp);

/* Dynamic menu support */
static int *last_menu_cur=NULL;
static int *last_menu_bar=NULL;
static int save_menu_cur=-1;
static int save_menu_bar=-1;
98
static int save_menu_opts=-1;
99

100 101
char* uifcYesNoOpts[]={"Yes","No",NULL};

102 103 104 105 106
static void reset_dynamic(void) {
	last_menu_cur=NULL;
	last_menu_bar=NULL;
	save_menu_cur=-1;
	save_menu_bar=-1;
107
	save_menu_opts=-1;
108 109
}

110
static uifc_graphics_t cp437_chars = {
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
	.background=0xb0,
	.help_char='?',
	.close_char=0xfe,
	.up_arrow=30,
	.down_arrow=31,
	.button_left='[',
	.button_right=']',

	.list_top_left=0xc9,
	.list_top=0xcd,
	.list_top_right=0xbb,
	.list_separator_left=0xcc,
	.list_separator_right=0xb9,
	.list_horizontal_separator=0xcd,
	.list_left=0xba,
	.list_right=0xba,
	.list_bottom_left=0xc8,
	.list_bottom_right=0xbc,
	.list_bottom=0xcd,
	.list_scrollbar_separator=0xb3,

	.input_top_left=0xc9,
	.input_top=0xcd,
	.input_top_right=0xbb,
	.input_left=0xba,
	.input_right=0xba,
	.input_bottom_left=0xc8,
	.input_bottom_right=0xbc,
	.input_bottom=0xcd,

	.popup_top_left=0xda,
	.popup_top=0xc4,
	.popup_top_right=0xbf,
	.popup_left=0xb3,
	.popup_right=0xb3,
	.popup_bottom_left=0xc0,
	.popup_bottom_right=0xd9,
	.popup_bottom=0xc4,

	.help_top_left=0xda,
	.help_top=0xc4,
	.help_top_right=0xbf,
	.help_left=0xb3,
	.help_right=0xb3,
	.help_bottom_left=0xc0,
	.help_bottom_right=0xd9,
	.help_bottom=0xc4,
	.help_titlebreak_left=0xb4,
	.help_titlebreak_right=0xc3,
	.help_hitanykey_left=0xb4,
	.help_hitanykey_right=0xc3,
};

164 165 166 167 168
/****************************************************************************/
/* Initialization function, see uifc.h for details.							*/
/* Returns 0 on success.													*/
/****************************************************************************/

169 170
void uifc_mouse_enable(void)
{
171 172 173 174 175
	ciomouse_setevents(0);
	ciomouse_addevent(CIOLIB_BUTTON_1_DRAG_START);
	ciomouse_addevent(CIOLIB_BUTTON_1_DRAG_MOVE);
	ciomouse_addevent(CIOLIB_BUTTON_1_DRAG_END);
	ciomouse_addevent(CIOLIB_BUTTON_1_CLICK);
deuce's avatar
deuce committed
176
	ciomouse_addevent(CIOLIB_BUTTON_2_CLICK);
177
	ciomouse_addevent(CIOLIB_BUTTON_3_CLICK);
deuce's avatar
deuce committed
178 179
	ciomouse_addevent(CIOLIB_BUTTON_4_PRESS);
	ciomouse_addevent(CIOLIB_BUTTON_5_PRESS);
deuce's avatar
deuce committed
180
	mousepointer(CIOLIB_MOUSEPTR_BAR);
181 182 183 184 185 186 187 188 189
	showmouse();
}

void uifc_mouse_disable(void)
{
	ciomouse_setevents(0);
	hidemouse();
}

deuce's avatar
deuce committed
190
int kbwait(void) {
191
	int timeout=0;
192
	while(timeout++<50) {
193
		if(kbhit())
194
			return(TRUE);
195
		mswait(1);
196 197 198 199
	}
	return(FALSE);
}

200
int inkey(void)
deuce's avatar
deuce committed
201 202 203 204
{
	int c;

	c=getch();
205
	if(!c || c==0xe0)
206
		c|=(getch()<<8);
deuce's avatar
deuce committed
207 208
	return(c);
}
209

210
int uifcini32(uifcapi_t* uifcapi)
211
{
212
	unsigned	i;
213 214 215 216 217 218
	struct	text_info txtinfo;

    if(uifcapi==NULL || uifcapi->size!=sizeof(uifcapi_t))
        return(-1);

    api=uifcapi;
219
    if (api->chars == NULL)
rswindell's avatar
rswindell committed
220 221 222 223
		api->chars = &cp437_chars;

	if (api->yesNoOpts == NULL)
		api->yesNoOpts = uifcYesNoOpts;
224

225
    /* install function handlers */
226 227 228
    api->bail=uifcbail;
    api->scrn=uscrn;
    api->msg=umsg;
rswindell's avatar
rswindell committed
229 230 231
	api->msgf=umsgf;
	api->confirm=confirm;
	api->deny=deny;
232 233 234 235 236
    api->pop=upop;
    api->list=ulist;
    api->input=uinput;
    api->sethelp=sethelp;
    api->showhelp=help;
deuce's avatar
deuce committed
237
	api->showbuf=showbuf;
238
	api->timedisplay=timedisplay;
239
	api->bottomline=bottomline;
240
	api->getstrxy=ugetstr;
241
	api->printf=uprintf;
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271

    if(api->scrn_len!=0) {
        switch(api->scrn_len) {
            case 14:
                textmode(C80X14);
                break;
            case 21:
                textmode(C80X21);
                break;
            case 25:
                textmode(C80);
                break;
            case 28:
                textmode(C80X28);
                break;
            case 43:
                textmode(C80X43);
                break;
            case 50:
                textmode(C80X50);
                break;
            case 60:
                textmode(C80X60);
                break;
            default:
                textmode(C4350);
                break;
        }
    }

272
#if 0
273
    clrscr();
274
#endif
275 276 277 278

    gettextinfo(&txtinfo);
    /* unsupported mode? */
    if(txtinfo.screenheight<MIN_LINES
deuce's avatar
deuce committed
279
/*        || txtinfo.screenheight>MAX_LINES */
deuce's avatar
deuce committed
280
        || txtinfo.screenwidth<40) {
281 282 283
        textmode(C80);  /* set mode to 80x25*/
        gettextinfo(&txtinfo);
    }
284
	window(1,1,txtinfo.screenwidth,txtinfo.screenheight);
285 286

    api->scrn_len=txtinfo.screenheight;
deuce's avatar
deuce committed
287
    if(api->scrn_len<MIN_LINES) {
288 289
		uifcbail();
		printf("\r\nUIFC: Screen length (%u) must be %d lines or greater\r\n"
deuce's avatar
deuce committed
290
            ,api->scrn_len,MIN_LINES);
291 292 293 294
        return(-2);
    }
    api->scrn_len--; /* account for status line */

295
    if(txtinfo.screenwidth<40) {
296 297
		uifcbail();
        printf("\r\nUIFC: Screen width (%u) must be at least 40 characters\r\n"
298 299 300 301 302 303 304
            ,txtinfo.screenwidth);
        return(-3);
    }
	api->scrn_width=txtinfo.screenwidth;

    if(!(api->mode&UIFC_COLOR)
        && (api->mode&UIFC_MONO
305 306 307 308 309 310
            || txtinfo.currmode==MONO || txtinfo.currmode==BW40 || txtinfo.currmode==BW80
            || txtinfo.currmode==MONO14 || txtinfo.currmode==BW40X14 || txtinfo.currmode==BW80X14
            || txtinfo.currmode==MONO21 || txtinfo.currmode==BW40X21 || txtinfo.currmode==BW80X21
            || txtinfo.currmode==MONO28 || txtinfo.currmode==BW40X28 || txtinfo.currmode==BW80X28
            || txtinfo.currmode==MONO43 || txtinfo.currmode==BW40X43 || txtinfo.currmode==BW80X43
            || txtinfo.currmode==MONO50 || txtinfo.currmode==BW40X50 || txtinfo.currmode==BW80X50
deuce's avatar
deuce committed
311
            || txtinfo.currmode==MONO60 || txtinfo.currmode==BW40X60 || txtinfo.currmode==BW80X60
312
			|| txtinfo.currmode==ATARI_40X24 || txtinfo.currmode==ATARI_80X25))
313
	{
314 315 316 317 318
        api->bclr=BLACK;
        api->hclr=WHITE;
        api->lclr=LIGHTGRAY;
        api->cclr=LIGHTGRAY;
			api->lbclr=BLACK|(LIGHTGRAY<<4);	/* lightbar color */
319
    } else {
320 321 322 323 324
        api->bclr=BLUE;
        api->hclr=YELLOW;
        api->lclr=WHITE;
        api->cclr=CYAN;
		api->lbclr=BLUE|(LIGHTGRAY<<4);	/* lightbar color */
325
    }
326

327 328
	blk_scrn_len=api->scrn_width*api->scrn_len;
	if((blk_scrn=(struct vmem_cell *)malloc(blk_scrn_len * sizeof(*blk_scrn)))==NULL)  {
deuce's avatar
deuce committed
329
				cprintf("UIFC line %d: error allocating %u bytes."
330
					,__LINE__,blk_scrn_len * sizeof(*blk_scrn));
deuce's avatar
deuce committed
331 332
				return(-1);
	}
333
	if((tmp_buffer=(struct vmem_cell *)malloc(blk_scrn_len * sizeof(*blk_scrn)))==NULL)  {
deuce's avatar
deuce committed
334
				cprintf("UIFC line %d: error allocating %u bytes."
335
					,__LINE__,blk_scrn_len * sizeof(*blk_scrn));
deuce's avatar
deuce committed
336 337
				return(-1);
	}
338
	if((tmp_buffer2=(struct vmem_cell *)malloc(blk_scrn_len * sizeof(*blk_scrn)))==NULL)  {
339
				cprintf("UIFC line %d: error allocating %u bytes."
340
					,__LINE__,blk_scrn_len * sizeof(*blk_scrn));
341 342
				return(-1);
	}
343 344 345 346 347 348
    for(i=0;i<blk_scrn_len;i++) {
		blk_scrn[i].legacy_attr = api->cclr|(api->bclr<<4);
		blk_scrn[i].ch = api->chars->background;
		blk_scrn[i].font = 0;
		attr2palette(blk_scrn[i].legacy_attr, &blk_scrn[i].fg, &blk_scrn[i].bg);
	}
349 350 351 352

    cursor=_NOCURSOR;
    _setcursortype(cursor);

353
	if(cio_api.mouse && !(api->mode&UIFC_NOMOUSE)) {
354
		api->mode|=UIFC_MOUSE;
355 356
		uifc_mouse_enable();
	}
357

358 359 360 361
	/* A esc_delay of less than 10 is stupid... silently override */
	if(api->esc_delay < 10)
		api->esc_delay=25;

362 363
	if(cio_api.escdelay)
		*(cio_api.escdelay)=api->esc_delay;
364

365 366
	for(i=0; i<MAX_BUFS; i++)
		sav[i].buf=NULL;
deuce's avatar
deuce committed
367 368 369
	api->savnum=0;

	api->initialized=TRUE;
370

371
    return(0);
372 373
}

374 375 376 377
void docopy(void)
{
	int	key;
	struct mouse_event mevent;
378 379 380
	struct ciolib_screen *screen;
	struct ciolib_screen *sbuffer;
	int sbufsize;
381 382 383
	int x,y,startx,starty,endx,endy,lines;
	int outpos;
	char *copybuf;
384 385 386 387 388 389 390 391 392 393
	uint32_t bg0;
	uint32_t bg1;
	uint32_t bg6;
	uint32_t fg8;

	attr2palette(0x08, &fg8, &bg0);
	attr2palette(0x10, NULL, &bg1);
	attr2palette(0x60, NULL, &bg6);
	screen=ciolib_savescreen();
	sbuffer=ciolib_savescreen();
394 395
	freepixels(sbuffer->pixels);
	sbuffer->pixels = NULL;
396
	sbufsize=screen->text_info.screenwidth * screen->text_info.screenheight * sizeof(screen->vmem[0]);
397 398
	while(1) {
		key=getch();
399
		if(key==0 || key==0xe0)
400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
			key|=getch()<<8;
		switch(key) {
			case CIO_KEY_MOUSE:
				getmouse(&mevent);
				if(mevent.startx<mevent.endx) {
					startx=mevent.startx;
					endx=mevent.endx;
				}
				else {
					startx=mevent.endx;
					endx=mevent.startx;
				}
				if(mevent.starty<mevent.endy) {
					starty=mevent.starty;
					endy=mevent.endy;
				}
				else {
					starty=mevent.endy;
					endy=mevent.starty;
				}
				switch(mevent.event) {
					case CIOLIB_BUTTON_1_DRAG_MOVE:
422
						memcpy(sbuffer->vmem,screen->vmem,sbufsize);
423 424 425
						for(y=starty-1;y<endy;y++) {
							for(x=startx-1;x<endx;x++) {
								int pos=y*api->scrn_width+x;
426 427 428

								if (sbuffer->vmem[pos].bg != bg1)
									sbuffer->vmem[pos].bg = bg1;
429
								else
430 431 432 433 434 435 436 437 438
									sbuffer->vmem[pos].bg = bg6;
								if (sbuffer->vmem[pos].bg == sbuffer->vmem[pos].fg)
									attr2palette(sbuffer->vmem[pos].legacy_attr | 0x08, &sbuffer->vmem[pos].fg, &sbuffer->vmem[pos].bg);
								if ((sbuffer->vmem[pos].legacy_attr & 0x70) != 0x10)
									sbuffer->vmem[pos].legacy_attr = (sbuffer->vmem[pos].legacy_attr & 0x8f) | 0x10;
								else
									sbuffer->vmem[pos].legacy_attr = (sbuffer->vmem[pos].legacy_attr & 0x8f) | 0x60;
								if (((sbuffer->vmem[pos].legacy_attr & 0x70) >> 4) == (sbuffer->vmem[pos].legacy_attr & 0x08))
									sbuffer->vmem[pos].legacy_attr |= 0x08;
439 440
							}
						}
441
						restorescreen(sbuffer);
442 443 444
						break;
					case CIOLIB_BUTTON_1_DRAG_END:
						lines=abs(mevent.endy-mevent.starty)+1;
deuce's avatar
deuce committed
445
						copybuf=malloc(((endy-starty+1)*(endx-startx+1)+1+lines*2)*4);
446 447 448
						outpos=0;
						for(y=starty-1;y<endy;y++) {
							for(x=startx-1;x<endx;x++) {
deuce's avatar
deuce committed
449 450 451 452 453 454 455 456 457 458
								size_t outlen;
								uint8_t *utf8str;
								char ch;

								ch = screen->vmem[(y*api->scrn_width+x)].ch ? screen->vmem[(y*api->scrn_width+x)].ch : ' ';
								utf8str = cp_to_utf8(conio_fontdata[screen->vmem[(y*api->scrn_width+x)].font].cp, &ch, 1, &outlen);
								if (utf8str == NULL)
									continue;
								memcpy(copybuf + outpos, utf8str, outlen);
								outpos += outlen;
459
							}
460 461 462
							#ifdef _WIN32
								copybuf[outpos++]='\r';
							#endif
463 464 465 466
							copybuf[outpos++]='\n';
						}
						copybuf[outpos]=0;
						copytext(copybuf, strlen(copybuf));
deuce's avatar
deuce committed
467
						free(copybuf);
468 469 470
						restorescreen(screen);
						freescreen(screen);
						freescreen(sbuffer);
471 472 473 474
						return;
				}
				break;
			default:
475 476 477
				restorescreen(screen);
				freescreen(screen);
				freescreen(sbuffer);
478 479 480 481 482 483
				ungetch(key);
				return;
		}
	}
}

484 485 486 487 488 489 490 491 492
static int uifc_getmouse(struct mouse_event *mevent)
{
	mevent->startx=0;
	mevent->starty=0;
	mevent->event=0;
	if(api->mode&UIFC_MOUSE) {
		getmouse(mevent);
		if(mevent->event==CIOLIB_BUTTON_3_CLICK)
			return(ESC);
493 494 495 496
		if(mevent->event==CIOLIB_BUTTON_1_DRAG_START) {
			docopy();
			return(0);
		}
497 498 499 500 501 502 503 504 505 506 507 508
		if(mevent->starty==api->buttony) {
			if(mevent->startx>=api->exitstart
					&& mevent->startx<=api->exitend
					&& mevent->event==CIOLIB_BUTTON_1_CLICK) {
				return(ESC);
			}
			if(mevent->startx>=api->helpstart
					&& mevent->startx<=api->helpend
					&& mevent->event==CIOLIB_BUTTON_1_CLICK) {
				return(CIO_KEY_F(1));
			}
		}
deuce's avatar
deuce committed
509 510 511 512
		if (mevent->event == CIOLIB_BUTTON_4_PRESS)
			return(CIO_KEY_UP);
		if (mevent->event == CIOLIB_BUTTON_5_PRESS)
			return(CIO_KEY_DOWN);
513 514 515 516 517
		return(0);
	}
	return(-1);
}

518 519
void uifcbail(void)
{
deuce's avatar
deuce committed
520 521
	int i;

522 523
	_setcursortype(_NORMALCURSOR);
	textattr(LIGHTGRAY);
524
	uifc_mouse_disable();
525
	suspendciolib();
deuce's avatar
deuce committed
526 527 528
	FREE_AND_NULL(blk_scrn);
	FREE_AND_NULL(tmp_buffer);
	FREE_AND_NULL(tmp_buffer2);
529
	api->initialized=FALSE;
deuce's avatar
deuce committed
530 531
	for(i=0; i< MAX_BUFS; i++)
		FREE_AND_NULL(sav[i].buf);
532 533 534 535 536 537 538 539
}

/****************************************************************************/
/* Clear screen, fill with background attribute, display application title.	*/
/* Returns 0 on success.													*/
/****************************************************************************/
int uscrn(char *str)
{
540
    textattr(api->bclr|(api->cclr<<4));
541
    gotoxy(1,1);
542
    clreol();
543 544
    gotoxy(3,1);
	cputs(str);
545
    if(!vmem_puttext(1,2,api->scrn_width,api->scrn_len,blk_scrn))
546 547
        return(-1);
    gotoxy(1,api->scrn_len+1);
548
    clreol();
549
	reset_dynamic();
550
	setname(str);
551 552 553 554 555 556 557
    return(0);
}

/****************************************************************************/
/****************************************************************************/
static void scroll_text(int x1, int y1, int x2, int y2, int down)
{
558
	vmem_gettext(x1,y1,x2,y2,tmp_buffer2);
559
	if(down)
560
		vmem_puttext(x1,y1+1,x2,y2,tmp_buffer2);
561
	else
562
		vmem_puttext(x1,y1,x2,y2-1,tmp_buffer2+(((x2-x1)+1)));
563 564 565 566 567 568
}

/****************************************************************************/
/* Updates time in upper left corner of screen with current time in ASCII/  */
/* Unix format																*/
/****************************************************************************/
569
static void timedisplay(BOOL force)
570 571
{
	static time_t savetime;
572
	static int savemin;
573
	time_t now;
574
	struct tm gm;
575
	int old_hold;
576 577

	now=time(NULL);
578 579
	localtime_r(&now, &gm);
	if(force || savemin != gm.tm_min || difftime(now,savetime)>=60) {
580 581
		old_hold=hold_update;
		hold_update=FALSE;
582
		uprintf(api->scrn_width-25,1,api->bclr|(api->cclr<<4),utimestr(&now));
583
		hold_update=old_hold;
584
		savetime=now;
585
		savemin = gm.tm_min;
586 587 588 589 590 591
	}
}

/****************************************************************************/
/* Truncates white-space chars off end of 'str'								*/
/****************************************************************************/
592
static void truncspctrl(char *str)
593 594 595 596
{
	uint c;

	c=strlen(str);
deuce's avatar
deuce committed
597
	while(c && (uchar)str[c-1]<=' ') c--;
598 599 600 601
	if(str[c]!=0)	/* don't write to string constants */
		str[c]=0;
}

602
static void
603
inactive_win(struct vmem_cell *buf, int left, int top, int right, int bottom, int y, int hbrdrsize, uchar cclr, uchar lclr, uchar hclr, int btop)
604 605 606 607 608 609 610 611
{
	int width = right - left + 1;
	int height = bottom - top + 1;
	int i, j;

	vmem_gettext(left, top, right, bottom, buf);
	for (i=0; i < (width * height); i++)
		set_vmem_attr(&buf[i], lclr | (cclr<<4));
612 613
	j=(((y-btop)*width))+3+((width-hbrdrsize-2));
	for(i=(((y-btop)*width))+3;i<j;i++)
614 615 616 617 618
		set_vmem_attr(&buf[i], hclr|(cclr<<4));

	vmem_puttext(left, top, right, bottom, buf);
}

619 620 621 622
/****************************************************************************/
/* General menu function, see uifc.h for details.							*/
/****************************************************************************/
int ulist(int mode, int left, int top, int width, int *cur, int *bar
deuce's avatar
deuce committed
623
	, char *initial_title, char **option)
624
{
deuce's avatar
deuce committed
625
	struct vmem_cell *ptr, *win, shade[MAX_LINES*2], line[MAX_COLS];
rswindell's avatar
rswindell committed
626
	static char search[MAX_OPLN] = "";
627 628 629
	int height,y;
	int i,j,opts=0,s=0; /* s=search index into options */
	int	is_redraw=0;
630 631 632 633 634 635 636 637 638 639
	int s_top=SCRN_TOP;
	int s_left=SCRN_LEFT;
	int s_right=SCRN_RIGHT;
	int s_bottom=api->scrn_len-3;
	int hbrdrsize=2;
	int lbrdrwidth=1;
	int rbrdrwidth=1;
	int vbrdrsize=4;
	int tbrdrwidth=3;
	int bbrdrwidth=1;
640
	int title_len;
641
	int tmpcur=0;
642
	struct mouse_event mevnt;
643
	char	*title=NULL;
644
	int	a,b,c,longopt;
645
	int	optheight=0;
646
	int gotkey;
647 648
	uchar	hclr,lclr,bclr,cclr,lbclr;

649
	if(cur==NULL) cur=&tmpcur;
650
	api->exit_flags = 0;
651 652 653 654 655 656 657 658 659 660 661 662
	hclr=api->hclr;
	lclr=api->lclr;
	bclr=api->bclr;
	cclr=api->cclr;
	lbclr=api->lbclr;
	if(mode & WIN_INACT) {
		bclr=api->cclr;
		hclr=api->lclr;
		lclr=api->lclr;
		cclr=api->cclr;
		lbclr=(api->cclr<<4)|api->hclr;
	}
663 664
	title=strdup(initial_title==NULL?"":initial_title);

665 666
	if(!(api->mode&UIFC_NHM))
		uifc_mouse_disable();
667 668

	title_len=strlen(title);
669 670 671

	if(mode&WIN_FAT) {
		s_top=1;
672
		s_left=2;
673 674
		s_right=api->scrn_width-3;  /* Leave space for the shadow */
		s_bottom=api->scrn_len-1;   /* Leave one for the shadow */
675
	}
676 677 678 679 680 681 682 683
	if(mode&WIN_NOBRDR) {
		hbrdrsize=0;
		vbrdrsize=0;
		lbrdrwidth=0;
		rbrdrwidth=0;
		tbrdrwidth=0;
		bbrdrwidth=0;
	}
684 685 686 687 688 689 690 691
	/* Count the options */
	while (option != NULL && opts < MAX_OPTS) {
		if (option[opts] == NULL || option[opts][0] == 0)
			break;
		else opts++;
	}
	if (mode&WIN_XTR && opts<MAX_OPTS)
		opts++;
692

693
	/* Sanity-check the savnum */
694 695
	if(mode&WIN_SAV && api->savnum>=MAX_BUFS-1)
		putch(7);
696

697 698
	api->help_available = (api->helpbuf!=NULL || api->helpixbfile[0]!=0);

699
	/* Create the status bar/bottom-line */
700
	int bline = mode;
701 702
	if (api->bottomline != NULL) {
		if ((mode&(WIN_XTR | WIN_PASTEXTR)) == WIN_XTR && (*cur) == opts - 1)
703
			api->bottomline(bline & ~WIN_PASTE);
704 705 706
		else
			api->bottomline(bline);
	}
707
	optheight=opts+vbrdrsize;
708
	height=optheight;
709 710 711
	if(mode&WIN_FIXEDHEIGHT) {
		height=api->list_height;
	}
712 713
	if(top+height>s_bottom)
		height=(s_bottom)-top;
714 715
	if(optheight>height)
		optheight=height;
716 717
	if(!width || width<title_len+hbrdrsize+2) {
		width=title_len+hbrdrsize+2;
718
		for(i=0;i<opts;i++) {
719
			if(option[i]!=NULL) {
720
				truncspctrl(option[i]);
721 722 723
				if((j=strlen(option[i])+hbrdrsize+2+1)>width)
					width=j;
			}
724 725
		}
	}
726
	/* Determine minimum widths here to accommodate mouse "icons" in border */
727
	if(!(mode&WIN_NOBRDR) && api->mode&UIFC_MOUSE) {
728
		if(api->help_available && width<8)
729 730 731 732
			width=8;
		else if(width<5)
			width=5;
	}
733 734
	if(width>(s_right+1)-s_left) {
		width=(s_right+1)-s_left;
735 736 737 738 739
		if(title_len>(width-hbrdrsize-2)) {
			*(title+width-hbrdrsize-2-3)='.';
			*(title+width-hbrdrsize-2-2)='.';
			*(title+width-hbrdrsize-2-1)='.';
			*(title+width-hbrdrsize-2)=0;
740
			title_len=strlen(title);
741
		}
742
	}
743
	if(mode&WIN_L2R)
deuce's avatar
deuce committed
744
		left=(s_right-s_left-width+1)/2;
745
	else if(mode&WIN_RHT)
746
		left=s_right-(width+hbrdrsize+2+left);
747
	if(mode&WIN_T2B)
deuce's avatar
deuce committed
748
		top=(api->scrn_len-height+1)/2-2;
749
	else if(mode&WIN_BOT)
750
		top=s_bottom-height-top;
751 752
	if(left<0)
		left=0;
753 754
	if(top<0)
		top=0;
755 756

	/* Dynamic Menus */
757
	if(mode&WIN_DYN
758 759 760
			&& cur != NULL
			&& bar != NULL
			&& last_menu_cur==cur
761 762
			&& last_menu_bar==bar
			&& save_menu_cur==*cur
763
			&& save_menu_bar==*bar
deuce's avatar
deuce committed
764
			&& save_menu_opts==opts) {
deuce's avatar
deuce committed
765
		is_redraw=1;
766 767
	}

768 769 770 771
	if(mode&WIN_DYN && mode&WIN_REDRAW)
		is_redraw=1;
	if(mode&WIN_DYN && mode&WIN_NODRAW)
		is_redraw=0;
772

773
	if(mode&WIN_ORG && !(mode&WIN_SAV)) {		/* Clear all save buffers on WIN_ORG */
deuce's avatar
deuce committed
774 775
		for(i=0; i< MAX_BUFS; i++)
			FREE_AND_NULL(sav[i].buf);
776
		api->savnum=0;
deuce's avatar
deuce committed
777 778
	}

779
	if(mode&WIN_SAV) {
780 781 782 783 784 785
		/* Check if this screen (by cur/bar) is already saved */
		for(i=0; i<MAX_BUFS; i++) {
			if(sav[i].buf!=NULL) {
				if(cur==sav[i].cur && bar==sav[i].bar) {
					/* Yes, it is... */
					for(j=api->savnum-1; j>i; j--) {
786
						/* Restore old screens */
deuce's avatar
deuce committed
787 788
						vmem_puttext(sav[j].left,sav[j].top,sav[j].right,sav[j].bot
							,(void *)sav[j].buf);	/* put original window back */
789 790 791 792 793 794 795 796 797 798 799 800 801 802 803
						FREE_AND_NULL(sav[j].buf);
					}
					api->savnum=i;
				}
			}
		}
		/* savnum not the next one - must be a dynamic window or we popped back up the stack */
		if(sav[api->savnum].buf != NULL) {
			/* Is this even the right window? */
			if(sav[api->savnum].cur==cur
					&& sav[api->savnum].bar==bar) {
				if((sav[api->savnum].left!=s_left+left
					|| sav[api->savnum].top!=s_top+top
					|| sav[api->savnum].right!=s_left+left+width+1
					|| sav[api->savnum].bot!=s_top+top+height)) { /* dimensions have changed */
deuce's avatar
deuce committed
804 805
					vmem_puttext(sav[api->savnum].left,sav[api->savnum].top,sav[api->savnum].right,sav[api->savnum].bot
						,(void *)sav[api->savnum].buf);	/* put original window back */
806
					FREE_AND_NULL(sav[api->savnum].buf);
deuce's avatar
deuce committed
807
					if ((sav[api->savnum].buf = malloc((width + 3) * (height + 2) * sizeof(struct vmem_cell)))==NULL) {
808
						cprintf("UIFC line %d: error allocating %u bytes."
809
							,__LINE__,(width+3)*(height+2)*sizeof(struct vmem_cell));
810
						free(title);
811 812
						if(!(api->mode&UIFC_NHM))
							uifc_mouse_enable();
813 814
						return(-1);
					}
deuce's avatar
deuce committed
815 816
					vmem_gettext(s_left+left,s_top+top,s_left+left+width+1
						,s_top+top+height,(void *)sav[api->savnum].buf);	  /* save again */
817 818 819 820 821 822 823 824 825 826 827 828
					sav[api->savnum].left=s_left+left;
					sav[api->savnum].top=s_top+top;
					sav[api->savnum].right=s_left+left+width+1;
					sav[api->savnum].bot=s_top+top+height;
					sav[api->savnum].cur=cur;
					sav[api->savnum].bar=bar;
				}
			}
			else {
				/* Find something available... */
				while(sav[api->savnum].buf!=NULL)
					api->savnum++;
829 830
			}
		}
831
		else {
deuce's avatar
deuce committed
832
			if((sav[api->savnum].buf=malloc((width+3)*(height+2)*sizeof(struct vmem_cell)))==NULL) {
833
				cprintf("UIFC line %d: error allocating %u bytes."
834
					,__LINE__,(width+3)*(height+2)*sizeof(struct vmem_cell));
835
				free(title);
836 837
				if(!(api->mode&UIFC_NHM))
					uifc_mouse_enable();
838
				return(-1);
839
			}
deuce's avatar
deuce committed
840 841
			vmem_gettext(s_left+left,s_top+top,s_left+left+width+1
				,s_top+top+height,(void *)sav[api->savnum].buf);
842 843 844 845
			sav[api->savnum].left=s_left+left;
			sav[api->savnum].top=s_top+top;
			sav[api->savnum].right=s_left+left+width+1;
			sav[api->savnum].bot=s_top+top+height;
846 847
			sav[api->savnum].cur=cur;
			sav[api->savnum].bar=bar;
848
		}
849 850 851 852 853
	}

	if(!is_redraw) {
		if(mode&WIN_ORG) { /* Clear around menu */
			if(top)
854
				vmem_puttext(1,2,api->scrn_width,s_top+top-1,blk_scrn);
855
			if((unsigned)(s_top+height+top)<=api->scrn_len)
856
				vmem_puttext(1,s_top+height+top,api->scrn_width,api->scrn_len,blk_scrn);
857
			if(left)
858
				vmem_puttext(1,s_top+top,s_left+left-1,s_top+height+top
859
					,blk_scrn);
860
			if(s_left+left+width<=s_right)
861
				vmem_puttext(s_left+left+width,s_top+top,/* s_right+2 */api->scrn_width
862
					,s_top+height+top,blk_scrn);
863
		}
deuce's avatar
deuce committed
864
		ptr=tmp_buffer;
865
		if(!(mode&WIN_NOBRDR)) {
866
			set_vmem(ptr++, api->chars->list_top_left, hclr|(bclr<<4), 0);
867

868
			if(api->mode&UIFC_MOUSE) {
869
				set_vmem(ptr++, api->chars->button_left, hclr|(bclr<<4), 0);
870
				/* *(ptr++)=''; */
871 872
				set_vmem(ptr++, api->chars->close_char, lclr|(bclr<<4), 0);
				set_vmem(ptr++, api->chars->button_right, hclr|(bclr<<4), 0);
873
				i=3;
874
				if(api->help_available) {
875 876 877
					set_vmem(ptr++, api->chars->button_left, hclr|(bclr<<4), 0);
					set_vmem(ptr++, api->chars->help_char, lclr|(bclr<<4), 0);
					set_vmem(ptr++, api->chars->button_right, hclr|(bclr<<4), 0);
878 879
					i+=3;
				}
880 881 882 883 884 885 886 887 888
				api->buttony=s_top+top;
				api->exitstart=s_left+left+1;
				api->exitend=s_left+left+3;
				api->helpstart=s_left+left+4;
				api->helpend=s_left+left+6;
			}
			else
				i=0;

889 890 891 892
			for(;i<width-2;i++)
				set_vmem(ptr++, api->chars->list_top, hclr|(bclr<<4), 0);
			set_vmem(ptr++, api->chars->list_top_right, hclr|(bclr<<4), 0);
			set_vmem(ptr++, api->chars->list_left, hclr|(bclr<<4), 0);
893 894 895
			a=title_len;
			b=(width-a-1)/2;
			for(i=0;i<b;i++) {
896
				set_vmem(ptr++, ' ', hclr|(bclr<<4), 0);
897
			}
898 899 900 901 902 903 904 905 906
			for(i=0;i<a;i++)
				set_vmem(ptr++, title[i], hclr|(bclr<<4), 0);
			for(i=0;i<width-(a+b)-2;i++)
				set_vmem(ptr++, ' ', hclr|(bclr<<4), 0);
			set_vmem(ptr++, api->chars->list_right, hclr|(bclr<<4), 0);
			set_vmem(ptr++, api->chars->list_separator_left, hclr|(bclr<<4), 0);
			for(i=0;i<width-2;i++)
				set_vmem(ptr++, api->chars->list_horizontal_separator, hclr|(bclr<<4), 0);
			set_vmem(ptr++, api->chars->list_separator_right, hclr|(bclr<<4), 0);
907 908 909 910 911 912
		}

		if((*cur)>=opts)
			(*cur)=opts-1;			/* returned after scrolled */

		if(!bar) {
913 914 915 916
			if((*cur)>height-vbrdrsize-1)
				(*cur)=height-vbrdrsize-1;
			if((*cur)>opts-1)
				(*cur)=opts-1;
917
			i=0;
918 919 920 921
		}
		else {
			if((*bar)>=opts)
				(*bar)=opts-1;
922 923
			if((*bar)>height-vbrdrsize-1)
				(*bar)=height-vbrdrsize-1;
924
			if((*cur)==opts-1)
925 926 927
				(*bar)=height-vbrdrsize-1;
			if((*bar)>opts-1)
				(*bar)=opts-1;
928 929 930
			if((*bar)<0)
				(*bar)=0;
			i=(*cur)-(*bar);
Deucе's avatar
Deucе committed
931 932 933 934
			if (i < 0) {
				*bar += i;
				i = 0;
			}
935
			if(i+(height-vbrdrsize-1)>=opts) {
Deucе's avatar