#include <cdk_int.h>

/*
 * $Author: Thorsten.Glaser $
 * $Date: 2005/04/24 20:40:43 $
 * $Revision: 1.121 $
 */

/*
 * Declare file local prototypes.
 */
static int createList (CDKRADIO *radio, char **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))

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, char *title, char **list, int listSize, chtype choiceChar, int defItem, chtype highlight, boolean Box, boolean shadow)
{
   CDKRADIO *radio	= 0;
   int parentWidth	= getmaxx(cdkscreen->window);
   int parentHeight	= getmaxy(cdkscreen->window);
   int boxWidth		= width;
   int boxHeight	= height;
   int xpos		= xplace;
   int ypos		= yplace;
   int widestItem	= 0;
   int j;

   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 },
   };

   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)
   {
      destroyCDKObject(radio);
      return (0);
   }

   updateViewWidth(radio, widestItem);

   /* 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;
   }

   /* 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, bindings[j].from, getcCDKBind, (void *)(long)bindings[j].to);

   /* Register this baby. */
   registerCDKObject (cdkscreen, vRADIO, radio);

   /* Return the radio list */
   return (radio);
}

/*
 * This actually manages the radio widget.
 */
int activateCDKRadio (CDKRADIO *radio, chtype *actions)
{
   /* Draw the radio list. */
   drawCDKRadio (radio, ObjOf(radio)->box);

   /* Check if actions is null. */
   if (actions == 0)
   {
      chtype input;
      int ret;

      for (;;)
      {
	 /* Get the input. */
	 input = getcCDKObject (ObjOf(radio));

	 /* Inject the character into the widget. */
	 ret = injectCDKRadio (radio, input);
	 if (radio->exitType != vEARLY_EXIT)
	 {
	    return ret;
	 }
      }
   }
   else
   {
      int length = chlen (actions);
      int j, ret;

      /* Inject each character one at a time. */
      for (j=0; j < length; j++)
      {
	 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;
   int ppReturn = 1;
   int ret = unknownInt;
   bool complete = FALSE;

   /* Set the exit type. */
   setExitType(radio, 0);

   /* Draw the radio list */
   drawCDKRadioList (radio, ObjOf(radio)->box);

   /* Check if there is a pre-process function to be called. */
   if (PreProcessFuncOf(radio) != 0)
   {
      /* Call the pre-process function. */
      ppReturn = PreProcessFuncOf(radio) (vRADIO, radio, PreProcessDataOf(radio), input);
   }

   /* Should we continue? */
   if (ppReturn != 0)
   {
      /* Check for a predefined key binding. */
      if (checkCDKObjectBind (vRADIO, radio, input) != 0)
      {
	 checkEarlyExit(radio);
	 complete = TRUE;
      }
      else
      {
	 switch (input)
	 {
	    case KEY_UP :
		 scroller_KEY_UP(radio);
		 break;

	    case KEY_DOWN :
		 scroller_KEY_DOWN(radio);
		 break;

	    case KEY_RIGHT :
		 scroller_KEY_RIGHT(radio);
		 break;

	    case KEY_LEFT :
		 scroller_KEY_LEFT(radio);
		 break;

	    case KEY_PPAGE :
		 scroller_KEY_PPAGE(radio);
		 break;

	    case KEY_NPAGE :
		 scroller_KEY_NPAGE(radio);
		 break;

	    case KEY_HOME :
		 scroller_KEY_HOME(radio);
		 break;

	    case KEY_END :
		 scroller_KEY_END(radio);
		 break;

	    case '$' :
		 radio->leftChar = radio->maxLeftChar;
		 break;

	    case '|' :
		 radio->leftChar = 0;
		 break;

	    case SPACE :
		 radio->selectedItem = radio->currentItem;
		 break;

	    case KEY_ESC :
		 setExitType(radio, input);
		 ret = -1;
		 complete = TRUE;
		 break;

	    case KEY_TAB : case KEY_ENTER :
		 setExitType(radio, input);
		 ret = radio->selectedItem;
		 complete = TRUE;
		 break;

	    case CDK_REFRESH :
		 eraseCDKScreen (ScreenOf(radio));
		 refreshCDKScreen (ScreenOf(radio));
		 break;

	    default :
		 break;
	 }
      }

      /* Should we call a post-process? */
      if (!complete && (PostProcessFuncOf(radio) != 0))
      {
	 PostProcessFuncOf(radio) (vRADIO, radio, PostProcessDataOf(radio), input);
      }
   }

   if (!complete) {
      drawCDKRadioList (radio, ObjOf(radio)->box);
      setExitType(radio, 0);
   }

   ResultOf(radio).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;
   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(widget);
}

/*
 * Set variables that depend upon the list-size.
 */
static void setViewSize(CDKRADIO *widget, int listSize)
{
   scroller_SetViewSize(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);
}

#define SCREENPOS(w,n) (w)->itemPos[n] - (w)->leftChar + scrollbarAdj + BorderOf(w)

/*
 * This redraws the radio list.
 */
static void drawCDKRadioList (CDKRADIO *radio, boolean Box)
{
   int scrollbarAdj	= (radio->scrollbarPlacement == LEFT) ? 1 : 0;
   int screenPos	= 0;
   int j;
   int xpos, ypos;

   /* draw the list */
   for (j=0; j < radio->viewSize; j++)
   {
      xpos = SCREEN_XPOS(radio, 0);
      ypos = SCREEN_YPOS(radio, j);

      screenPos = SCREENPOS(radio, j + radio->currentTop);

      /* Draw the empty string. */
      writeBlanks (radio->win, xpos, ypos,
		   HORIZONTAL, 0, radio->boxWidth - BorderOf(radio));

      /* Draw the line. */
      writeChtype (radio->win,
		   (screenPos >= 0) ? screenPos : 1,
		   ypos,
		   radio->item[j + radio->currentTop],
		   HORIZONTAL,
		   (screenPos >= 0) ? 0 : (1 - screenPos),
		   radio->itemLen[j + radio->currentTop]);

      /* Draw the selected choice... */
      xpos += scrollbarAdj;
      mvwaddch (radio->win, ypos, xpos++, radio->leftBoxChar);
      mvwaddch (radio->win, ypos, xpos++, ((j + radio->currentTop) == radio->selectedItem) ? radio->choiceChar : ' ');
      mvwaddch (radio->win, ypos, xpos++, radio->rightBoxChar);
   }

   /* Highlight the current item. */
   if (ObjPtr(radio)->hasFocus)
   {
      screenPos = SCREENPOS(radio, radio->currentItem);
      ypos = SCREEN_YPOS(radio, radio->currentHigh);

      writeChtypeAttrib (radio->win,
			(screenPos >= 0) ? screenPos : (1 + scrollbarAdj),
			ypos,
			radio->item[radio->currentItem],
			radio->highlight,
			HORIZONTAL,
			(screenPos >= 0) ? 0 : (1 - screenPos),
			radio->itemLen[radio->currentItem]);
   }

   if (radio->scrollbar)
   {
      radio->togglePos = floorCDK(radio->currentItem * radio->step);
      radio->togglePos = MINIMUM(radio->togglePos, getmaxy(radio->scrollbarWin) - 1);

      mvwvline (radio->scrollbarWin, 0, 0, ACS_CKBOARD, getmaxy(radio->scrollbarWin));
      mvwvline (radio->scrollbarWin, radio->togglePos, 0, ' ' | A_REVERSE, radio->toggleSize);
   }

   /* Box it if needed. */
   if (Box)
   {
      drawObjBox (radio->win, ObjOf(radio));
   }

   /* Refresh the window. */
   refreshCDKWindow (radio->win);
}

/*
 * 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);
      }
   }
}

/*
 * This function destroys the radio widget.
 */
static void _destroyCDKRadio (CDKOBJS *object)
{
   if (object != 0)
   {
      CDKRADIO *radio = (CDKRADIO *)object;

      cleanCdkTitle (object);
      CDKfreeChtypes(radio->item);

      /* Clean up the windows. */
      deleteCursesWindow (radio->scrollbarWin);
      deleteCursesWindow (radio->shadowWin);
      deleteCursesWindow (radio->win);

      /* 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, char **list, int listSize)
{
   int widestItem	= -1;
   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[])
{
   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 selected item of the widget
 */
void setCDKRadioCurrentItem (CDKRADIO *radio, int item)
{
   scroller_SetPosition(radio, item);
}
int getCDKRadioCurrentItem (CDKRADIO *radio)
{
   return radio->currentItem;
}

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);
}

dummyRefreshData(Radio)

dummySaveData(Radio)

static int createList (CDKRADIO *radio, char **list, int listSize, int boxWidth)
{
   int status = 0;
   int widestItem = 0;

   if (listSize > 0)
   {
      chtype **newList = typeCallocN(chtype *, listSize + 1);
      int *newLen = typeCallocN(int, listSize + 1);
      int *newPos = typeCallocN(int, listSize + 1);
      int j;

      if (newList != 0
        && newLen != 0
	&& newPos != 0)
      {
	 /* 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)
	 {
	    CDKfreeChtypes (radio->item);
	    freeChecked (radio->itemLen);
	    freeChecked (radio->itemPos);

	    radio->item = newList;
	    radio->itemLen = newLen;
	    radio->itemPos = newPos;
	 }
	 else
	 {
	    CDKfreeChtypes (newList);
	    freeChecked (newLen);
	    freeChecked (newPos);
	 }
      }
   }
   return status ? widestItem : 0;
}


syntax highlighted by Code2HTML, v. 0.9.1