1 /*
2  *  radiolist.c -- implements the radiolist box
3  *
4  *  AUTHOR: Stuart Herbert - S.Herbert@sheffield.ac.uk
5  *   (from checklist.c by Savio Lam (lam836@cs.cuhk.hk))
6  *
7  *	Substantial rennovation:  12/18/95, Jordan K. Hubbard
8  *
9  *  This program is free software; you can redistribute it and/or
10  *  modify it under the terms of the GNU General Public License
11  *  as published by the Free Software Foundation; either version 2
12  *  of the License, or (at your option) any later version.
13  *
14  *  This program is distributed in the hope that it will be useful,
15  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *  GNU General Public License for more details.
18  *
19  *  You should have received a copy of the GNU General Public License
20  *  along with this program; if not, write to the Free Software
21  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22  */
23 
24 #include <sys/cdefs.h>
25 __FBSDID("$FreeBSD: stable/9/gnu/lib/libodialog/radiolist.c 114603 2003-05-03 21:31:08Z obrien $");
26 
27 #include <dialog.h>
28 #include "dialog.priv.h"
29 
30 
31 static void print_item(WINDOW *win, char *tag, char *item, int status, int choice, int selected, dialogMenuItem *me);
32 
33 #define DREF(di, item)		((di) ? &((di)[(item)]) : NULL)
34 
35 static int list_width, check_x, item_x;
36 
37 
38 /*
39  * Display a dialog box with a list of options that can be turned on or off
40  */
41 int
dialog_radiolist(unsigned char * title,unsigned char * prompt,int height,int width,int list_height,int cnt,void * it,unsigned char * result)42 dialog_radiolist(unsigned char *title, unsigned char *prompt, int height, int width, int list_height,
43 		 int cnt, void *it, unsigned char *result)
44 {
45     int i, j, x, y, cur_x, cur_y, old_x, old_y, box_x, box_y, key = 0, button,
46 	choice, l, k, scroll, max_choice, *status, item_no = 0, was_on = 0;
47     int redraw_menu = FALSE, cursor_reset = FALSE;
48     int rval = 0, onlist = 1, ok_space, cancel_space;
49     char okButton, cancelButton;
50     WINDOW *dialog, *list;
51     unsigned char **items = NULL;
52     dialogMenuItem *ditems;
53 
54     /* Allocate space for storing item on/off status */
55     if ((status = alloca(sizeof(int) * abs(cnt))) == NULL) {
56 	endwin();
57 	fprintf(stderr, "\nCan't allocate memory in dialog_radiolist().\n");
58 	exit(-1);
59     }
60 
61 draw:
62     button = choice = scroll = 0;
63     /* Previous calling syntax, e.g. just a list of strings? */
64     if (cnt >= 0) {
65 	items = it;
66 	ditems = NULL;
67 	item_no = cnt;
68 	/* Initializes status */
69 	for (i = 0; i < item_no; i++) {
70 	    status[i] = !strcasecmp(items[i*3 + 2], "on");
71 	    if (status[i]) {
72 		if (was_on)
73 		    status[i] = FALSE;
74 		else
75 		    was_on = 1;
76 	    }
77 	}
78     }
79     /* It's the new specification format - fake the rest of the code out */
80     else {
81 	item_no = abs(cnt);
82 	ditems = it;
83 	if (!items)
84 	    items = (unsigned char **)alloca((item_no * 3) * sizeof(unsigned char *));
85 	/* Initializes status */
86 	for (i = 0; i < item_no; i++) {
87 	    status[i] = ditems[i].checked ? ditems[i].checked(&ditems[i]) : FALSE;
88 	    if (status[i]) {
89 		if (was_on)
90 		    status[i] = FALSE;
91 		else
92 		    was_on = 1;
93 	    }
94 	    items[i*3] = ditems[i].prompt;
95 	    items[i*3 + 1] = ditems[i].title;
96 	    items[i*3 + 2] = status[i] ? "on" : "off";
97 	}
98     }
99     max_choice = MIN(list_height, item_no);
100 
101     check_x = 0;
102     item_x = 0;
103     /* Find length of longest item in order to center radiolist */
104     for (i = 0; i < item_no; i++) {
105 	l = strlen(items[i * 3]);
106 	for (j = 0; j < item_no; j++) {
107 	    k = strlen(items[j * 3 + 1]);
108 	    check_x = MAX(check_x, l + k + 6);
109 	}
110 	item_x = MAX(item_x, l);
111     }
112     if (height < 0)
113 	height = strheight(prompt) + list_height + 4 + 2;
114     if (width < 0) {
115 	i = strwidth(prompt);
116 	j = ((title != NULL) ? strwidth(title) : 0);
117 	width = MAX(i, j);
118 	width = MAX(width, check_x + 4) + 4;
119     }
120     width = MAX(width, 24);
121 
122     if (width > COLS)
123 	width = COLS;
124     if (height > LINES)
125 	height = LINES;
126     /* center dialog box on screen */
127     x = DialogX ? DialogX : (COLS - width) / 2;
128     y = DialogY ? DialogY : (LINES - height) / 2;
129 
130 #ifdef HAVE_NCURSES
131     if (use_shadow)
132 	draw_shadow(stdscr, y, x, height, width);
133 #endif
134     dialog = newwin(height, width, y, x);
135     if (dialog == NULL) {
136 	endwin();
137 	fprintf(stderr, "\nnewwin(%d,%d,%d,%d) failed, maybe wrong dims\n", height, width, y, x);
138 	return -1;
139     }
140     keypad(dialog, TRUE);
141 
142     draw_box(dialog, 0, 0, height, width, dialog_attr, border_attr);
143     wattrset(dialog, border_attr);
144     wmove(dialog, height - 3, 0);
145     waddch(dialog, ACS_LTEE);
146     for (i = 0; i < width - 2; i++)
147 	waddch(dialog, ACS_HLINE);
148     wattrset(dialog, dialog_attr);
149     waddch(dialog, ACS_RTEE);
150     wmove(dialog, height - 2, 1);
151     for (i = 0; i < width - 2; i++)
152 	waddch(dialog, ' ');
153 
154     if (title != NULL) {
155 	wattrset(dialog, title_attr);
156 	wmove(dialog, 0, (width - strlen(title)) / 2 - 1);
157 	waddch(dialog, ' ');
158 	waddstr(dialog, title);
159 	waddch(dialog, ' ');
160     }
161     wattrset(dialog, dialog_attr);
162     wmove(dialog, 1, 2);
163     print_autowrap(dialog, prompt, height - 1, width - 2, width, 1, 2, TRUE, FALSE);
164 
165     list_width = width - 6;
166     getyx(dialog, cur_y, cur_x);
167     box_y = cur_y + 1;
168     box_x = (width - list_width) / 2 - 1;
169 
170     /* create new window for the list */
171     list = subwin(dialog, list_height, list_width, y + box_y + 1, x + box_x + 1);
172     if (list == NULL) {
173 	delwin(dialog);
174 	endwin();
175 	fprintf(stderr, "\nsubwin(dialog,%d,%d,%d,%d) failed, maybe wrong dims\n", list_height, list_width,
176 		y + box_y + 1,x + box_x + 1);
177 	return -1;
178     }
179     keypad(list, TRUE);
180 
181     /* draw a box around the list items */
182     draw_box(dialog, box_y, box_x, list_height+2, list_width+2, menubox_border_attr, menubox_attr);
183 
184     check_x = (list_width - check_x) / 2;
185     item_x = check_x + item_x + 6;
186 
187     /* Print the list */
188     for (i = 0; i < max_choice; i++)
189 	print_item(list, items[i * 3], items[i * 3 + 1], status[i], i, i == choice, DREF(ditems, i));
190     wnoutrefresh(list);
191     print_arrows(dialog, scroll, list_height, item_no, box_x, box_y, check_x + 4, cur_x, cur_y);
192 
193     display_helpline(dialog, height-1, width);
194 
195     x = width/ 2 - 11;
196     y = height - 2;
197     if (ditems && result) {
198 	cancelButton = toupper(ditems[CANCEL_BUTTON].prompt[0]);
199 	print_button(dialog, ditems[CANCEL_BUTTON].prompt, y, x + strlen(ditems[OK_BUTTON].prompt) + 5,
200 		     ditems[CANCEL_BUTTON].checked ? ditems[CANCEL_BUTTON].checked(&ditems[CANCEL_BUTTON]) : FALSE);
201 	okButton = toupper(ditems[OK_BUTTON].prompt[0]);
202 	print_button(dialog, ditems[OK_BUTTON].prompt, y, x,
203 		     ditems[OK_BUTTON].checked ? ditems[OK_BUTTON].checked(&ditems[OK_BUTTON]) : TRUE);
204     }
205     else {
206 	cancelButton = 'C';
207 	print_button(dialog, "Cancel", y, x + 14, FALSE);
208 	okButton = 'O';
209 	print_button(dialog, "  OK  ", y, x, TRUE);
210     }
211     wnoutrefresh(dialog);
212     wmove(list, choice, check_x+1);
213     wrefresh(list);
214 
215     while (key != ESC) {
216 	key = wgetch(dialog);
217 
218 	/* See if its the short-cut to "OK" */
219 	if (toupper(key) == okButton) {
220 	    if (ditems) {
221 		if (result && ditems[OK_BUTTON].fire) {
222 		    int st;
223 		    WINDOW *save;
224 
225 		    save = dupwin(newscr);
226 		    st = ditems[OK_BUTTON].fire(&ditems[OK_BUTTON]);
227 		    if (st & DITEM_RESTORE) {
228 			touchwin(save);
229 			wrefresh(save);
230 		    }
231 		    delwin(save);
232 		}
233 	    }
234 	    else if (result) {
235 		*result = '\0';
236 		for (i = 0; i < item_no; i++) {
237 		    if (status[i]) {
238 			strcat(result, items[i*3]);
239 			break;
240 		    }
241 		}
242 	    }
243 	    rval = 0;
244 	    key = ESC;
245 	    break;
246 	}
247 
248 	/* Shortcut to cancel */
249 	if (toupper(key) == cancelButton) {
250 	    if (ditems && result && ditems[CANCEL_BUTTON].fire) {
251 		int st;
252 		WINDOW *save;
253 
254 		save = dupwin(newscr);
255 		st = ditems[CANCEL_BUTTON].fire(&ditems[CANCEL_BUTTON]);
256 		if (st & DITEM_RESTORE) {
257 		    touchwin(save);
258 		    wrefresh(save);
259 		}
260 		delwin(save);
261 	    }
262 	    rval = 1;
263 	    key = ESC;
264 	    break;
265 	}
266 
267 	/* Check if key pressed matches first character of any item tag in list */
268 	for (i = 0; i < max_choice; i++)
269 	    if (key != ' ' && toupper(key) == toupper(items[(scroll + i) * 3][0]))
270 		break;
271 
272 	if (i < max_choice || (key >= '1' && key <= MIN('9', '0' + max_choice)) ||
273 	    KEY_IS_UP(key) || KEY_IS_DOWN(key) || ((key == ' ' || key == '\r' || key == '\n') && onlist == 1)) {
274 
275 	    /* if moving from buttons to the list, reset and redraw buttons */
276 	    if (!onlist) {
277 		onlist = 1;
278 		button = 0;
279 
280 	    	if (ditems && result ) {
281 		    print_button(dialog, ditems[CANCEL_BUTTON].prompt, y, x + strlen(ditems[OK_BUTTON].prompt) + 5,
282 			ditems[CANCEL_BUTTON].checked ? ditems[CANCEL_BUTTON].checked(&ditems[CANCEL_BUTTON]) : button);
283 		    print_button(dialog, ditems[OK_BUTTON].prompt, y, x,
284 			ditems[OK_BUTTON].checked ? ditems[OK_BUTTON].checked(&ditems[OK_BUTTON]) : !button);
285 		}
286 		else {
287 		    print_button(dialog, "Cancel", y, x + 14, button);
288 		    print_button(dialog, "  OK  ", y, x, !button);
289 		}
290 	    }
291 	    wmove(list, choice, check_x+1);
292 	    wnoutrefresh(dialog);
293 	    wrefresh(list);
294 
295 	    if (key >= '1' && key <= MIN('9', '0' + max_choice))
296 		i = key - '1';
297 	    else if (KEY_IS_UP(key)) {
298 		if (!choice) {
299 		    if (scroll) {
300 			/* Scroll list down */
301 			getyx(dialog, cur_y, cur_x);    /* Save cursor position */
302 			if (list_height > 1) {
303 			    /* De-highlight current first item before scrolling down */
304 			    print_item(list, items[scroll*3], items[scroll*3 + 1], status[scroll], 0,
305 				       FALSE, DREF(ditems, scroll));
306 			    scrollok(list, TRUE);
307 			    wscrl(list, -1);
308 			    scrollok(list, FALSE);
309 			}
310 			scroll--;
311 			print_item(list, items[scroll*3], items[scroll*3 + 1], status[scroll], 0,
312 				   TRUE, DREF(ditems, scroll));
313 			print_arrows(dialog, scroll, list_height, item_no, box_x, box_y, check_x + 4, cur_x, cur_y);
314 			wmove(list, choice, check_x+1);
315 			wnoutrefresh(dialog);
316 			wrefresh(list);
317 		    }
318 		    continue;    /* wait for another key press */
319 		}
320 		else
321 		    i = choice - 1;
322 	    }
323 	    else if (KEY_IS_DOWN(key)) {
324 		if (choice == max_choice - 1) {
325 		    if (scroll + choice < item_no - 1) {
326 			/* Scroll list up */
327 			getyx(dialog, cur_y, cur_x);    /* Save cursor position */
328 			if (list_height > 1) {
329 			    /* De-highlight current last item before scrolling up */
330 			    print_item(list, items[(scroll + max_choice - 1) * 3],
331 				       items[(scroll + max_choice - 1) * 3 + 1],
332 				       status[scroll + max_choice - 1], max_choice - 1,
333 				       FALSE, DREF(ditems, scroll + max_choice - 1));
334 			    scrollok(list, TRUE);
335 			    scroll(list);
336 			    scrollok(list, FALSE);
337 			}
338 			scroll++;
339 			print_item(list, items[(scroll + max_choice - 1) * 3],
340 				   items[(scroll + max_choice - 1) * 3 + 1],
341 				   status[scroll + max_choice - 1], max_choice - 1,
342 				   TRUE, DREF(ditems, scroll + max_choice - 1));
343 			print_arrows(dialog, scroll, list_height, item_no, box_x, box_y, check_x + 4, cur_x, cur_y);
344 			wmove(list, choice, check_x+1);
345 			wnoutrefresh(dialog);
346 			wrefresh(list);
347 		    }
348 		    continue;    /* wait for another key press */
349 		}
350 		else
351 		    i = choice + 1;
352 	    }
353 	    else if ((key == ' ' || key == '\r' || key == '\n') && onlist) {    /* Toggle item status */
354 		getyx(list, old_y, old_x);     /* Save cursor position */
355 		if (status[scroll + choice])
356 		    continue;
357 		else if (ditems) {
358 		    if (ditems[scroll + choice].fire) {
359 			int st;
360 			WINDOW *save;
361 
362 			save = dupwin(newscr);
363 			st = ditems[scroll + choice].fire(&ditems[scroll + choice]);
364 			if (st & DITEM_RESTORE) {
365 			    touchwin(save);
366 			    wrefresh(save);
367 			}
368 			delwin(save);
369 			if (st & DITEM_REDRAW) {
370 			    wclear(list);
371 			    for (i = 0; i < item_no; i++)
372 				status[i] = ditems[i].checked ? ditems[i].checked(&ditems[i]) : FALSE;
373 
374 			    for (i = 0; i < max_choice; i++) {
375 				print_item(list, items[(scroll + i) * 3], items[(scroll + i) * 3 + 1],
376 					   status[scroll + i], i, i == choice,
377 					   DREF(ditems, scroll + i));
378 			    }
379 /*			    wmove(list, old_y, old_x);*/  /* Restore cursor to previous position */
380 /*			    wrefresh(list); */
381 			}
382 			if (st & DITEM_LEAVE_MENU) {
383 			    /* Allow a fire action to take us out of the menu */
384 			    key = ESC;
385 			    break;
386 			}
387 			else if (st & DITEM_RECREATE) {
388 			    delwin(list);
389 			    delwin(dialog);
390 			    dialog_clear();
391 			    goto draw;
392 			}
393 		    }
394 		    for (i = 0; i < item_no; i++)
395 			status[i] = ditems[i].checked ? ditems[i].checked(&ditems[i]) : FALSE;
396 		}
397 		else {
398 		    for (i = 0; i < item_no; i++)
399 			status[i] = 0;
400 		    status[scroll + choice] = TRUE;
401 		}
402 		for (i = 0; i < max_choice; i++)
403 		    print_item(list, items[(scroll + i) * 3], items[(scroll + i) * 3 + 1],
404 			       status[scroll + i], i, i == choice, DREF(ditems, scroll + i));
405 		wmove(list, choice, check_x+1);  /* Restore cursor position */
406 		wrefresh(list);
407 		continue;    /* wait for another key press */
408 	    }
409 
410 	    if (i != choice) {
411 		/* De-highlight current item */
412 		print_item(list, items[(scroll + choice) * 3], items[(scroll + choice) * 3 +1],
413 			   status[scroll + choice], choice, FALSE, DREF(ditems, scroll + choice));
414 		/* Highlight new item */
415 		choice = i;
416 		print_item(list, items[(scroll + choice) * 3], items[(scroll + choice) * 3 + 1],
417 			   status[scroll + choice], choice, TRUE, DREF(ditems, scroll + choice));
418 		wmove(list, choice, check_x+1);  /* Restore cursor position */
419 		wrefresh(list);
420 	    }
421 	    continue;    /* wait for another key press */
422 	}
423 
424 	switch (key) {
425 	case KEY_PPAGE:
426 	    if (scroll > height-4)		/* can we go up? */
427 		scroll -= (height-4);
428 	    else
429 		scroll = 0;
430 	    redraw_menu = TRUE;
431 	    if (!onlist) {
432 		onlist = 1;
433 		button = 0;
434 	    }
435 	    break;
436 
437 	case KEY_NPAGE:
438 	    if (scroll + list_height >= item_no-1 - list_height) { /* can we go down a full page? */
439 		scroll = item_no - list_height;
440 		if (scroll < 0)
441 		    scroll = 0;
442 	    }
443 	    else
444 		scroll += list_height;
445 	    redraw_menu = TRUE;
446 	    if (!onlist) {
447 		onlist = 1;
448 		button = 0;
449 	    }
450 	    break;
451 
452 	case KEY_HOME:
453 	    scroll = 0;
454 	    choice = 0;
455 	    redraw_menu = TRUE;
456 	    cursor_reset = TRUE;
457 	    onlist = 1;
458 	    break;
459 
460 	case KEY_END:
461 	    scroll = item_no - list_height;
462 	    if (scroll < 0)
463 		scroll = 0;
464 	    choice = max_choice - 1;
465 	    redraw_menu = TRUE;
466 	    cursor_reset = TRUE;
467 	    onlist = 1;
468 	    break;
469 
470 	case TAB:
471 	case KEY_BTAB:
472 	    /* move to next component */
473 	    if (onlist) {      /* on list, next is ok button */
474 		onlist = 0;
475 		if (ditems && result)
476 		    ok_space = 1;
477 		else
478 		    ok_space = 3;
479 		wmove(dialog, y, x + ok_space);
480 		wrefresh(dialog);
481 		break;
482 	    }
483 	    else if (button) {      /* on cancel button, next is list */
484 		button = 0;
485 		onlist = 1;
486 		redraw_menu = TRUE;
487 		break;
488 	    }
489 	    /* on ok button, next is cancel button, same as left/right case */
490 
491 	case KEY_LEFT:
492 	case KEY_RIGHT:
493 	    onlist = 0;
494 	    button = !button;
495 	    if (ditems && result) {
496 		print_button(dialog, ditems[CANCEL_BUTTON].prompt, y, x + strlen(ditems[OK_BUTTON].prompt) + 5,
497 			     ditems[CANCEL_BUTTON].checked ? ditems[CANCEL_BUTTON].checked(&ditems[CANCEL_BUTTON]) : button);
498 		print_button(dialog, ditems[OK_BUTTON].prompt, y, x,
499 			     ditems[OK_BUTTON].checked ? ditems[OK_BUTTON].checked(&ditems[OK_BUTTON]) : !button);
500 		ok_space = 1;
501 		cancel_space = strlen(ditems[OK_BUTTON].prompt) + 6;
502 	    }
503 	    else {
504 		print_button(dialog, "Cancel", y, x + 14, button);
505 		print_button(dialog, "  OK  ", y, x, !button);
506 		ok_space = 3;
507 		cancel_space = 15;
508 	    }
509 	    if (button)
510 		wmove(dialog, y, x + cancel_space);
511 	    else
512 		wmove(dialog, y, x + ok_space);
513 	    wrefresh(dialog);
514 	    break;
515 
516 	case ' ':
517 	case '\r':
518 	case '\n':
519 	    if (!onlist) {
520 		if (ditems) {
521 		    if (result && ditems[button ? CANCEL_BUTTON : OK_BUTTON].fire) {
522 			int st;
523 			WINDOW *save;
524 
525 			save = dupwin(newscr);
526 			st = ditems[button ? CANCEL_BUTTON : OK_BUTTON].fire(&ditems[button ? CANCEL_BUTTON : OK_BUTTON]);
527 			if (st & DITEM_RESTORE) {
528 			    touchwin(save);
529 			    wrefresh(save);
530 			}
531 			delwin(save);
532 		    }
533 		}
534 		else if (result) {
535 		    *result = '\0';
536 		    for (i = 0; i < item_no; i++) {
537 			if (status[i]) {
538 			    strcpy(result, items[i*3]);
539 			    break;
540 			}
541 		    }
542 		}
543 		rval = button;
544 		key = ESC;
545 		break;
546 	    }
547 
548 	case ESC:
549 	    rval = -1;
550 	    break;
551 
552 	case KEY_F(1):
553 	case '?':
554 	    display_helpfile();
555 	    break;
556 	}
557 
558 	if (redraw_menu) {
559 	    getyx(list, old_y, old_x);
560 	    wclear(list);
561 	    for (i = 0; i < max_choice; i++)
562 		print_item(list, items[(scroll + i) * 3], items[(scroll + i) * 3 + 1], status[scroll + i],
563 			   i, i == choice, DREF(ditems, scroll + i));
564 	    print_arrows(dialog, scroll, list_height, item_no, box_x, box_y, check_x + 4, cur_x, cur_y);
565 
566 	    /* redraw buttons to fix highlighting */
567 	    if (ditems && result) {
568 		print_button(dialog, ditems[CANCEL_BUTTON].prompt, y, x + strlen(ditems[OK_BUTTON].prompt) + 5,
569 			ditems[CANCEL_BUTTON].checked ? ditems[CANCEL_BUTTON].checked(&ditems[CANCEL_BUTTON]) : button);
570 		print_button(dialog, ditems[OK_BUTTON].prompt, y, x,
571 			ditems[OK_BUTTON].checked ? ditems[OK_BUTTON].checked(&ditems[OK_BUTTON]) : !button);
572 	    }
573 	    else {
574 		print_button(dialog, "Cancel", y, x + 14, button);
575 		print_button(dialog, "  OK  ", y, x, !button);
576 	    }
577 	    wnoutrefresh(dialog);
578 	    if (cursor_reset) {
579 		wmove(list, choice, check_x+1);
580 		cursor_reset = FALSE;
581 	    }
582 	    else {
583 		wmove(list, old_y, old_x);
584 	    }
585 	    wrefresh(list);
586 	    redraw_menu = FALSE;
587 	}
588     }
589 
590     delwin(list);
591     delwin(dialog);
592     return rval;    /* ESC pressed */
593 }
594 
595 /*
596  * Print list item
597  */
598 static void
print_item(WINDOW * win,char * tag,char * item,int status,int choice,int selected,dialogMenuItem * me)599 print_item(WINDOW *win, char *tag, char *item, int status, int choice, int selected, dialogMenuItem *me)
600 {
601     int i;
602 
603     /* Clear 'residue' of last item */
604     wattrset(win, menubox_attr);
605     wmove(win, choice, 0);
606     for (i = 0; i < list_width; i++)
607 	waddch(win, ' ');
608     wmove(win, choice, check_x);
609     wattrset(win, selected ? check_selected_attr : check_attr);
610     wprintw(win, "%c%c%c", me && me->lbra ? me->lbra : '(',
611 	    status ? me && me->mark ? me->mark : '*' : ' ',
612 	    me && me->rbra ? me->rbra : ')');
613     wattrset(win, menubox_attr);
614     waddch(win, ' ');
615     wattrset(win, selected ? tag_key_selected_attr : tag_key_attr);
616     waddch(win, tag[0]);
617     wattrset(win, selected ? tag_selected_attr : tag_attr);
618     waddstr(win, tag + 1);
619     wmove(win, choice, item_x);
620     wattrset(win, selected ? item_selected_attr : item_attr);
621     waddstr(win, item);
622     /* If have a selection handler for this, call it */
623     if (me && me->selected) {
624 	wrefresh(win);
625 	me->selected(me, selected);
626     }
627 }
628 /* End of print_item() */
629