text: #include <cdk_int.h>
  #include <scroller.h>
  
  /*
   * $Author: tom $
   * $Date: 2016/11/20 20:14:41 $
   * $Revision: 1.146 $
   */
  
  /*
   * Declare file local prototypes.
   */
  static int createList (CDKRADIO *radio, CDK_CSTRING2 list, int listSize, int width);
  static void drawCDKRadioList (CDKRADIO *radio, boolean Box);
  static void setViewSize (CDKRADIO *scrollp, int listSize);
  static int maxViewSize (CDKRADIO *scrollp);
  
  /* Determine how many characters we can shift to the right */
  /* before all the items have been scrolled off the screen. */
  #define AvailableWidth(w)  ((w)->boxWidth - 2*BorderOf(w) - 3)
  #define updateViewWidth(w, widest) \
  	(w)->maxLeftChar = (((w)->boxWidth > widest) \
  			      ? 0 \
  			      : (widest - AvailableWidth(w)))
  #define WidestItem(w)      ((w)->maxLeftChar + AvailableWidth(w))
  
  #define SCREENPOS(w,n) (w)->itemPos[n] - (w)->leftChar + scrollbarAdj + BorderOf(w)
  
  DeclareCDKObjects (RADIO, Radio, setCdk, Int);
  
  /*
   * This function creates the radio widget.
   */
  CDKRADIO *newCDKRadio (CDKSCREEN *cdkscreen,
  		       int xplace,
  		       int yplace,
  		       int splace,
  		       int height,
  		       int width,
  		       const char *title,
  		       CDK_CSTRING2 list,
  		       int listSize,
  		       chtype choiceChar,
  		       int defItem,
  		       chtype highlight,
  		       boolean Box,
  		       boolean shadow)
  {
     /* *INDENT-EQLS* */
     CDKRADIO *radio      = 0;
     int parentWidth      = getmaxx (cdkscreen->window);
     int parentHeight     = getmaxy (cdkscreen->window);
     int boxWidth;
     int boxHeight;
     int xpos             = xplace;
     int ypos             = yplace;
     int widestItem       = 0;
     int j;
     /* *INDENT-OFF* */
     static const struct { int from; int to; } bindings[] = {
  		{ CDK_BACKCHAR,	KEY_PPAGE },
  		{ CDK_FORCHAR,	KEY_NPAGE },
  		{ 'g',		KEY_HOME },
  		{ '1',		KEY_HOME },
  		{ 'G',		KEY_END },
  		{ '<',		KEY_HOME },
  		{ '>',		KEY_END },
     };
     /* *INDENT-ON* */
  
     if ((radio = newCDKObject (CDKRADIO, &my_funcs)) == 0)
     {
        return (0);
     }
  
     setCDKRadioBox (radio, Box);
  
     /*
      * If the height is a negative value, the height will
      * be ROWS-height, otherwise, the height will be the
      * given height.
      */
     boxHeight = setWidgetDimension (parentHeight, height, 0);
  
     /*
      * If the width is a negative value, the width will
      * be COLS-width, otherwise, the width will be the
      * given width.
      */
     boxWidth = setWidgetDimension (parentWidth, width, 5);
  
     boxWidth = setCdkTitle (ObjOf (radio), title, boxWidth);
  
     /* Set the box height. */
     if (TitleLinesOf (radio) > boxHeight)
     {
        boxHeight = TitleLinesOf (radio)
  	 + MINIMUM (listSize, 8)
  	 + 2 * BorderOf (radio);
     }
  
     /* Adjust the box width if there is a scroll bar. */
     if (splace == LEFT || splace == RIGHT)
     {
        boxWidth++;
        radio->scrollbar = TRUE;
     }
     else
     {
        radio->scrollbar = FALSE;
     }
  
     /*
      * Make sure we didn't extend beyond the dimensions of the window.
      */
     radio->boxWidth = MINIMUM (boxWidth, parentWidth);
     radio->boxHeight = MINIMUM (boxHeight, parentHeight);
  
     setViewSize (radio, listSize);
  
     /* Each item in the needs to be converted to chtype * */
     widestItem = createList (radio, list, listSize, radio->boxWidth);
     if (widestItem > 0)
     {
        updateViewWidth (radio, widestItem);
     }
     else if (listSize)
     {
        destroyCDKObject (radio);
        return (0);
     }
  
     /* Rejustify the x and y positions if we need to. */
     alignxy (cdkscreen->window, &xpos, &ypos, radio->boxWidth, radio->boxHeight);
  
     /* Make the radio window */
     radio->win = newwin (radio->boxHeight, radio->boxWidth, ypos, xpos);
  
     /* Is the window null??? */
     if (radio->win == 0)
     {
        destroyCDKObject (radio);
        return (0);
     }
  
     /* Turn on the keypad. */
     keypad (radio->win, TRUE);
  
     /* Create the scrollbar window. */
     if (splace == RIGHT)
     {
        radio->scrollbarWin = subwin (radio->win,
  				    maxViewSize (radio), 1,
  				    SCREEN_YPOS (radio, ypos),
  				    (xpos
  				     + radio->boxWidth
  				     - BorderOf (radio)
  				     - 1));
     }
     else if (splace == LEFT)
     {
        radio->scrollbarWin = subwin (radio->win,
  				    maxViewSize (radio), 1,
  				    SCREEN_YPOS (radio, ypos),
  				    SCREEN_XPOS (radio, xpos));
     }
     else
     {
        radio->scrollbarWin = 0;
     }
  
     /* *INDENT-EQLS* Set the rest of the variables */
     ScreenOf (radio)             = cdkscreen;
     radio->parent                = cdkscreen->window;
     radio->scrollbarPlacement    = splace;
     radio->widestItem            = widestItem;
     radio->leftChar              = 0;
     radio->selectedItem          = 0;
     radio->highlight             = highlight;
     radio->choiceChar            = choiceChar;
     radio->leftBoxChar           = (chtype)'[';
     radio->rightBoxChar          = (chtype)']';
     radio->defItem               = defItem;
     initExitType (radio);
     ObjOf (radio)->inputWindow   = radio->win;
     ObjOf (radio)->acceptsFocus  = TRUE;
     radio->shadow                = shadow;
  
     setCDKRadioCurrentItem (radio, 0);
  
     /* Do we need to create the shadow??? */
     if (shadow)
     {
        radio->shadowWin = newwin (boxHeight, boxWidth + 1, ypos + 1, xpos + 1);
     }
  
     /* Setup the key bindings. */
     for (j = 0; j < (int)SIZEOF (bindings); ++j)
        bindCDKObject (vRADIO,
  		     radio,
  		     (chtype)bindings[j].from,
  		     getcCDKBind,
  		     (void *)(long)bindings[j].to);
  
     /* Register this baby. */
     registerCDKObject (cdkscreen, vRADIO, radio);
  
     /* Return the radio list */
     return (radio);
  }
  
  /*
   * Put the cursor on the currently-selected item.
   */
  static void fixCursorPosition (CDKRADIO *widget)
  {
     scroller_FixCursorPosition ((CDKSCROLLER *)widget);
  }
  
  /*
   * This actually manages the radio widget.
   */
  int activateCDKRadio (CDKRADIO *radio, chtype *actions)
  {
     /* Draw the radio list. */
     drawCDKRadio (radio, ObjOf (radio)->box);
  
     if (actions == 0)
     {
        chtype input;
        boolean functionKey;
  
        for (;;)
        {
  	 int ret;
  
  	 fixCursorPosition (radio);
  	 input = (chtype)getchCDKObject (ObjOf (radio), &functionKey);
  
  	 /* Inject the character into the widget. */
  	 ret = injectCDKRadio (radio, input);
  	 if (radio->exitType != vEARLY_EXIT)
  	 {
  	    return ret;
  	 }
        }
     }
     else
     {
        int length = chlen (actions);
        int j;
  
        /* Inject each character one at a time. */
        for (j = 0; j < length; j++)
        {
  	 int ret = injectCDKRadio (radio, actions[j]);
  	 if (radio->exitType != vEARLY_EXIT)
  	 {
  	    return ret;
  	 }
        }
     }
  
     /* Set the exit type and return. */
     setExitType (radio, 0);
     return -1;
  }
  
  /*
   * This injects a single character into the widget.
   */
  static int _injectCDKRadio (CDKOBJS *object, chtype input)
  {
     CDKRADIO *radio = (CDKRADIO *)object;
     CDKSCROLLER *widget = (CDKSCROLLER *)object;
     int ppReturn = 1;
     int ret = unknownInt;
     bool complete = FALSE;
  
     /* Set the exit type. */
     setExitType (widget, 0);
  
     /* Draw the widget list */
     drawCDKRadioList (radio, ObjOf (widget)->box);
  
     /* Check if there is a pre-process function to be called. */
     if (PreProcessFuncOf (widget) != 0)
     {
        /* Call the pre-process function. */
        ppReturn = PreProcessFuncOf (widget) (vRADIO,
  					    widget,
  					    PreProcessDataOf (widget),
  					    input);
     }
  
     /* Should we continue? */
     if (ppReturn != 0)
     {
        /* Check for a predefined key binding. */
        if (checkCDKObjectBind (vRADIO, widget, input) != 0)
        {
  	 checkEarlyExit (widget);
  	 complete = TRUE;
        }
        else
        {
  	 switch (input)
  	 {
  	 case KEY_UP:
  	    scroller_KEY_UP (widget);
  	    break;
  
  	 case KEY_DOWN:
  	    scroller_KEY_DOWN (widget);
  	    break;
  
  	 case KEY_RIGHT:
  	    scroller_KEY_RIGHT (widget);
  	    break;
  
  	 case KEY_LEFT:
  	    scroller_KEY_LEFT (widget);
  	    break;
  
  	 case KEY_PPAGE:
  	    scroller_KEY_PPAGE (widget);
  	    break;
  
  	 case KEY_NPAGE:
  	    scroller_KEY_NPAGE (widget);
  	    break;
  
  	 case KEY_HOME:
  	    scroller_KEY_HOME (widget);
  	    break;
  
  	 case KEY_END:
  	    scroller_KEY_END (widget);
  	    break;
  
  	 case '$':
  	    widget->leftChar = widget->maxLeftChar;
  	    break;
  
  	 case '|':
  	    widget->leftChar = 0;
  	    break;
  
  	 case SPACE:
  	    radio->selectedItem = widget->currentItem;
  	    break;
  
  	 case KEY_ESC:
  	    setExitType (widget, input);
  	    ret = -1;
  	    complete = TRUE;
  	    break;
  
  	 case KEY_ERROR:
  	    setExitType (widget, input);
  	    complete = TRUE;
  	    break;
  
  	 case KEY_TAB:
  	 case KEY_ENTER:
  	    setExitType (widget, input);
  	    ret = radio->selectedItem;
  	    complete = TRUE;
  	    break;
  
  	 case CDK_REFRESH:
  	    eraseCDKScreen (ScreenOf (widget));
  	    refreshCDKScreen (ScreenOf (widget));
  	    break;
  
  	 default:
  	    break;
  	 }
        }
  
        /* Should we call a post-process? */
        if (!complete && (PostProcessFuncOf (widget) != 0))
        {
  	 PostProcessFuncOf (widget) (vRADIO,
  				     widget,
  				     PostProcessDataOf (widget),
  				     input);
        }
     }
  
     if (!complete)
     {
        drawCDKRadioList (radio, ObjOf (widget)->box);
        setExitType (widget, 0);
     }
  
     fixCursorPosition (radio);
     ResultOf (widget).valueInt = ret;
     return (ret != unknownInt);
  }
  
  /*
   * This moves the radio field to the given location.
   */
  static void _moveCDKRadio (CDKOBJS *object,
  			   int xplace,
  			   int yplace,
  			   boolean relative,
  			   boolean refresh_flag)
  {
     CDKRADIO *radio = (CDKRADIO *)object;
     /* *INDENT-EQLS* */
     int currentX = getbegx (radio->win);
     int currentY = getbegy (radio->win);
     int xpos     = xplace;
     int ypos     = yplace;
     int xdiff    = 0;
     int ydiff    = 0;
  
     /*
      * If this is a relative move, then we will adjust where we want
      * to move to.
      */
     if (relative)
     {
        xpos = getbegx (radio->win) + xplace;
        ypos = getbegy (radio->win) + yplace;
     }
  
     /* Adjust the window if we need to. */
     alignxy (WindowOf (radio), &xpos, &ypos, radio->boxWidth, radio->boxHeight);
  
     /* Get the difference. */
     xdiff = currentX - xpos;
     ydiff = currentY - ypos;
  
     /* Move the window to the new location. */
     moveCursesWindow (radio->win, -xdiff, -ydiff);
     moveCursesWindow (radio->scrollbarWin, -xdiff, -ydiff);
     moveCursesWindow (radio->shadowWin, -xdiff, -ydiff);
  
     /* Touch the windows so they 'move'. */
     refreshCDKWindow (WindowOf (radio));
  
     /* Redraw the window, if they asked for it. */
     if (refresh_flag)
     {
        drawCDKRadio (radio, ObjOf (radio)->box);
     }
  }
  
  static int maxViewSize (CDKRADIO *widget)
  {
     return scroller_MaxViewSize ((CDKSCROLLER *)widget);
  }
  
  /*
   * Set variables that depend upon the list-size.
   */
  static void setViewSize (CDKRADIO *widget, int listSize)
  {
     scroller_SetViewSize ((CDKSCROLLER *)widget, listSize);
  }
  
  /*
   * This function draws the radio widget.
   */
  static void _drawCDKRadio (CDKOBJS *object, boolean Box GCC_UNUSED)
  {
     CDKRADIO *radio = (CDKRADIO *)object;
  
     /* Do we need to draw in the shadow??? */
     if (radio->shadowWin != 0)
     {
        drawShadow (radio->shadowWin);
     }
  
     drawCdkTitle (radio->win, object);
  
     /* Draw in the radio list. */
     drawCDKRadioList (radio, ObjOf (radio)->box);
  }
  
  /*
   * This redraws the radio list.
   */
  static void drawCDKRadioList (CDKRADIO *radio, boolean Box)
  {
     int scrollbarAdj = (radio->scrollbarPlacement == LEFT) ? 1 : 0;
     int j, k;
  
     /* draw the list */
     for (j = 0; j < radio->viewSize; j++)
     {
        int xpos = SCREEN_XPOS (radio, 0);
        int ypos = SCREEN_YPOS (radio, j);
  
        /* Draw the empty string. */
        writeBlanks (radio->win, xpos, ypos,
  		   HORIZONTAL, 0, radio->boxWidth - BorderOf (radio));
  
        k = j + radio->currentTop;
  
        /* Draw the element in the radio list. */
        if (k < radio->listSize)
        {
  	 int screenPos = SCREENPOS (radio, k);
  
  	 /* Draw the line. */
  	 writeChtype (radio->win,
  		      (screenPos >= 0) ? screenPos : 1,
  		      ypos,
  		      radio->item[k],
  		      HORIZONTAL,
  		      (screenPos >= 0) ? 0 : (1 - screenPos),
  		      radio->itemLen[k]);
  
  	 /* Draw the selected choice... */
  	 xpos += scrollbarAdj;
  	 (void)mvwaddch (radio->win, ypos, xpos++, radio->leftBoxChar);
  	 (void)mvwaddch (radio->win, ypos, xpos++, ((k == radio->selectedItem)
  						    ? radio->choiceChar
  						    : ' '));
  	 (void)mvwaddch (radio->win, ypos, xpos++, radio->rightBoxChar);
        }
     }
  
     /* Highlight the current item. */
     if (ObjPtr (radio)->hasFocus)
     {
        k = radio->currentItem;
        if (k < radio->listSize)
        {
  	 int screenPos = SCREENPOS (radio, k);
  	 int ypos = SCREEN_YPOS (radio, radio->currentHigh);
  
  	 writeChtypeAttrib (radio->win,
  			    (screenPos >= 0) ? screenPos : (1 + scrollbarAdj),
  			    ypos,
  			    radio->item[k],
  			    radio->highlight,
  			    HORIZONTAL,
  			    (screenPos >= 0) ? 0 : (1 - screenPos),
  			    radio->itemLen[k]);
        }
     }
  
     if (radio->scrollbar)
     {
        radio->togglePos = floorCDK (radio->currentItem * (double)radio->step);
        radio->togglePos = MINIMUM (radio->togglePos,
  				  getmaxy (radio->scrollbarWin) - 1);
  
        (void)mvwvline (radio->scrollbarWin, 0, 0,
  		      ACS_CKBOARD, getmaxy (radio->scrollbarWin));
        (void)mvwvline (radio->scrollbarWin, radio->togglePos, 0,
  		      ' ' | A_REVERSE, radio->toggleSize);
     }
  
     /* Box it if needed. */
     if (Box)
     {
        drawObjBox (radio->win, ObjOf (radio));
     }
     else
     {
        touchwin (radio->win);
     }
  
     fixCursorPosition (radio);
  }
  
  /*
   * This sets the background attribute of the widget.
   */
  static void _setBKattrRadio (CDKOBJS *object, chtype attrib)
  {
     if (object != 0)
     {
        CDKRADIO *widget = (CDKRADIO *)object;
  
        wbkgd (widget->win, attrib);
        if (widget->scrollbarWin != 0)
        {
  	 wbkgd (widget->scrollbarWin, attrib);
        }
     }
  }
  
  static void destroyInfo (CDKRADIO *widget)
  {
     CDKfreeChtypes (widget->item);
     widget->item = 0;
  
     freeAndNull (widget->itemLen);
     freeAndNull (widget->itemPos);
  }
  
  /*
   * This function destroys the radio widget.
   */
  static void _destroyCDKRadio (CDKOBJS *object)
  {
     if (object != 0)
     {
        CDKRADIO *radio = (CDKRADIO *)object;
  
        cleanCdkTitle (object);
        destroyInfo (radio);
  
        /* Clean up the windows. */
        deleteCursesWindow (radio->scrollbarWin);
        deleteCursesWindow (radio->shadowWin);
        deleteCursesWindow (radio->win);
  
        /* Clean the key bindings. */
        cleanCDKObjectBindings (vRADIO, radio);
  
        /* Unregister this object. */
        unregisterCDKObject (vRADIO, radio);
     }
  }
  
  /*
   * This function erases the radio widget.
   */
  static void _eraseCDKRadio (CDKOBJS *object)
  {
     if (validCDKObject (object))
     {
        CDKRADIO *radio = (CDKRADIO *)object;
  
        eraseCursesWindow (radio->win);
        eraseCursesWindow (radio->shadowWin);
     }
  }
  
  /*
   * This set various attributes of the radio list.
   */
  void setCDKRadio (CDKRADIO *radio, chtype highlight, chtype choiceChar, int Box)
  {
     setCDKRadioHighlight (radio, highlight);
     setCDKRadioChoiceCharacter (radio, choiceChar);
     setCDKRadioBox (radio, Box);
  }
  
  /*
   * This sets the radio list items.
   */
  void setCDKRadioItems (CDKRADIO *radio, CDK_CSTRING2 list, int listSize)
  {
     int widestItem;
     int j = 0;
  
     widestItem = createList (radio, list, listSize, radio->boxWidth);
     if (widestItem <= 0)
        return;
  
     /* Clean up the display. */
     for (j = 0; j < radio->viewSize; j++)
     {
        writeBlanks (radio->win,
  		   SCREEN_XPOS (radio, 0),
  		   SCREEN_YPOS (radio, j),
  		   HORIZONTAL,
  		   0,
  		   radio->boxWidth - BorderOf (radio));
     }
  
     setViewSize (radio, listSize);
  
     setCDKRadioCurrentItem (radio, 0);
     radio->leftChar = 0;
     radio->selectedItem = 0;
  
     updateViewWidth (radio, widestItem);
  }
  int getCDKRadioItems (CDKRADIO *radio, char **list)
  {
     if (list != 0)
     {
        int j;
  
        for (j = 0; j < radio->listSize; j++)
        {
  	 list[j] = chtype2Char (radio->item[j]);
        }
     }
     return radio->listSize;
  }
  
  /*
   * This sets the highlight bar of the radio list.
   */
  void setCDKRadioHighlight (CDKRADIO *radio, chtype highlight)
  {
     radio->highlight = highlight;
  }
  chtype getCDKRadioHighlight (CDKRADIO *radio)
  {
     return radio->highlight;
  }
  
  /*
   * This sets the character to use when selecting an item in the list.
   */
  void setCDKRadioChoiceCharacter (CDKRADIO *radio, chtype character)
  {
     radio->choiceChar = character;
  }
  chtype getCDKRadioChoiceCharacter (CDKRADIO *radio)
  {
     return radio->choiceChar;
  }
  
  /*
   * This sets the character to use to draw the left side of the
   * choice box on the list.
   */
  void setCDKRadioLeftBrace (CDKRADIO *radio, chtype character)
  {
     radio->leftBoxChar = character;
  }
  chtype getCDKRadioLeftBrace (CDKRADIO *radio)
  {
     return radio->leftBoxChar;
  }
  
  /*
   * This sets the character to use to draw the right side of the
   * choice box on the list.
   */
  void setCDKRadioRightBrace (CDKRADIO *radio, chtype character)
  {
     radio->rightBoxChar = character;
  }
  chtype getCDKRadioRightBrace (CDKRADIO *radio)
  {
     return radio->rightBoxChar;
  }
  
  /*
   * This sets the box attribute of the widget.
   */
  void setCDKRadioBox (CDKRADIO *radio, boolean Box)
  {
     ObjOf (radio)->box = Box;
     ObjOf (radio)->borderSize = Box ? 1 : 0;
  }
  boolean getCDKRadioBox (CDKRADIO *radio)
  {
     return ObjOf (radio)->box;
  }
  
  /*
   * This sets the current high lighted item of the widget
   */
  void setCDKRadioCurrentItem (CDKRADIO *radio, int item)
  {
     scroller_SetPosition ((CDKSCROLLER *)radio, item);
     radio->selectedItem = item;
  }
  int getCDKRadioCurrentItem (CDKRADIO *radio)
  {
     return radio->currentItem;
  }
  
  /*
   * This sets the selected item of the widget
   */
  void setCDKRadioSelectedItem (CDKRADIO *radio, int item)
  {
     radio->selectedItem = item;
  }
  int getCDKRadioSelectedItem (CDKRADIO *radio)
  {
     return radio->selectedItem;
  }
  
  static void _focusCDKRadio (CDKOBJS *object)
  {
     CDKRADIO *radio = (CDKRADIO *)object;
  
     drawCDKRadioList (radio, ObjOf (radio)->box);
  }
  
  static void _unfocusCDKRadio (CDKOBJS *object)
  {
     CDKRADIO *radio = (CDKRADIO *)object;
  
     drawCDKRadioList (radio, ObjOf (radio)->box);
  }
  
  static int createList (CDKRADIO *radio, CDK_CSTRING2 list, int listSize, int boxWidth)
  {
     int status = 0;
     int widestItem = 0;
  
     if (listSize >= 0)
     {
        /* *INDENT-EQLS* */
        chtype **newList = typeCallocN (chtype *, listSize + 1);
        int *newLen      = typeCallocN (int, listSize + 1);
        int *newPos      = typeCallocN (int, listSize + 1);
  
        if (newList != 0
  	  && newLen != 0
  	  && newPos != 0)
        {
  	 int j;
  
  	 /* Each item in the needs to be converted to chtype * */
  	 status = 1;
  	 boxWidth -= (2 + BorderOf (radio));
  	 for (j = 0; j < listSize; j++)
  	 {
  	    newList[j] = char2Chtype (list[j], &newLen[j], &newPos[j]);
  	    if (newList[j] == 0)
  	    {
  	       status = 0;
  	       break;
  	    }
  	    newPos[j] = justifyString (boxWidth, newLen[j], newPos[j]) + 3;
  	    widestItem = MAXIMUM (widestItem, newLen[j]);
  	 }
  	 if (status)
  	 {
  	    destroyInfo (radio);
  
  	    radio->item = newList;
  	    radio->itemLen = newLen;
  	    radio->itemPos = newPos;
  	 }
  	 else
  	 {
  	    CDKfreeChtypes (newList);
  	    freeChecked (newLen);
  	    freeChecked (newPos);
  	 }
        }
        else
        {
  	 CDKfreeChtypes (newList);
  	 freeChecked (newLen);
  	 freeChecked (newPos);
        }
     }
     else
     {
        destroyInfo (radio);
     }
  
     return status ? widestItem : 0;
  }
  
  dummyRefreshData (Radio)
  
  dummySaveData (Radio)
  