putmsg.cpp 14.9 KB
Newer Older
1
/* Synchronet message/menu display routine */
2
// vi: tabstop=4
3

4
/* $Id: putmsg.cpp,v 1.68 2020/05/11 05:03:47 rswindell Exp $ */
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
 *																			*
 * 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.					*
 * See the GNU General Public License for more details: gpl.txt or			*
 * http://www.fsf.org/copyleft/gpl.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.	*
 ****************************************************************************/

#include "sbbs.h"
sbbs's avatar
sbbs committed
38
#include "wordwrap.h"
rswindell's avatar
rswindell committed
39
#include "utf8.h"
40
#include "zmodem.h"
41
#include "petdefs.h"
42
43

/****************************************************************************/
44
/* Outputs a NULL terminated string with @-code parsing,                    */
45
/* checking for message aborts, pauses, ANSI escape and ^A sequences.		*/
46
/* Changes local text attributes if necessary.                              */
47
48
49
/* Returns the last char of the buffer access.. 0 if not aborted.           */
/* If P_SAVEATR bit is set in mode, the attributes set by the message       */
/* will be the current attributes after the message is displayed, otherwise */
50
51
/* the attributes prior to displaying the message are always restored.      */
/* Stops parsing/displaying upon CTRL-Z (only in P_CPM_EOF mode).           */
52
/****************************************************************************/
53
char sbbs_t::putmsg(const char *buf, long mode, long org_cols, JSObject* obj)
54
{
rswindell's avatar
rswindell committed
55
	uint 	tmpatr;
56
57
	ulong 	orgcon=console;
	ulong	sys_status_sav=sys_status;
58
	enum output_rate output_rate = cur_output_rate;
59

60
	attr_sp=0;	/* clear any saved attributes */
61
62
63
	tmpatr=curatr;	/* was lclatr(-1) */
	if(!(mode&P_SAVEATR))
		attr(LIGHTGRAY);
64
65
	if(mode&P_NOPAUSE)
		sys_status|=SS_PAUSEOFF;
Rob Swindell's avatar
Rob Swindell committed
66

67
	char ret = putmsgfrag(buf, mode, org_cols, obj);
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
	if(!(mode&P_SAVEATR)) {
 		console=orgcon;
		attr(tmpatr);
	}
	if(!(mode&P_NOATCODES) && cur_output_rate != output_rate)
		set_output_rate(output_rate);

	if(mode&P_PETSCII)
		outcom(PETSCII_UPPERLOWER);

	attr_sp=0;	/* clear any saved attributes */

	/* Restore original settings of Forced Pause On/Off */
	sys_status&=~(SS_PAUSEOFF|SS_PAUSEON);
	sys_status|=(sys_status_sav&(SS_PAUSEOFF|SS_PAUSEON));
	return(ret);
}

// Print a message fragment, doesn't save/restore any console states (e.g. attributes, auto-pause)
87
char sbbs_t::putmsgfrag(const char* buf, long& mode, long org_cols, JSObject* obj)
88
89
90
91
{
	char 	tmp2[256],tmp3[128];
	char*	str=(char*)buf;
	uchar	exatr=0;
92
	char	mark = '\0';
93
	int 	i;
94
	long	col = column;
95
96
97
98
99
100
101
	ulong	l=0;
	uint	lines_printed = 0;
	struct mouse_hotspot hot_spot = {0};

	hot_attr = 0;
	hungry_hotspots = true;
	str = auto_utf8(str, mode);
102
103
	size_t len = strlen(str);

104
	long term = term_supports();
105
106
	if(!(mode&P_NOATCODES) && memcmp(str, "@WRAPOFF@", 9) == 0) {
		mode &= ~P_WORDWRAP;
107
		l += 9;
rswindell's avatar
rswindell committed
108
	}
109
	if(mode&P_WORDWRAP) {
110
		char* wrapoff = NULL;
111
		if(!(mode&P_NOATCODES)) {
112
113
114
			wrapoff = strstr((char*)str+l, "@WRAPOFF@");
			if(wrapoff != NULL)
				*wrapoff = 0;
115
		}
116
		char *wrapped;
rswindell's avatar
rswindell committed
117
118
		if(org_cols < TERM_COLS_MIN)
			org_cols = TERM_COLS_DEFAULT;
119
		if((wrapped=::wordwrap((char*)str+l, cols - 1, org_cols - 1, /* handle_quotes: */TRUE
120
			,/* is_utf8: */INT_TO_BOOL(mode&P_UTF8))) == NULL)
121
			errormsg(WHERE,ERR_ALLOC,"wordwrap buffer",0);
122
123
		else {
			truncsp_lines(wrapped);
124
			mode &= ~P_WORDWRAP;
125
			putmsgfrag(wrapped, mode);
126
127
			free(wrapped);
			l=strlen(str);
128
			if(wrapoff != NULL)
129
				l += 9;	// Skip "<NUL>WRAPOFF@"
130
		}
131
132
	}

133
	while(l < len && (mode&P_NOABORT || !msgabort()) && online) {
rswindell's avatar
rswindell committed
134
135
136
137
138
139
		switch(str[l]) {
			case '\r':
			case '\n':
			case FF:
			case CTRL_A:
				break;
140
141
142
143
144
145
			case ZHEX:
				if(l && str[l - 1] == ZDLE) {
					l++;
					continue;
				}
				// fallthrough
rswindell's avatar
rswindell committed
146
			default: // printing char
147
				if((mode & P_INDENT) && column < col)
148
					cursor_right(col - column);
149
				else if((mode&P_TRUNCATE) && column >= (cols - 1)) {
rswindell's avatar
rswindell committed
150
151
					l++;
					continue;
152
				} else if(mode&P_WRAP) {
rswindell's avatar
rswindell committed
153
154
155
156
157
158
159
160
161
162
163
					if(org_cols) {
						if(column > (org_cols - 1)) {
							CRLF;
						}
					} else {
						if(column >= (cols - 1)) {
							CRLF;
						}
					}
				}
				break;
rswindell's avatar
rswindell committed
164
		}
165
		if(mode & P_MARKUP) {
166
167
168
169
170
			const char* marks = "*/_#";
			if(((mark == 0) && strchr(marks, str[l]) != NULL) || str[l] == mark) {
				char* next = NULL;
				char following = '\0';
				if(mark == 0) {
171
					next = strchr(str + l + 1, str[l]);
172
173
174
					if(next != NULL)
						following = *(next + 1);
				}
175
				char *blank = strstr(str + l + 1, "\n\r");
176
177
178
				if(((l < 1 || ((IS_WHITESPACE(str[l - 1]) || IS_PUNCTUATION(str[l - 1])) && strchr(marks, str[l - 1]) == NULL))
						&& IS_ALPHANUMERIC(str[l + 1]) && mark == 0 && next != NULL
						&& (following == '\0' || IS_WHITESPACE(following) || (IS_PUNCTUATION(following) && strchr(marks, following) == NULL))
179
180
						&& (blank == NULL || next < blank))
					|| str[l] == mark) {
181
182
183
184
					if(mark == 0)
						mark = str[l];
					else {
						mark = 0;
185
						if(!(mode & P_HIDEMARKS))
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
							outchar(str[l]);
					}
					switch(str[l]) {
						case '*':
							attr(curatr ^ HIGH);
							break;
						case '/':
							attr(curatr ^ BLINK);
							break;							
						case '_':
							attr(curatr ^ (HIGH|BLINK));
							break;
						case '#':
							attr(((curatr&0x0f) << 4) | ((curatr&0xf0) >> 4));
							break;
					}
202
					if(mark != 0 && !(mode & P_HIDEMARKS))
203
204
205
206
207
208
						outchar(str[l]);
					l++;
					continue;
				}
			}
		}
209
		if(str[l]==CTRL_A && str[l+1]!=0) {
210
			if(str[l+1]=='"' && !(sys_status&SS_NEST_PF) && !(mode&P_NOATCODES)) {  /* Quote a file */
211
212
				l+=2;
				i=0;
213
				while(i<(int)sizeof(tmp2)-1 && IS_PRINTABLE(str[l]) && str[l]!='\\' && str[l]!='/')
214
					tmp2[i++]=str[l++];
215
216
217
218
219
				if(i > 0) {
					tmp2[i]=0;
					sys_status|=SS_NEST_PF; 	/* keep it only one message deep! */
					SAFEPRINTF2(tmp3,"%s%s",cfg.text_dir,tmp2);
					printfile(tmp3,0);
220
					sys_status&=~SS_NEST_PF;
221
				}
rswindell's avatar
rswindell committed
222
			}
223
224
			else if(str[l+1] == 'Z')	/* Ctrl-AZ==EOF (uppercase 'Z' only) */
				break;
225
			else if(str[l + 1] == '~') {
226
				l += 2;
227
228
229
230
				if(str[l] >= ' ')
					add_hotspot(str[l], /* hungry: */true);
				else
					add_hotspot('\r', /* hungry: */true);
231
			}
232
			else if(str[l + 1] == '`' && str[l + 2] >= ' ') {
233
				add_hotspot(str[l + 2], /* hungry: */false);
234
235
				l += 2;
			}
236
			else {
rswindell's avatar
rswindell committed
237
				bool was_tos = (row == 0);
238
				ctrl_a(str[l+1]);
rswindell's avatar
rswindell committed
239
				if(row == 0 && !was_tos && (sys_status&SS_ABORT) && !lines_printed)	/* Aborted at (auto) pause prompt (e.g. due to CLS)? */
240
241
					sys_status &= ~SS_ABORT;				/* Clear the abort flag (keep displaying the msg/file) */
				l+=2;
242
			}
rswindell's avatar
rswindell committed
243
		}
244
		else if(!(mode&P_NOXATTRS)
245
			&& (cfg.sys_misc&SM_PCBOARD) && str[l]=='@' && str[l+1]=='X'
246
			&& IS_HEXDIGIT(str[l+2]) && IS_HEXDIGIT(str[l+3])) {
247
			sprintf(tmp2,"%.2s",str+l+2);
rswindell's avatar
rswindell committed
248
249
250
251
252
253
254
255
256
257
258
259
260
261
			ulong val = ahtoul(tmp2);
			// @X00 saves the current color and @XFF restores that saved color
			static uchar save_attr;
			switch(val) {
				case 0x00:
					save_attr = curatr;
					break;
				case 0xff:
					attr(save_attr);
					break;
				default:
					attr(val);
					break;
			}
262
			exatr=1;
263
			l+=4;
rswindell's avatar
rswindell committed
264
		}
265
		else if(!(mode&P_NOXATTRS)
266
			&& (cfg.sys_misc&SM_WILDCAT) && str[l]=='@' && str[l+3]=='@'
267
			&& IS_HEXDIGIT(str[l+1]) && IS_HEXDIGIT(str[l+2])) {
268
269
270
			sprintf(tmp2,"%.2s",str+l+1);
			attr(ahtoul(tmp2));
			// exatr=1;
271
			l+=4;
rswindell's avatar
rswindell committed
272
		}
273
		else if(!(mode&P_NOXATTRS)
274
275
			&& (cfg.sys_misc&SM_RENEGADE) && str[l]=='|' && IS_DIGIT(str[l+1])
			&& IS_DIGIT(str[l+2]) && !(useron.misc&RIP)) {
276
277
			sprintf(tmp2,"%.2s",str+l+1);
			i=atoi(tmp2);
rswindell's avatar
rswindell committed
278
			if(i>=16) { 				/* setting background */
279
280
				i-=16;
				i<<=4;
rswindell's avatar
rswindell committed
281
				i|=(curatr&0x0f);		/* leave foreground alone */
282
			}
283
284
285
286
			else
				i|=(curatr&0xf0);		/* leave background alone */
			attr(i);
			exatr=1;
287
			l+=3;	/* Skip |xx */
288
		}
289
		else if(!(mode&P_NOXATTRS)
290
			&& (cfg.sys_misc&SM_CELERITY) && str[l]=='|' && IS_ALPHA(str[l+1])
291
			&& !(useron.misc&RIP)) {
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
			switch(str[l+1]) {
				case 'k':
					attr((curatr&0xf0)|BLACK);
					break;
				case 'b':
					attr((curatr&0xf0)|BLUE);
					break;
				case 'g':
					attr((curatr&0xf0)|GREEN);
					break;
				case 'c':
					attr((curatr&0xf0)|CYAN);
					break;
				case 'r':
					attr((curatr&0xf0)|RED);
					break;
				case 'm':
					attr((curatr&0xf0)|MAGENTA);
					break;
				case 'y':
					attr((curatr&0xf0)|YELLOW);
					break;
				case 'w':
					attr((curatr&0xf0)|LIGHTGRAY);
					break;
				case 'd':
					attr((curatr&0xf0)|BLACK|HIGH);
					break;
				case 'B':
					attr((curatr&0xf0)|BLUE|HIGH);
					break;
				case 'G':
					attr((curatr&0xf0)|GREEN|HIGH);
					break;
				case 'C':
					attr((curatr&0xf0)|CYAN|HIGH);
					break;
				case 'R':
					attr((curatr&0xf0)|RED|HIGH);
					break;
				case 'M':
					attr((curatr&0xf0)|MAGENTA|HIGH);
					break;
				case 'Y':   /* Yellow */
					attr((curatr&0xf0)|YELLOW|HIGH);
					break;
				case 'W':
					attr((curatr&0xf0)|LIGHTGRAY|HIGH);
					break;
341
				case 'S':   /* swap foreground and background - TODO: This sets foreground to BLACK! */
342
					attr((curatr&0x07)<<4);
343
					break;
rswindell's avatar
rswindell committed
344
			}
345
346
			exatr=1;
			l+=2;	/* Skip |x */
rswindell's avatar
rswindell committed
347
		}  /* Skip second digit if it exists */
348
		else if(!(mode&P_NOXATTRS)
349
			&& (cfg.sys_misc&SM_WWIV) && str[l]==CTRL_C && IS_DIGIT(str[l+1])) {
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
			exatr=1;
			switch(str[l+1]) {
				default:
					attr(LIGHTGRAY);
					break;
				case '1':
					attr(CYAN|HIGH);
					break;
				case '2':
					attr(BROWN|HIGH);
					break;
				case '3':
					attr(MAGENTA);
					break;
				case '4':
365
					attr(LIGHTGRAY|HIGH|BG_BLUE);
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
					break;
				case '5':
					attr(GREEN);
					break;
				case '6':
					attr(RED|HIGH|BLINK);
					break;
				case '7':
					attr(BLUE|HIGH);
					break;
				case '8':
					attr(BLUE);
					break;
				case '9':
					attr(CYAN);
381
					break;
rswindell's avatar
rswindell committed
382
			}
383
			l+=2;
rswindell's avatar
rswindell committed
384
		}
385
		else {
386
			if(!(mode&P_PETSCII) && str[l]=='\n') {
387
388
				if(exatr) 	/* clear at newline for extra attr codes */
					attr(LIGHTGRAY);
389
390
				if(l==0 || str[l-1]!='\r')	/* expand sole LF to CR/LF */
					outchar('\r');
391
				lines_printed++;
392
393
			}

394
			/* ansi escape sequence */
395
			if(outchar_esc >= ansiState_csi) {
396
397
398
				if(str[l]=='A' || str[l]=='B' || str[l]=='H' || str[l]=='J'
					|| str[l]=='f' || str[l]=='u')    /* ANSI anim */
					lncntr=0;			/* so defeat pause */
399
400
				if(str[l]=='"' || str[l]=='c') {
					l++;				/* don't pass on keyboard reassignment or Device Attributes (DA) requests */
401
402
					continue;
				}
rswindell's avatar
rswindell committed
403
			}
404
			if(str[l]=='!' && str[l+1]=='|' && useron.misc&RIP) /* RIP */
405
				lncntr=0;				/* so defeat pause */
406
			if(str[l]=='@' && !(mode&P_NOATCODES)) {
407
408
				if(memcmp(str+l, "@EOF@", 5) == 0)
					break;
rswindell's avatar
rswindell committed
409
410
				if(memcmp(str+l, "@CLEAR@", 7) == 0) {
					CLS;
411
					sys_status &= ~SS_ABORT;
rswindell's avatar
rswindell committed
412
413
414
415
416
417
418
419
					l += 7;
					while(str[l] != 0 && (str[l] == '\r' || str[l] == '\n'))
						l++;
					continue;
				}
				if(memcmp(str+l, "@CENTER@", 8) == 0) {
					l += 8;
					i=0;
420
					while(i<(int)sizeof(tmp2)-1 && str[l] != 0 && str[l] != '\r' && str[l] != '\n')
rswindell's avatar
rswindell committed
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
						tmp2[i++] = str[l++];
					tmp2[i] = 0;
					truncsp(tmp2);
					center(tmp2);
					if(str[l] == '\r')
						l++;
					if(str[l] == '\n')
						l++;
					continue;
				}
				if(memcmp(str+l, "@SYSONLY@", 9) == 0) {
					if(!SYSOP)
						console^=CON_ECHO_OFF;
					l += 9;
					continue;
				}
437
438
				if(memcmp(str+l, "@WORDWRAP@", 10) == 0) {
					l += 10;
439
					mode |= P_WORDWRAP;
440
					return putmsgfrag(str+l, mode, org_cols);
441
				}
442
443
				if(memcmp(str+l, "@QON@", 5) == 0) {	// Allow the file display to be aborted (PCBoard)
					l += 5;
444
					mode &= ~P_NOABORT;
445
446
447
448
					continue;
				}
				if(memcmp(str+l, "@QOFF@", 6) == 0) {	// Do not allow the display of teh file to be aborted (PCBoard)
					l += 6;
449
					mode |= P_NOABORT;
450
451
					continue;
				}
rswindell's avatar
rswindell committed
452
				bool was_tos = (row == 0);
453
 				i=show_atcode((char *)str+l, obj);	/* returns 0 if not valid @ code */
454
				l+=i;					/* i is length of code string */
rswindell's avatar
rswindell committed
455
				if(row > 0 && !was_tos && (sys_status&SS_ABORT) && !lines_printed)	/* Aborted at (auto) pause prompt (e.g. due to CLS)? */
456
					sys_status &= ~SS_ABORT;				/* Clear the abort flag (keep displaying the msg/file) */
457
				if(i)					/* if valid string, go to top */
458
					continue;
rswindell's avatar
rswindell committed
459
			}
460
			if(mode&P_CPM_EOF && str[l]==CTRL_Z)
461
				break;
462
463
464
465
466
467
468
469
			if(hot_attr) {
				if(curatr == hot_attr && str[l] > ' ') {
					hot_spot.y = row;
					if(!hot_spot.minx)
						hot_spot.minx = column;
					hot_spot.maxx = column;
					hot_spot.cmd[strlen(hot_spot.cmd)] = str[l];
				} else if(hot_spot.cmd[0]) {
470
					hot_spot.hungry = hungry_hotspots;
471
472
473
474
					add_hotspot(&hot_spot);
					memset(&hot_spot, 0, sizeof(hot_spot));
				}
			}
475
			size_t skip = sizeof(char);
476
			if(mode&P_PETSCII) {
477
				if(term&PETSCII) {
478
					outcom(str[l]);
479
480
481
					switch(str[l]) {
						case '\r':	// PETSCII "Return" / new-line
							column = 0;
Rob Swindell's avatar
Rob Swindell committed
482
							/* fall-through */
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
						case PETSCII_DOWN:
							lncntr++;
							break;
						case PETSCII_CLEAR:
						case PETSCII_HOME:
							row=0;
							column=0;
							lncntr=0;
							break;
						case PETSCII_BLACK:
						case PETSCII_WHITE:
						case PETSCII_RED:
						case PETSCII_GREEN:
						case PETSCII_BLUE:
						case PETSCII_ORANGE:
						case PETSCII_BROWN:
						case PETSCII_YELLOW:
						case PETSCII_CYAN:
						case PETSCII_LIGHTRED:
						case PETSCII_DARKGRAY:
						case PETSCII_MEDIUMGRAY:
						case PETSCII_LIGHTGREEN:
						case PETSCII_LIGHTBLUE:
						case PETSCII_LIGHTGRAY:
						case PETSCII_PURPLE:
						case PETSCII_UPPERLOWER:
						case PETSCII_UPPERGRFX:
						case PETSCII_FLASH_ON:
						case PETSCII_FLASH_OFF:
						case PETSCII_REVERSE_ON:
						case PETSCII_REVERSE_OFF:
							// No cursor movement
							break;
						default:
							inc_column(1);
							break;
					}
				} else
521
					petscii_to_ansibbs(str[l]);
522
			} else if((str[l]&0x80) && (mode&P_UTF8)) {
523
524
525
				if(term&UTF8)
					outcom(str[l]);
				else
rswindell's avatar
rswindell committed
526
					skip = print_utf8_as_cp437(str + l, len - l);
527
528
			} else {
				uint atr = curatr;
529
				outchar(str[l]);
530
531
532
				if(curatr != atr)	// We assume the attributes are retained between lines
					attr(atr);
			}
533
			l += skip;
534
		}
rswindell's avatar
rswindell committed
535
	}
deuce's avatar
deuce committed
536

537
	return str[l];
538
}