/*
 * ========================================================================
 * Copyright 2013-2026 Eduardo Chappa
 * Copyright 2006-2007 University of Washington
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * ========================================================================
 */

#include "headers.h"
#include "radio.h"
#include "keymenu.h"
#include "busy.h"
#include "status.h"
#include "mailcmd.h"
#include "titlebar.h"
#include "roleconf.h"
#include "../pith/state.h"
#include "../pith/conf.h"
#include "../pith/newmail.h"
#include "../pith/util.h"


/*
 * Internal prototypes
 */
int       pre_screen_config_want_to(char *, int, int);
ESCKEY_S *construct_combined_esclist(ESCKEY_S *, ESCKEY_S *);
void      radio_help(int, int, HelpType);
void      draw_radio_prompt(int, unsigned, unsigned, char *);


#define	RAD_BUT_COL	0
#define WANT_TO_BUF     2500


/*
 * want_to's array passed to radio_buttions...
 */
static ESCKEY_S yorn[] = {
    {'y', 'y', "Y", N_("Yes")},
    {'n', 'n', "N", N_("No")},
    {-1, 0, NULL, NULL}
};

int
pre_screen_config_want_to(char *question, int dflt, int on_ctrl_C)
{
    int ret = 0;
    char rep[WANT_TO_BUF], *p;
#ifdef _WINDOWS
    rep[0] = '\0';
    mswin_flush();
    if(strlen(question) + 3 < WANT_TO_BUF){
      snprintf(rep, sizeof(rep), "%s ?", question);
      rep[sizeof(rep)-1] = '\0';
    }

    switch (mswin_yesno_utf8 (*rep ? rep : question)) {
      default:
      case 0:   return (on_ctrl_C);
      case 1:   return ('y');
      case 2:   return ('n');
    }
#endif
    while(!ret){
      if(fprintf(stdout, "%s? [%c]:", question, dflt) < 0
	  || fgets(rep, WANT_TO_BUF, stdin) == NULL)
	alpine_panic(_("error on fprintf() or fgets()"));
      if((p = strpbrk(rep, "\r\n")) != NULL)
	*p = '\0';
      switch(*rep){
        case 'Y':
        case 'y':
	  ret = (int)'y';
	  break;
        case 'N':
        case 'n':
	  ret = (int)'n';
	  break;
        case '\0':
	  ret = dflt;
	  break;
        default:
	  break;
      }
    }
    return ret;
}


/*----------------------------------------------------------------------
     Ask a yes/no question in the status line

   Args: question     -- string to prompt user with
         dflt         -- The default answer to the question (should probably
			 be y or n)
         on_ctrl_C    -- Answer returned on ^C
	 help         -- Two line help text
	 flags        -- Flags to modify behavior
			 WT_DING	  - ding the bell when asking.
			 WT_FLUSH_IN      - Discard pending input.
			 WT_SEQ_SENSITIVE - Caller is sensitive to sequence
			                    number changes caused by
					    unsolicited expunges while we're
					    viewing a message.

 Result: Messes up the status line,
         returns y, n, dflt, on_ctrl_C, or SEQ_EXCEPTION
  ---*/
int
want_to(char *question, int dflt, int on_ctrl_C, HelpType help, int flags)
{
    char *free_this = NULL, *free_this2 = NULL, *prompt;
    int	  rv, width;
    size_t len;

    if((flags & WT_DING) && F_OFF(F_QUELL_BEEPS, ps_global)){
        Writechar(BELL, 0);
        fflush(stdout);
    }

    if(!ps_global->ttyo)
      return(pre_screen_config_want_to(question, dflt, on_ctrl_C));
#ifdef _WINDOWS
    if (mswin_usedialog ()) {
	mswin_flush ();
	switch (mswin_yesno_utf8 (question)) {
	default:
	case 0:		return (on_ctrl_C);
	case 1:		return ('y');
	case 2:		return ('n');
        }
    }
#endif

    /*----
       One problem with adding the (y/n) here is that shrinking the 
       screen while in radio_buttons() will cause it to get chopped
       off. It would be better to truncate the question passed in
       here and leave the full "(y/n) [x] : " on.
      ----*/

    len = strlen(question) + 4;
    free_this = (char *) fs_get(len);
    width = utf8_width(question);

    if(width + 2 < ps_global->ttyo->screen_cols){
	snprintf(free_this, len, "%s? ", question);
	free_this[len-1] = '\0';
	prompt = free_this;
    }
    else if(width + 1 < ps_global->ttyo->screen_cols){
	snprintf(free_this, len, "%s?", question);
	free_this[len-1] = '\0';
	prompt = free_this;
    }
    else if(width < ps_global->ttyo->screen_cols){
	snprintf(free_this, len, "%s", question);
	free_this[len-1] = '\0';
	prompt = free_this;
    }
    else{
	free_this2 = (char *) fs_get(len);
	snprintf(free_this2, len, "%s? ", question);
	prompt = short_str(free_this2, free_this, len, ps_global->ttyo->screen_cols-1, MidDots);
    }

    if(on_ctrl_C == 'n')	/* don't ever let cancel == 'n' */
      on_ctrl_C = 0;

    rv = radio_buttons(prompt,
	(ps_global->ttyo->screen_rows > 4) ? - FOOTER_ROWS(ps_global) : -1,
	yorn, dflt, on_ctrl_C, help, flags);

    if(free_this)
      fs_give((void **) &free_this);

    if(free_this2)
      fs_give((void **) &free_this2);

    return(rv);
}


int
one_try_want_to(char *question, int dflt, int on_ctrl_C, HelpType help, int flags)
{
    char     *q2;
    int	      rv;
    size_t    l;

    l = strlen(question) + 5;
    q2 = fs_get((l+1) * sizeof(char));
    strncpy(q2, question, l);
    q2[l] = '\0';
    (void) utf8_truncate(q2, ps_global->ttyo->screen_cols - 6);
    strncat(q2, "? ", l+1 - strlen(q2) - 1);
    q2[l] = '\0';
    rv = radio_buttons(q2,
	(ps_global->ttyo->screen_rows > 4) ? - FOOTER_ROWS(ps_global) : -1,
	yorn, dflt, on_ctrl_C, help, flags | RB_ONE_TRY);
    fs_give((void **) &q2);

    return(rv);
}


/*----------------------------------------------------------------------
    Prompt user for a choice among alternatives

Args --  utf8prompt:    The prompt for the question/selection
         line:      The line to prompt on, if negative then relative to bottom
         esc_list:  ESC_KEY_S list of keys
         dflt:	    The selection when the <CR> is pressed (should probably
		      be one of the chars in esc_list)
         on_ctrl_C: The selection when ^C is pressed
         help_text: Text to be displayed on bottom two lines
	 flags:     Logically OR'd flags modifying our behavior to:
		RB_FLUSH_IN    - Discard any pending input chars.
		RB_ONE_TRY     - Only give one chance to answer.  Returns
				 on_ctrl_C value if not answered acceptably
				 on first try.
		RB_NO_NEWMAIL  - Quell the usual newmail check.
		RB_SEQ_SENSITIVE - The caller is sensitive to sequence number
				   changes so return on_ctrl_C if an
				   unsolicited expunge happens while we're
				   viewing a message.
		RB_RET_HELP    - Instead of the regular internal handling
				 way of handling help_text, this just causes
				 radio_buttons to return 3 when help is
				 asked for, so that the caller handles it
				 instead.
	
	 Note: If there are enough keys in the esc_list to need a second
	       screen, and there is no help, then the 13th key will be
	       put in the help position.

Result -- Returns the letter pressed. Will be one of the characters in the
          esc_list argument, or dflt, or on_ctrl_C, or SEQ_EXCEPTION.

This will pause for any new status message to be seen and then prompt the user.
The prompt will be truncated to fit on the screen. Redraw and resize are
handled along with ^Z suspension. Typing ^G will toggle the help text on and
off. Character types that are not buttons will result in a beep (unless one_try
is set).
  ----*/
int
radio_buttons(char *utf8prompt, int line, ESCKEY_S *esc_list, int dflt,
	      int on_ctrl_C, HelpType help_text, int flags)
{
    UCS              ucs;
    register int     ch, real_line;
    char            *q, *ds = NULL;
    unsigned         maxcol;
    int              max_label, i, start, fkey_table[12];
    int		     km_popped = 0;
    struct key	     rb_keys[12];
    struct key_menu  rb_keymenu;
    bitmap_t	     bitmap;
    struct variable *vars = ps_global->vars;
    COLOR_PAIR      *lastc = NULL, *promptc = NULL;

#ifdef	_WINDOWS
    int		     cursor_shown;

    if (mswin_usedialog()){
	MDlgButton button_list[25];
	LPTSTR     free_names[25];
	LPTSTR     free_labels[25];
	int        b, i, ret;
	char     **help;

	memset(&free_names, 0, sizeof(LPTSTR) * 25);
	memset(&free_labels, 0, sizeof(LPTSTR) * 25);
	memset(&button_list, 0, sizeof (MDlgButton) * 25);
	b = 0;

	if(flags & RB_RET_HELP){
	    if(help_text != NO_HELP)
	      alpine_panic("RET_HELP and help in radio_buttons!");

	    button_list[b].ch = '?';
	    button_list[b].rval = 3;
	    button_list[b].name = TEXT("?");
	    free_labels[b] = utf8_to_lptstr(N_("Help"));
	    button_list[b].label = free_labels[b];
	    ++b;
	}

	for(i = 0; esc_list && esc_list[i].ch != -1 && i < 23; ++i){
	  if(esc_list[i].ch != -2){
	    button_list[b].ch = esc_list[i].ch;
	    button_list[b].rval = esc_list[i].rval;
	    free_names[b] = utf8_to_lptstr(esc_list[i].name);
	    button_list[b].name = free_names[b];
	    free_labels[b] = utf8_to_lptstr(esc_list[i].label);
	    button_list[b].label = free_labels[b];
	    ++b;
	  }
	}

	button_list[b].ch = -1;
	
	/* assumption here is that HelpType is char **  */
	help = help_text;

	ret = mswin_select(utf8prompt, button_list, dflt, on_ctrl_C, help, flags);
	for(i = 0; i < 25; i++){
	    if(free_names[i])
	      fs_give((void **) &free_names[i]);
	    if(free_labels[i])
	      fs_give((void **) &free_labels[i]);
	}

	return (ret);
    }

#endif /* _WINDOWS */

    suspend_busy_cue();
    flush_ordered_messages();		/* show user previous status msgs */
    mark_status_dirty();		/* clear message next display call */
    real_line = line > 0 ? line : ps_global->ttyo->screen_rows + line;
    MoveCursor(real_line, RAD_BUT_COL);
    CleartoEOLN();

    /*---- Find widest label ----*/
    max_label = 0;
    for(i = 0; esc_list && esc_list[i].ch != -1 && i < 11; i++){
      if(esc_list[i].ch == -2) /* -2 means to skip this key and leave blank */
	continue;
      if(esc_list[i].name)
        max_label = MAX(max_label, utf8_width(esc_list[i].name));
    }

    if(ps_global->ttyo->screen_cols - max_label - 1 > 0)
      maxcol = ps_global->ttyo->screen_cols - max_label - 1;
    else
      maxcol = 0;

    /*
     * We need to be able to truncate q, so copy it in case it is
     * a readonly string.
     */
    q = cpystr(utf8prompt);

    /*---- Init structs for keymenu ----*/
    for(i = 0; i < 12; i++)
      memset((void *)&rb_keys[i], 0, sizeof(struct key));

    memset((void *)&rb_keymenu, 0, sizeof(struct key_menu));
    rb_keymenu.how_many = 1;
    rb_keymenu.keys     = rb_keys;

    /*---- Setup key menu ----*/
    start = 0;
    clrbitmap(bitmap);
    memset(fkey_table, NO_OP_COMMAND, 12 * sizeof(int));
    if(flags & RB_RET_HELP && help_text != NO_HELP)
      alpine_panic("RET_HELP and help in radio_buttons!");

    /* if shown, always at position 0 */
    if(help_text != NO_HELP || flags & RB_RET_HELP){
	rb_keymenu.keys[0].name  = "?";
	rb_keymenu.keys[0].label = N_("Help");
	setbitn(0, bitmap);
	fkey_table[0] = ctrl('G');
	start++;
    }

    if(on_ctrl_C){
	rb_keymenu.keys[1].name  = "^C";
	rb_keymenu.keys[1].label = N_("Cancel");
	setbitn(1, bitmap);
	fkey_table[1] = ctrl('C');
	start++;
    }

    start = start ? 2 : 0;
    /*---- Show the usual possible keys ----*/
    for(i=start; esc_list && esc_list[i-start].ch != -1; i++){
	/*
	 * If we have an esc_list item we'd like to put in the non-existent
	 * 13th slot, and there is no help, we put it in the help slot
	 * instead.  We're hacking now...!
	 *
	 * We may also have invisible esc_list items that don't show up
	 * on the screen.  We use this when we have two different keys
	 * which are synonyms, like ^P and KEY_UP.  If all the slots are
	 * already full we can still fit invisible keys off the screen to
	 * the right.  A key is invisible if it's label is "".
	 */
	if(i >= 12){
	    if(esc_list[i-start].label
	       && esc_list[i-start].label[0] != '\0'){  /* visible */
		if(i == 12){  /* special case where we put it in help slot */
		    if(help_text != NO_HELP)
		  alpine_panic("Programming botch in radio_buttons(): too many keys");

		    if(esc_list[i-start].ch != -2)
		      setbitn(0, bitmap); /* the help slot */

		    fkey_table[0] = esc_list[i-start].ch;
		    rb_keymenu.keys[0].name  = esc_list[i-start].name;
		    if(esc_list[i-start].ch != -2
		       && esc_list[i-start].rval == dflt
		       && esc_list[i-start].label){
		        size_t l;

			l = strlen(esc_list[i-start].label) + 2;
			ds = (char *)fs_get((l+1) * sizeof(char));
			snprintf(ds, l+1, "[%s]", esc_list[i-start].label);
			ds[l] = '\0';
			rb_keymenu.keys[0].label = ds;
		    }
		    else
		      rb_keymenu.keys[0].label = esc_list[i-start].label;
		}
		else
		  alpine_panic("Botch in radio_buttons(): too many keys");
	    }
	}
	else{
	    if(esc_list[i-start].ch != -2)
	      setbitn(i, bitmap);

	    fkey_table[i] = esc_list[i-start].ch;
	    rb_keymenu.keys[i].name  = esc_list[i-start].name;
	    if(esc_list[i-start].ch != -2
	       && esc_list[i-start].rval == dflt
	       && esc_list[i-start].label){
	        size_t l;

		l = strlen(esc_list[i-start].label) + 2;
		ds = (char *)fs_get((l+1) * sizeof(char));
		snprintf(ds, l+1, "[%s]", esc_list[i-start].label);
		ds[l] = '\0';
		rb_keymenu.keys[i].label = ds;
	    }
	    else
	      rb_keymenu.keys[i].label = esc_list[i-start].label;
	}
    }

    for(; i < 12; i++)
      rb_keymenu.keys[i].name = NULL;

    ps_global->mangled_footer = 1;

#ifdef	_WINDOWS
    cursor_shown = mswin_showcaret(1);
#endif

    if(pico_usingcolor() && VAR_PROMPT_FORE_COLOR &&
       VAR_PROMPT_BACK_COLOR &&
       pico_is_good_color(VAR_PROMPT_FORE_COLOR) &&
       pico_is_good_color(VAR_PROMPT_BACK_COLOR)){
	lastc = pico_get_cur_color();
	if(lastc){
	    promptc = new_color_pair(VAR_PROMPT_FORE_COLOR,
				     VAR_PROMPT_BACK_COLOR);
	    (void)pico_set_colorp(promptc, PSC_NONE);
	}
    }
    else
      StartInverse();

    draw_radio_prompt(real_line, RAD_BUT_COL, maxcol, q);

    while(1){
        fflush(stdout);

	/*---- Paint the keymenu ----*/
	if(lastc)
	  (void)pico_set_colorp(lastc, PSC_NONE);
	else
	  EndInverse();

	draw_keymenu(&rb_keymenu, bitmap, ps_global->ttyo->screen_cols,
		     1 - FOOTER_ROWS(ps_global), 0, FirstMenu);
	if(promptc)
	  (void)pico_set_colorp(promptc, PSC_NONE);
	else
	  StartInverse();

	MoveCursor(real_line, MIN(RAD_BUT_COL+utf8_width(q), maxcol+1));

	if(flags & RB_FLUSH_IN)
	  flush_input();

  newcmd:
	/* Timeout 5 min to keep imap mail stream alive */
        ucs = read_char(600);
        dprint((2,
                   "Want_to read: %s (0x%x)\n", pretty_command(ucs), ucs));
	if((ucs < 0x80) && isupper((unsigned char) ucs))
	  ucs = tolower((unsigned char) ucs);

	if(F_ON(F_USE_FK,ps_global)
	   && (((ucs < 0x80) && isalpha((unsigned char) ucs) && !strchr("YyNn",(int) ucs))
	       || ((ucs >= PF1 && ucs <= PF12)
		   && (ucs = fkey_table[ucs - PF1]) == NO_OP_COMMAND))){
	    /*
	     * The funky test above does two things.  It maps
	     * esc_list character commands to function keys, *and* prevents
	     * character commands from input while in function key mode.
	     * NOTE: this breaks if we ever need more than the first
	     * twelve function keys...
	     */
	    if(flags & RB_ONE_TRY){
		ch = ucs = on_ctrl_C;
	        goto out_of_loop;
	    }

	    Writechar(BELL, 0);
	    continue;
	}

        switch(ucs){

          default:
	    for(i = 0; esc_list && esc_list[i].ch != -1; i++)
	      if(ucs == esc_list[i].ch){
		  int len, n;

		  MoveCursor(real_line,len=MIN(RAD_BUT_COL+utf8_width(q),maxcol+1));
		  for(n = 0, len = ps_global->ttyo->screen_cols - len;
		      esc_list[i].label && esc_list[i].label[n] && len > 0;
		      n++, len--)
		    Writechar(esc_list[i].label[n], 0);

		  ch = esc_list[i].rval;
		  goto out_of_loop;
	      }

	    if(flags & RB_ONE_TRY){
		ch = on_ctrl_C;
	        goto out_of_loop;
	    }

	    Writechar(BELL, 0);
	    break;

          case ctrl('M'):
          case ctrl('J'):
            ch = dflt;
	    for(i = 0; esc_list && esc_list[i].ch != -1; i++)
	      if(ch == esc_list[i].rval){
		  int len, n;

		  MoveCursor(real_line,len=MIN(RAD_BUT_COL+utf8_width(q),maxcol+1));
		  for(n = 0, len = ps_global->ttyo->screen_cols - len;
		      esc_list[i].label && esc_list[i].label[n] && len > 0;
		      n++, len--)
		    Writechar(esc_list[i].label[n], 0);
		  break;
	      }

            goto out_of_loop;

          case ctrl('C'):
	    if(on_ctrl_C || (flags & RB_ONE_TRY)){
		ch = on_ctrl_C;
		goto out_of_loop;
	    }

	    Writechar(BELL, 0);
	    break;


          case '?':
          case ctrl('G'):
	    if(FOOTER_ROWS(ps_global) == 1 && km_popped == 0){
		km_popped++;
		FOOTER_ROWS(ps_global) = 3;
		line = -3;
		real_line = ps_global->ttyo->screen_rows + line;
		if(lastc)
		  (void)pico_set_colorp(lastc, PSC_NONE);
		else
		  EndInverse();

		clearfooter(ps_global);
		if(promptc)
		  (void)pico_set_colorp(promptc, PSC_NONE);
		else
		  StartInverse();

		draw_radio_prompt(real_line, RAD_BUT_COL, maxcol, q);
		break;
	    }

	    if(flags & RB_RET_HELP){
		ch = 3;
		goto out_of_loop;
	    }
	    else if(help_text != NO_HELP && FOOTER_ROWS(ps_global) > 1){
		mark_keymenu_dirty();
		if(lastc)
		  (void)pico_set_colorp(lastc, PSC_NONE);
		else
		  EndInverse();

		MoveCursor(real_line + 1, RAD_BUT_COL);
		CleartoEOLN();
		MoveCursor(real_line + 2, RAD_BUT_COL);
		CleartoEOLN();
		radio_help(real_line, RAD_BUT_COL, help_text);
		sleep(5);
		MoveCursor(real_line, MIN(RAD_BUT_COL+utf8_width(q), maxcol+1));
		if(promptc)
		  (void)pico_set_colorp(promptc, PSC_NONE);
		else
		  StartInverse();
	    }
	    else
	      Writechar(BELL, 0);

            break;
            

          case NO_OP_COMMAND:
	    goto newcmd;		/* misunderstood escape? */

          case NO_OP_IDLE:		/* UNODIR, keep the stream alive */
	    if(flags & RB_NO_NEWMAIL)
	      goto newcmd;

	    i = new_mail(0, VeryBadTime, NM_DEFER_SORT);
	    if(sp_expunge_count(ps_global->mail_stream)
	       && flags & RB_SEQ_SENSITIVE){
		if(on_ctrl_C)
		  ch = on_ctrl_C;
		else
		  ch = SEQ_EXCEPTION;

		goto out_of_loop;
	    }

	    if(i < 0)
	      break;		/* no changes, get on with life */
            /* Else fall into redraw to adjust displayed numbers and such */


          case KEY_RESIZE:
          case ctrl('L'):
            real_line = line > 0 ? line : ps_global->ttyo->screen_rows + line;
	    if(lastc)
	      (void)pico_set_colorp(lastc, PSC_NONE);
	    else
	      EndInverse();

            ClearScreen();
            redraw_titlebar();
            if(ps_global->redrawer != NULL)
              (*ps_global->redrawer)();
	    if(FOOTER_ROWS(ps_global) == 3 || km_popped)
              redraw_keymenu();

	    if(ps_global->ttyo->screen_cols - max_label - 1 > 0)
	      maxcol = ps_global->ttyo->screen_cols - max_label - 1;
	    else
	      maxcol = 0;

	    if(promptc)
	      (void)pico_set_colorp(promptc, PSC_NONE);
	    else
	      StartInverse();

	    draw_radio_prompt(real_line, RAD_BUT_COL, maxcol, q);
            break;

        } /* switch */
    }

  out_of_loop:

#ifdef	_WINDOWS
    if(!cursor_shown)
      mswin_showcaret(0);
#endif

    fs_give((void **) &q);
    if(ds)
      fs_give((void **) &ds);

    if(lastc){
	(void) pico_set_colorp(lastc, PSC_NONE);
	free_color_pair(&lastc);
	if(promptc)
	  free_color_pair(&promptc);
    }
    else
      EndInverse();

    fflush(stdout);
    resume_busy_cue(0);
    if(km_popped){
	FOOTER_ROWS(ps_global) = 1;
	clearfooter(ps_global);
	ps_global->mangled_body = 1;
    }

    return(ch);
}


#define OTHER_RETURN_VAL 1300
#define KEYS_PER_LIST 8

/*
 * This should really be part of radio_buttons itself, I suppose. It was
 * easier to do it this way. This is for when there are more than 12
 * possible commands. We could have all the radio_buttons calls call this
 * instead of radio_buttons, or rename this to radio_buttons.
 *
 * Radio_buttons is limited to 10 visible commands unless there is no Help,
 * in which case it is 11 visible commands.
 * Double_radio_buttons is limited to 16 visible commands because it uses
 * slots 3 and 4 for readability and the OTHER CMD.
 */
int
double_radio_buttons(char *prompt, int line, ESCKEY_S *esc_list, int dflt, int on_ctrl_C, HelpType help_text, int flags)
{
    ESCKEY_S *list = NULL, *list1 = NULL, *list2 = NULL;
    int       i = 0, j;
    int       v = OTHER_RETURN_VAL, listnum = 0;

#ifdef _WINDOWS
    if(mswin_usedialog())
      return(radio_buttons(prompt, line, esc_list, dflt, on_ctrl_C,
			   help_text, flags));
#endif

    /* check to see if it will all fit in one */
    while(esc_list && esc_list[i].ch != -1)
      i++;

    i++;		/* for ^C */
    if(help_text != NO_HELP)
      i++;
    
    if(i <= 12)
      return(radio_buttons(prompt, line, esc_list, dflt, on_ctrl_C,
			   help_text, flags));

    /*
     * Won't fit, split it into two lists.
     *
     * We can fit at most 8 items in the visible list. The rest of
     * the commands have to be invisible. Each of list1 and list2 should
     * have no more than 8 visible (name != "" || label != "") items.
     */
    list1 = (ESCKEY_S *)fs_get((KEYS_PER_LIST+1) * sizeof(*list1));
    memset(list1, 0, (KEYS_PER_LIST+1) * sizeof(*list1));
    list2 = (ESCKEY_S *)fs_get((KEYS_PER_LIST+1) * sizeof(*list2));
    memset(list2, 0, (KEYS_PER_LIST+1) * sizeof(*list2));

    for(j=0,i=0; esc_list[i].ch != -1 && j < KEYS_PER_LIST; j++,i++)
      list1[j] = esc_list[i];
    
    list1[j].ch = -1;

    for(j=0; esc_list[i].ch != -1 && j < KEYS_PER_LIST; j++,i++)
      list2[j] = esc_list[i];

    list2[j].ch = -1;

    list = construct_combined_esclist(list1, list2);

    while(v == OTHER_RETURN_VAL){
	v = radio_buttons(prompt,line,list,dflt,on_ctrl_C,help_text,flags);
	if(v == OTHER_RETURN_VAL){
	    fs_give((void **) &list);
	    listnum = 1 - listnum;
	    list = construct_combined_esclist(listnum ? list2 : list1,
					      listnum ? list1 : list2);
	}
    }

    if(list)
      fs_give((void **) &list);
    if(list1)
      fs_give((void **) &list1);
    if(list2)
      fs_give((void **) &list2);

    return(v);
}


ESCKEY_S *
construct_combined_esclist(ESCKEY_S *list1, ESCKEY_S *list2)
{
    ESCKEY_S *list;
    int       i, j=0, count;
    
    count = 2;	/* for blank key and for OTHER key */
    for(i=0; list1 && list1[i].ch != -1; i++)
      count++;
    for(i=0; list2 && list2[i].ch != -1; i++)
      count++;
    
    list = (ESCKEY_S *) fs_get((count + 1) * sizeof(*list));
    memset(list, 0, (count + 1) * sizeof(*list));

    list[j].ch    = -2;			/* leave blank */
    list[j].rval  = 0;
    list[j].name  = NULL;
    list[j++].label = NULL;

    list[j].ch    = 'o';
    list[j].rval  = OTHER_RETURN_VAL;
    list[j].name  = "O";
    list[j].label = N_("OTHER CMDS");

    /*
     * Make sure that O for OTHER CMD or the return val for OTHER CMD
     * isn't used for something else.
     */
    for(i=0; list1 && list1[i].ch != -1; i++){
	if(list1[i].rval == list[j].rval)
	  alpine_panic("1bad rval in d_r");
	if(F_OFF(F_USE_FK,ps_global) && list1[i].ch == list[j].ch)
	  alpine_panic("1bad ch in ccl");
    }

    for(i=0; list2 && list2[i].ch != -1; i++){
	if(list2[i].rval == list[j].rval)
	  alpine_panic("2bad rval in d_r");
	if(F_OFF(F_USE_FK,ps_global) && list2[i].ch == list[j].ch)
	  alpine_panic("2bad ch in ccl");
    }

    j++;

    /* the visible set */
    for(i=0; list1 && list1[i].ch != -1; i++){
	if(i >= KEYS_PER_LIST && list1[i].label[0] != '\0')
	  alpine_panic("too many visible keys in ccl");

	list[j++] = list1[i];
    }

    /* the rest are invisible */
    for(i=0; list2 && list2[i].ch != -1; i++){
	list[j] = list2[i];
	list[j].label = "";
	list[j++].name  = "";
    }

    list[j].ch = -1;

    return(list);
}


/*----------------------------------------------------------------------

  ----*/
void
radio_help(int line, int column, HelpType help)
{
    char **text;

    /* assumption here is that HelpType is char **  */
    text = help;
    if(text == NULL)
      return;
    
    MoveCursor(line + 1, column);
    CleartoEOLN();
    if(text[0])
      PutLine0(line + 1, column, text[0]);

    MoveCursor(line + 2, column);
    CleartoEOLN();
    if(text[1])
      PutLine0(line + 2, column, text[1]);

    fflush(stdout);
}


/*----------------------------------------------------------------------
   Paint the screen with the radio buttons prompt
  ----*/
void
draw_radio_prompt(int line, unsigned start_c, unsigned max_c, char *q)
{
    size_t len;
    unsigned x, width, got_width;
    char  *tmpq = NULL, *useq;

    width = utf8_width(q);
    if(width > max_c - start_c + 1){
	tmpq = (char *) fs_get((len=(strlen(q)+1)) * sizeof(char));
	(void) utf8_to_width(tmpq, q, len, max_c - start_c + 1, &got_width);
	useq = tmpq;
	width = got_width;
    }
    else
      useq = q;

    PutLine0(line, start_c, useq);
    x = start_c + width;
    MoveCursor(line, x);
    while(x++ < ps_global->ttyo->screen_cols)
      Writechar(' ', 0);

    MoveCursor(line, start_c + width);
    fflush(stdout);
    if(tmpq)
      fs_give((void **) &tmpq);
}
