1/*
2** Copyright (C) 1997 Free Software Foundation, Inc.
3**
4** This file is part of TACK.
5**
6** TACK is free software; you can redistribute it and/or modify
7** it under the terms of the GNU General Public License as published by
8** the Free Software Foundation; either version 2, or (at your option)
9** any later version.
10**
11** TACK is distributed in the hope that it will be useful,
12** but WITHOUT ANY WARRANTY; without even the implied warranty of
13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14** GNU General Public License for more details.
15**
16** You should have received a copy of the GNU General Public License
17** along with TACK; see the file COPYING.  If not, write to
18** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19** Boston, MA 02110-1301, USA
20*/
21
22#include <tack.h>
23#include <time.h>
24#include <tic.h>
25
26MODULE_ID("$Id: edit.c,v 1.10 2005/09/17 19:49:16 tom Exp $")
27
28/*
29 * Terminfo edit features
30 */
31static void show_info(struct test_list *, int *, int *);
32static void show_value(struct test_list *, int *, int *);
33static void show_untested(struct test_list *, int *, int *);
34static void show_changed(struct test_list *, int *, int *);
35
36#define SHOW_VALUE	1
37#define SHOW_EDIT	2
38#define SHOW_DELETE	3
39
40struct test_list edit_test_list[] = {
41	{MENU_CLEAR, 0, 0, 0, "i) display current terminfo", show_info, 0},
42	{0, 0, 0, 0, "w) write the current terminfo to a file", save_info, 0},
43	{SHOW_VALUE, 3, 0, 0, "v) show value of a selected cap", show_value, 0},
44	{SHOW_EDIT, 4, 0, 0, "e) edit value of a selected cap", show_value, 0},
45	{SHOW_DELETE, 3, 0, 0, "d) delete string", show_value, 0},
46	{0, 3, 0, 0, "m) show caps that have been modified", show_changed, 0},
47	{MENU_CLEAR + FLAG_CAN_TEST, 0, 0, 0, "c) show caps that can be tested", show_report, 0},
48	{MENU_CLEAR + FLAG_TESTED, 0, 0, 0, "t) show caps that have been tested", show_report, 0},
49	{MENU_CLEAR + FLAG_FUNCTION_KEY, 0, 0, 0, "f) show a list of function keys", show_report, 0},
50	{MENU_CLEAR, 0, 0, 0, "u) show caps defined that can not be tested", show_untested, 0},
51	{MENU_LAST, 0, 0, 0, 0, 0, 0}
52};
53
54static char change_pad_text[MAX_CHANGES][80];
55static struct test_list change_pad_list[MAX_CHANGES] = {
56	{MENU_LAST, 0, 0, 0, 0, 0, 0}
57};
58
59static void build_change_menu(struct test_menu *);
60static void change_one_entry(struct test_list *, int *, int *);
61
62struct test_menu change_pad_menu = {
63	0, 'q', 0,
64	"Select cap name", "change", 0,
65	build_change_menu, change_pad_list, 0, 0, 0
66};
67
68static TERMTYPE	original_term;		/* terminal type description */
69
70static char flag_boolean[BOOLCOUNT];	/* flags for booleans */
71static char flag_numerics[NUMCOUNT];	/* flags for numerics */
72static char flag_strings[STRCOUNT];	/* flags for strings */
73static int xon_index;			/* Subscript for (xon) */
74static int xon_shadow;
75
76static int start_display;		/* the display has just started */
77static int display_lines;		/* number of lines displayed */
78
79/*
80**	send_info_string(str)
81**
82**	Return the terminfo string prefixed by the correct separator
83*/
84static void
85send_info_string(
86	const char *str,
87	int *ch)
88{
89	int len;
90
91	if (display_lines == -1) {
92		return;
93	}
94	len = strlen(str);
95	if (len + char_count + 3 >= columns) {
96		if (start_display == 0) {
97			put_str(",");
98		}
99		put_crlf();
100		if (++display_lines > lines) {
101			ptext("-- more -- ");
102			*ch = wait_here();
103			if (*ch == 'q') {
104				display_lines = -1;
105				return;
106			}
107			display_lines = 0;
108		}
109		if (len >= columns) {
110			/* if the terminal does not (am) then this loses */
111			if (columns) {
112				display_lines += ((strlen(str) + 3) / columns) + 1;
113			}
114			put_str("   ");
115			put_str(str);
116			start_display = 0;
117			return;
118		}
119		ptext("   ");
120	} else
121	if (start_display == 0) {
122		ptext(", ");
123	} else {
124		ptext("   ");
125	}
126	ptext(str);
127	start_display = 0;
128}
129
130/*
131**	show_info(test_list, status, ch)
132**
133**	Display the current terminfo
134*/
135static void
136show_info(
137	struct test_list *t GCC_UNUSED,
138	int *state GCC_UNUSED,
139	int *ch)
140{
141	int i;
142	char buf[1024];
143
144	display_lines = 1;
145	start_display = 1;
146	for (i = 0; i < BOOLCOUNT; i++) {
147		if ((i == xon_index) ? xon_shadow : CUR Booleans[i]) {
148			send_info_string(boolnames[i], ch);
149		}
150	}
151	for (i = 0; i < NUMCOUNT; i++) {
152		if (CUR Numbers[i] >= 0) {
153			sprintf(buf, "%s#%d", numnames[i], CUR Numbers[i]);
154			send_info_string(buf, ch);
155		}
156	}
157	for (i = 0; i < STRCOUNT; i++) {
158		if (CUR Strings[i]) {
159			sprintf(buf, "%s=%s", strnames[i],
160				print_expand(CUR Strings[i]));
161			send_info_string(buf, ch);
162		}
163	}
164	put_newlines(2);
165	*ch = REQUEST_PROMPT;
166}
167
168/*
169**	save_info_string(str, fp)
170**
171**	Write the terminfo string prefixed by the correct separator
172*/
173static void
174save_info_string(
175	const char *str,
176	FILE *fp)
177{
178	int len;
179
180	len = strlen(str);
181	if (len + display_lines >= 77) {
182		if (display_lines > 0) {
183			(void) fprintf(fp, "\n\t");
184		}
185		display_lines = 8;
186	} else
187	if (display_lines > 0) {
188		(void) fprintf(fp, " ");
189		display_lines++;
190	} else {
191		(void) fprintf(fp, "\t");
192		display_lines = 8;
193	}
194	(void) fprintf(fp, "%s,", str);
195	display_lines += len + 1;
196}
197
198/*
199**	save_info(test_list, status, ch)
200**
201**	Write the current terminfo to a file
202*/
203void
204save_info(
205	struct test_list *t,
206	int *state,
207	int *ch)
208{
209	int i;
210	FILE *fp;
211	time_t now;
212	char buf[1024];
213
214	if ((fp = fopen(tty_basename, "w")) == (FILE *) NULL) {
215		(void) sprintf(temp, "can't open: %s", tty_basename);
216		ptextln(temp);
217		generic_done_message(t, state, ch);
218		return;
219	}
220	time(&now);
221	/* Note: ctime() returns a newline at the end of the string */
222	(void) fprintf(fp, "# Terminfo created by TACK for TERM=%s on %s",
223		tty_basename, ctime(&now));
224	(void) fprintf(fp, "%s|%s,\n", tty_basename, longname());
225
226	display_lines = 0;
227	for (i = 0; i < BOOLCOUNT; i++) {
228		if (i == xon_index ? xon_shadow : CUR Booleans[i]) {
229			save_info_string(boolnames[i], fp);
230		}
231	}
232	for (i = 0; i < NUMCOUNT; i++) {
233		if (CUR Numbers[i] >= 0) {
234			sprintf(buf, "%s#%d", numnames[i], CUR Numbers[i]);
235			save_info_string(buf, fp);
236		}
237	}
238	for (i = 0; i < STRCOUNT; i++) {
239		if (CUR Strings[i]) {
240			sprintf(buf, "%s=%s", strnames[i],
241				_nc_tic_expand(CUR Strings[i], TRUE, TRUE));
242			save_info_string(buf, fp);
243		}
244	}
245	(void) fprintf(fp, "\n");
246	(void) fclose(fp);
247	sprintf(temp, "Terminfo saved as file: %s", tty_basename);
248	ptextln(temp);
249}
250
251/*
252**	show_value(test_list, status, ch)
253**
254**	Display the value of a selected cap
255*/
256static void
257show_value(
258	struct test_list *t,
259	int *state GCC_UNUSED,
260	int *ch)
261{
262	struct name_table_entry const *nt;
263	char *s;
264	int n, op, b;
265	char buf[1024];
266	char tmp[1024];
267
268	ptext("enter name: ");
269	read_string(buf, 80);
270	if (buf[0] == '\0' || buf[1] == '\0') {
271		*ch = buf[0];
272		return;
273	}
274	if (line_count + 2 >= lines) {
275		put_clear();
276	}
277	op = t->flags & 255;
278	if ((nt = _nc_find_entry(buf, _nc_info_hash_table))) {
279		switch (nt->nte_type) {
280		case BOOLEAN:
281			if (op == SHOW_DELETE) {
282				if (nt->nte_index == xon_index) {
283					xon_shadow = 0;
284				} else {
285					CUR Booleans[nt->nte_index] = 0;
286				}
287				return;
288			}
289			b = nt->nte_index == xon_index ? xon_shadow :
290				CUR Booleans[nt->nte_index];
291			sprintf(temp, "boolean  %s %s", buf,
292				b ? "True" : "False");
293			break;
294		case STRING:
295			if (op == SHOW_DELETE) {
296				CUR Strings[nt->nte_index] = (char *) 0;
297				return;
298			}
299			if (CUR Strings[nt->nte_index]) {
300				sprintf(temp, "string  %s %s", buf,
301					expand(CUR Strings[nt->nte_index]));
302			} else {
303				sprintf(temp, "undefined string %s", buf);
304			}
305			break;
306		case NUMBER:
307			if (op == SHOW_DELETE) {
308				CUR Numbers[nt->nte_index] = -1;
309				return;
310			}
311			sprintf(temp, "numeric  %s %d", buf,
312				CUR Numbers[nt->nte_index]);
313			break;
314		default:
315			sprintf(temp, "unknown");
316			break;
317		}
318		ptextln(temp);
319	} else {
320		sprintf(temp, "Cap not found: %s", buf);
321		ptextln(temp);
322		return;
323	}
324	if (op != SHOW_EDIT) {
325		return;
326	}
327	if (nt->nte_type == BOOLEAN) {
328		ptextln("Value flipped");
329		if (nt->nte_index == xon_index) {
330			xon_shadow = !xon_shadow;
331		} else {
332			CUR Booleans[nt->nte_index] = !CUR Booleans[nt->nte_index];
333		}
334		return;
335	}
336	ptextln("Enter new value");
337	read_string(buf, sizeof(buf));
338
339	switch (nt->nte_type) {
340	case STRING:
341		_nc_reset_input((FILE *) 0, buf);
342		_nc_trans_string(tmp, tmp + sizeof(tmp));
343		s = (char *)malloc(strlen(tmp) + 1);
344		strcpy(s, tmp);
345		CUR Strings[nt->nte_index] = s;
346		sprintf(temp, "new string value  %s", nt->nte_name);
347		ptextln(temp);
348		ptextln(expand(CUR Strings[nt->nte_index]));
349		break;
350	case NUMBER:
351		if (sscanf(buf, "%d", &n) == 1) {
352			CUR Numbers[nt->nte_index] = n;
353			sprintf(temp, "new numeric value  %s %d",
354				nt->nte_name, n);
355			ptextln(temp);
356		} else {
357			sprintf(temp, "Illegal number: %s", buf);
358			ptextln(temp);
359		}
360		break;
361	default:
362		break;
363	}
364}
365
366/*
367**	get_string_cap_byname(name, long_name)
368**
369**	Given a cap name, find the value
370**	Errors are quietly ignored.
371*/
372char *
373get_string_cap_byname(
374	const char *name,
375	const char **long_name)
376{
377	struct name_table_entry const *nt;
378
379	if ((nt = _nc_find_entry(name, _nc_info_hash_table))) {
380		if (nt->nte_type == STRING) {
381			*long_name = strfnames[nt->nte_index];
382			return (CUR Strings[nt->nte_index]);
383		}
384	}
385	*long_name = "??";
386	return (char *) 0;
387}
388
389/*
390**	get_string_cap_byvalue(value)
391**
392**	Given a capability string, find its position in the data base.
393**	Return the index or -1 if not found.
394*/
395int
396get_string_cap_byvalue(
397	const char *value)
398{
399	int i;
400
401	if (value) {
402		for (i = 0; i < STRCOUNT; i++) {
403			if (CUR Strings[i] == value) {
404				return i;
405			}
406		}
407		/* search for translated strings */
408		for (i = 0; i < TM_last; i++) {
409			if (TM_string[i].value == value) {
410				return TM_string[i].index;
411			}
412		}
413	}
414	return -1;
415}
416
417/*
418**	show_changed(test_list, status, ch)
419**
420**	Display a list of caps that have been changed.
421*/
422static void
423show_changed(
424	struct test_list *t GCC_UNUSED,
425	int *state GCC_UNUSED,
426	int *ch)
427{
428	int i, header = 1, v;
429	const char *a;
430	const char *b;
431	static char title[] = "                     old value   cap  new value";
432	char abuf[1024];
433
434	for (i = 0; i < BOOLCOUNT; i++) {
435		v = (i == xon_index) ? xon_shadow : CUR Booleans[i];
436		if (original_term.Booleans[i] != v) {
437			if (header) {
438				ptextln(title);
439				header = 0;
440			}
441			sprintf(temp, "%30d %6s %d",
442				original_term.Booleans[i], boolnames[i], v);
443			ptextln(temp);
444		}
445	}
446	for (i = 0; i < NUMCOUNT; i++) {
447		if (original_term.Numbers[i] != CUR Numbers[i]) {
448			if (header) {
449				ptextln(title);
450				header = 0;
451			}
452			sprintf(temp, "%30d %6s %d",
453				original_term.Numbers[i], numnames[i],
454				CUR Numbers[i]);
455			ptextln(temp);
456		}
457	}
458	for (i = 0; i < STRCOUNT; i++) {
459		a = original_term.Strings[i] ? original_term.Strings[i] : "";
460		b = CUR Strings[i] ?  CUR Strings[i] : "";
461		if (strcmp(a, b)) {
462			if (header) {
463				ptextln(title);
464				header = 0;
465			}
466			strcpy(abuf, _nc_tic_expand(a, TRUE, TRUE));
467			sprintf(temp, "%30s %6s %s", abuf, strnames[i],
468				_nc_tic_expand(b, TRUE, TRUE));
469			putln(temp);
470		}
471	}
472	if (header) {
473		ptextln("No changes");
474	}
475	put_crlf();
476	*ch = REQUEST_PROMPT;
477}
478
479/*
480**	user_modified()
481**
482**	Return TRUE if the user has modified the terminfo
483*/
484int
485user_modified(void)
486{
487	const char *a, *b;
488	int i, v;
489
490	for (i = 0; i < BOOLCOUNT; i++) {
491		v = (i == xon_index) ? xon_shadow : CUR Booleans[i];
492		if (original_term.Booleans[i] != v) {
493			return TRUE;
494		}
495	}
496	for (i = 0; i < NUMCOUNT; i++) {
497		if (original_term.Numbers[i] != CUR Numbers[i]) {
498			return TRUE;
499		}
500	}
501	for (i = 0; i < STRCOUNT; i++) {
502		a = original_term.Strings[i] ? original_term.Strings[i] : "";
503		b = CUR Strings[i] ?  CUR Strings[i] : "";
504		if (strcmp(a, b)) {
505			return TRUE;
506		}
507	}
508	return FALSE;
509}
510
511/*****************************************************************************
512 *
513 * Maintain the list of capabilities that can be tested
514 *
515 *****************************************************************************/
516
517/*
518**	mark_cap(name, flag)
519**
520**	Mark the cap data base with the flag provided.
521*/
522static void
523mark_cap(
524	char *name,
525	int flag)
526{
527	struct name_table_entry const *nt;
528
529	if ((nt = _nc_find_entry(name, _nc_info_hash_table))) {
530		switch (nt->nte_type) {
531		case BOOLEAN:
532			flag_boolean[nt->nte_index] |= flag;
533			break;
534		case STRING:
535			flag_strings[nt->nte_index] |= flag;
536			break;
537		case NUMBER:
538			flag_numerics[nt->nte_index] |= flag;
539			break;
540		default:
541			sprintf(temp, "unknown cap type (%s)", name);
542			ptextln(temp);
543			break;
544		}
545	} else {
546		sprintf(temp, "Cap not found: %s", name);
547		ptextln(temp);
548		(void) wait_here();
549	}
550}
551
552/*
553**	can_test(name-list, flags)
554**
555**	Scan the name list and get the names.
556**	Enter each name into the can-test data base.
557**	<space> ( and ) may be used as separators.
558*/
559void
560can_test(
561	const char *s,
562	int flags)
563{
564	int ch, j;
565	char name[32];
566
567	if (s) {
568		for (j = 0; (name[j] = ch = *s); s++) {
569			if (ch == ' ' || ch == ')' || ch == '(') {
570				if (j) {
571					name[j] = '\0';
572					mark_cap(name, flags);
573				}
574				j = 0;
575			} else {
576				j++;
577			}
578		}
579		if (j) {
580			mark_cap(name, flags);
581		}
582	}
583}
584
585/*
586**	cap_index(name-list, index-list)
587**
588**	Scan the name list and return a list of indexes.
589**	<space> ( and ) may be used as separators.
590**	This list is terminated with -1.
591*/
592void
593cap_index(
594	const char *s,
595	int *inx)
596{
597	struct name_table_entry const *nt;
598	int ch, j;
599	char name[32];
600
601	if (s) {
602		for (j = 0; ; s++) {
603			name[j] = ch = *s;
604			if (ch == ' ' || ch == ')' || ch == '(' || ch == 0) {
605				if (j) {
606					name[j] = '\0';
607					if ((nt = _nc_find_entry(name,
608						_nc_info_hash_table)) &&
609						(nt->nte_type == STRING)) {
610						*inx++ = nt->nte_index;
611					}
612				}
613				if (ch == 0) {
614					break;
615				}
616				j = 0;
617			} else {
618				j++;
619			}
620		}
621	}
622	*inx = -1;
623}
624
625/*
626**	cap_match(name-list, cap)
627**
628**	Scan the name list and see if the cap is in the list.
629**	Return TRUE if we find an exact match.
630**	<space> ( and ) may be used as separators.
631*/
632int
633cap_match(
634	const char *names,
635	const char *cap)
636{
637	char *s;
638	int c, l, t;
639
640	if (names) {
641		l = strlen(cap);
642		while ((s = strstr(names, cap))) {
643			c = (names == s) ? 0 : *(s - 1);
644			t = s[l];
645			if ((c == 0 || c == ' ' || c == '(') &&
646				(t == 0 || t == ' ' || t == ')')) {
647				return TRUE;
648			}
649			if (t == 0) {
650				break;
651			}
652			names = s + l;
653		}
654	}
655	return FALSE;
656}
657
658/*
659**	show_report(test_list, status, ch)
660**
661**	Display a list of caps that can be tested
662*/
663void
664show_report(
665	struct test_list *t,
666	int *state GCC_UNUSED,
667	int *ch)
668{
669	int i, j, nc, flag;
670	const char *s;
671	const char *nx[BOOLCOUNT + NUMCOUNT + STRCOUNT];
672
673	flag = t->flags & 255;
674	nc = 0;
675	for (i = 0; i < BOOLCOUNT; i++) {
676		if (flag_boolean[i] & flag) {
677			nx[nc++] = boolnames[i];
678		}
679	}
680	for (i = 0; i < NUMCOUNT; i++) {
681		if (flag_numerics[i] & flag) {
682			nx[nc++] = numnames[i];
683		}
684	}
685	for (i = 0; i < STRCOUNT; i++) {
686		if (flag_strings[i] & flag) {
687			nx[nc++] = strnames[i];
688		}
689	}
690	/* sort */
691	for (i = 0; i < nc - 1; i++) {
692		for (j = i + 1; j < nc; j++) {
693			if (strcmp(nx[i], nx[j]) > 0) {
694				s = nx[i];
695				nx[i] = nx[j];
696				nx[j] = s;
697			}
698		}
699	}
700	if (flag & FLAG_FUNCTION_KEY) {
701		ptextln("The following function keys can be tested:");
702	} else
703	if (flag & FLAG_CAN_TEST) {
704		ptextln("The following capabilities can be tested:");
705	} else
706	if (flag & FLAG_TESTED) {
707		ptextln("The following capabilities have been tested:");
708	}
709	put_crlf();
710	for (i = 0; i < nc; i++) {
711		sprintf(temp, "%s ", nx[i]);
712		ptext(temp);
713	}
714	put_newlines(1);
715	*ch = REQUEST_PROMPT;
716}
717
718/*
719**	show_untested(test_list, status, ch)
720**
721**	Display a list of caps that are defined but cannot be tested.
722**	Don't bother to sort this list.
723*/
724static void
725show_untested(
726	struct test_list *t GCC_UNUSED,
727	int *state GCC_UNUSED,
728	int *ch)
729{
730	int i;
731
732	ptextln("Caps that are defined but cannot be tested:");
733	for (i = 0; i < BOOLCOUNT; i++) {
734		if (flag_boolean[i] == 0 && CUR Booleans[i]) {
735			sprintf(temp, "%s ", boolnames[i]);
736			ptext(temp);
737		}
738	}
739	for (i = 0; i < NUMCOUNT; i++) {
740		if (flag_numerics[i] == 0 && CUR Numbers[i] >= 0) {
741			sprintf(temp, "%s ", numnames[i]);
742			ptext(temp);
743		}
744	}
745	for (i = 0; i < STRCOUNT; i++) {
746		if (flag_strings[i] == 0 && CUR Strings[i]) {
747			sprintf(temp, "%s ", strnames[i]);
748			ptext(temp);
749		}
750	}
751	put_newlines(1);
752	*ch = REQUEST_PROMPT;
753}
754
755/*
756**	edit_init()
757**
758**	Initialize the function key data base
759*/
760void
761edit_init(void)
762{
763	int i, j, lc;
764	char *lab;
765	struct name_table_entry const *nt;
766	int label_strings[STRCOUNT];
767
768	_nc_copy_termtype(&original_term, &cur_term->type);
769	for (i = 0; i < BOOLCOUNT; i++) {
770		original_term.Booleans[i] = CUR Booleans[i];
771	}
772	for (i = 0; i < NUMCOUNT; i++) {
773		original_term.Numbers[i] = CUR Numbers[i];
774	}
775	/* scan for labels */
776	for (i = lc = 0; i < STRCOUNT; i++) {
777		original_term.Strings[i] = CUR Strings[i];
778		if (strncmp(strnames[i], "lf", 2) == 0) {
779			flag_strings[i] |= FLAG_LABEL;
780			if (CUR Strings[i]) {
781				label_strings[lc++] = i;
782			}
783		}
784	}
785	/* scan for function keys */
786	for (i = 0; i < STRCOUNT; i++) {
787		if ((strnames[i][0] == 'k') && strcmp(strnames[i], "kmous")) {
788			flag_strings[i] |= FLAG_FUNCTION_KEY;
789			lab = (char *) 0;
790			for (j = 0; j < lc; j++) {
791				if (!strcmp(&strnames[i][1],
792					&strnames[label_strings[j]][1])) {
793					lab = CUR Strings[label_strings[j]];
794					break;
795				}
796			}
797			enter_key(strnames[i], CUR Strings[i], lab);
798		}
799	}
800	/* Lookup the translated strings */
801	for (i = 0; i < TM_last; i++) {
802		if ((nt = _nc_find_entry(TM_string[i].name,
803			_nc_info_hash_table)) && (nt->nte_type == STRING)) {
804			TM_string[i].index = nt->nte_index;
805		} else {
806			sprintf(temp, "TM_string lookup failed for: %s",
807				TM_string[i].name);
808			ptextln(temp);
809		}
810	}
811	if ((nt = _nc_find_entry("xon", _nc_info_hash_table)) != 0) {
812		xon_index = nt->nte_index;
813	}
814	xon_shadow = xon_xoff;
815}
816
817/*
818**	change_one_entry(test_list, status, ch)
819**
820**	Change the padding on the selected cap
821*/
822static void
823change_one_entry(
824	struct test_list *test,
825	int *state,
826	int *chp)
827{
828	struct name_table_entry const *nt;
829	int i, j, x, star, slash,  v, dot, ch;
830	const char *s;
831	char *t, *p;
832	const char *current_string;
833	char buf[1024];
834	char pad[1024];
835
836	i = test->flags & 255;
837	if (i == 255) {
838		/* read the cap name from the user */
839		ptext("enter name: ");
840		read_string(pad, 32);
841		if (pad[0] == '\0' || pad[1] == '\0') {
842			*chp = pad[0];
843			return;
844		}
845		if ((nt = _nc_find_entry(pad, _nc_info_hash_table)) &&
846			(nt->nte_type == STRING)) {
847			x = nt->nte_index;
848			current_string = CUR Strings[x];
849		} else {
850			sprintf(temp, "%s is not a string capability", pad);
851			ptext(temp);
852			generic_done_message(test, state, chp);
853			return;
854		}
855	} else {
856		x = tx_index[i];
857		current_string = tx_cap[i];
858		strcpy(pad, strnames[x]);
859	}
860	if (!current_string) {
861		ptextln("That string is not currently defined.  Please enter a new value, including the padding delay:");
862		read_string(buf, sizeof(buf));
863		_nc_reset_input((FILE *) 0, buf);
864		_nc_trans_string(pad, pad + sizeof(pad));
865		t = (char *)malloc(strlen(pad) + 1);
866		strcpy(t, pad);
867		CUR Strings[x] = t;
868		sprintf(temp, "new string value  %s", strnames[x]);
869		ptextln(temp);
870		ptextln(expand(t));
871		return;
872	}
873	sprintf(buf, "Current value: (%s) %s", pad, _nc_tic_expand(current_string, TRUE, TRUE));
874	putln(buf);
875	ptextln("Enter new pad.  0 for no pad.  CR for no change.");
876	read_string(buf, 32);
877	if (buf[0] == '\0' || (buf[1] == '\0' && isalpha(UChar(buf[0])))) {
878		*chp = buf[0];
879		return;
880	}
881	star = slash = FALSE;
882	for (j = v = dot = 0; (ch = buf[j]); j++) {
883		if (ch >= '0' && ch <= '9') {
884			v = ch - '0' + v * 10;
885			if (dot) {
886				dot++;
887			}
888		} else if (ch == '*') {
889			star = TRUE;
890		} else if (ch == '/') {
891			slash = TRUE;
892		} else if (ch == '.') {
893			dot = 1;
894		} else {
895			sprintf(temp, "Illegal character: %c", ch);
896			ptextln(temp);
897			ptext("General format:  99.9*/  ");
898			generic_done_message(test, state, chp);
899			return;
900		}
901	}
902	while (dot > 2) {
903		v /= 10;
904		dot--;
905	}
906	if (dot == 2) {
907		sprintf(pad, "%d.%d%s%s", v / 10, v % 10,
908				star ? "*" : "", slash ? "/" : "");
909	} else {
910		sprintf(pad, "%d%s%s",
911			v, star ? "*" : "", slash ? "/" : "");
912	}
913	s = current_string;
914	t = buf;
915	for (v = 0; (ch = *t = *s++); t++) {
916		if (v == '$' && ch == '<') {
917			while ((ch = *s++) && (ch != '>'));
918			for (p = pad; (*++t = *p++); );
919			*t++ = '>';
920			while ((*t++ = *s++));
921			pad[0] = '\0';
922			break;
923		}
924		v = ch;
925	}
926	if (pad[0]) {
927		sprintf(t, "$<%s>", pad);
928	}
929	if ((t = (char *)malloc(strlen(buf) + 1))) {
930		strcpy(t, buf);
931		CUR Strings[x] = t;
932		if (i != 255) {
933			tx_cap[i] = t;
934		}
935	}
936	generic_done_message(test, state, chp);
937}
938
939/*
940**	build_change_menu(menu_list)
941**
942**	Build the change pad menu list
943*/
944static void
945build_change_menu(
946	struct test_menu *m)
947{
948	int i, j, k;
949	char *s;
950
951	for (i = j = 0; i < txp; i++) {
952		if ((k = tx_index[i]) >= 0) {
953			s = _nc_tic_expand(tx_cap[i], TRUE, TRUE);
954			s[40] = '\0';
955			sprintf(change_pad_text[j], "%c) (%s) %s",
956				'a' + j, strnames[k], s);
957			change_pad_list[j].flags = i;
958			change_pad_list[j].lines_needed = 4;
959			change_pad_list[j].menu_entry = change_pad_text[j];
960			change_pad_list[j].test_procedure = change_one_entry;
961			j++;
962		}
963	}
964	strcpy(change_pad_text[j], "z) enter name");
965	change_pad_list[j].flags = 255;
966	change_pad_list[j].lines_needed = 4;
967	change_pad_list[j].menu_entry = change_pad_text[j];
968	change_pad_list[j].test_procedure = change_one_entry;
969	j++;
970	change_pad_list[j].flags = MENU_LAST;
971	if (m->menu_title) {
972		put_crlf();
973		ptextln(m->menu_title);
974	}
975}
976