#include <cdk.h>

/*
 * $Author: garbled $
 * $Date: 2001/01/09 18:41:53 $
 * $Revision: 1.3 $
 */

DeclareCDKObjects(my_funcs,Dialog);

/*
 * This function creates a dialog widget.
 */
CDKDIALOG *newCDKDialog (CDKSCREEN *cdkscreen, int xplace, int yplace, char **mesg, int rows, char **buttonLabel, int buttonCount, chtype buttonHighlight, boolean separator, boolean Box, boolean shadow)
{
   /* Declare local variables. */
   CDKDIALOG *dialog	= newCDKObject(CDKDIALOG, &my_funcs);
   int boxWidth		= MIN_DIALOG_WIDTH;
   int boxHeight	= rows + 2;
   int xpos		= xplace;
   int ypos		= yplace;
   int maxmessagewidth	= 0;
   int buttonWidth, buttonPos;
   int x, junk2;

   /* Translate the char * message to a chtype * */
   for (x=0; x < rows; x++)
   {
      dialog->info[x]	= char2Chtype (mesg[x], &dialog->infoLen[x], &dialog->infoPos[x]);
      maxmessagewidth	= MAXIMUM (maxmessagewidth, dialog->infoLen[x]);
   }

   /* Translate the button label char * to a chtype * */
   buttonWidth = 0;
   for (x=0; x < buttonCount; x++)
   {
      dialog->buttonLabel[x]	= char2Chtype (buttonLabel[x], &dialog->buttonLen[x], &junk2);
      buttonWidth		+= dialog->buttonLen[x] + 1;
   }
   buttonWidth--;

   /* Determine the final dimensions of the box. */
   boxWidth	= MAXIMUM (boxWidth, maxmessagewidth + 2);
   boxWidth	= MAXIMUM (boxWidth, buttonWidth + 2);

   if (buttonCount > 0)
   {
      boxHeight += separator + 1;
   }

   /* Now we have to readjust the x and y positions. */
   alignxy (cdkscreen->window, &xpos, &ypos, boxWidth, boxHeight);

   /* Make the dialog window. */
   dialog->win			= newwin (boxHeight + !!shadow, boxWidth + !!shadow, ypos, xpos);

   /* If we couldn't create the window, we should return a null value. */
   if (dialog->win == 0)
   {
      /* Couldn't create the window. Clean up used memory. */
      for (x=0; x < dialog->messageRows ; x++)
      {
	 freeChtype (dialog->info[x]);
      }
      for (x=0; x < dialog->buttonCount; x++)
      {
	 freeChtype (dialog->buttonLabel[x]);
      }

      /* Remove the memory used by the dialog pointer. */
      free (dialog);

      /* Return a null dialog box. */
      return (0);
   }
   keypad (dialog->win, TRUE);
   leaveok (dialog->win, TRUE);

   /* Make the info window. */
   dialog->infoWin = subwin (dialog->win,
				rows, boxWidth - 2,
				ypos + 1, xpos + 1);

   /* Make the button window. */
   if (buttonCount > 0)
   {
      dialog->buttonWin = subwin (dialog->win,
				1, boxWidth - 2,
				ypos + rows + 2, xpos + 1);
   }

   /* Set up the dialog box attributes. */
   ScreenOf(dialog)		= cdkscreen;
   dialog->parent		= cdkscreen->window;
   dialog->buttonCount		= buttonCount;
   dialog->buttonHighlight	= buttonHighlight;
   dialog->currentButton	= 0;
   dialog->messageRows		= rows;
   dialog->boxHeight		= boxHeight;
   dialog->boxWidth		= boxWidth;
   dialog->separator		= separator;
   dialog->exitType		= vNEVER_ACTIVATED;
   ObjOf(dialog)->box		= Box;
   dialog->shadow		= shadow;
   dialog->ULChar		= ACS_ULCORNER;
   dialog->URChar		= ACS_URCORNER;
   dialog->LLChar		= ACS_LLCORNER;
   dialog->LRChar		= ACS_LRCORNER;
   dialog->HChar		= ACS_HLINE;
   dialog->VChar		= ACS_VLINE;
   dialog->BoxAttrib		= A_NORMAL;
   dialog->preProcessFunction	= 0;
   dialog->preProcessData	= 0;
   dialog->postProcessFunction	= 0;
   dialog->postProcessData	= 0;

   /* Find the button positions. */
   buttonPos = (boxWidth - 2 - buttonWidth) / 2;
   for (x=0; x < buttonCount; x++)
   {
      dialog->buttonPos[x]	= buttonPos;
      buttonPos			+= dialog->buttonLen[x] + 1;
   }

   /* Create the string alignments. */
   for (x=0; x < rows; x++)
   {
      dialog->infoPos[x] = justifyString (boxWidth - 2, dialog->infoLen[x], dialog->infoPos[x]);
   }

   /* Empty the key bindings. */
   cleanCDKObjectBindings (vDIALOG, dialog);

   /* Register this baby. */
   registerCDKObject (cdkscreen, vDIALOG, dialog);

   /* Return the dialog box pointer. */
   return (dialog);
}

/*
 * This lets the user select the button.
 */
int activateCDKDialog (CDKDIALOG *dialog, chtype *actions)
{
   /* Declare local variables. */
   chtype input = 0;
   int ret;

   /* Draw the dialog box. */
   drawCDKDialog (dialog, ObjOf(dialog)->box);

   /* Check if actions is null. */
   if (actions == 0)
   {
      for (;;)
      {
	 /* Get the input. */
	 wrefresh (dialog->win);
	 input = wgetch (dialog->win);

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

      /* Inject each character one at a time. */
      for (x=0; x < length; x++)
      {
	 ret = injectCDKDialog (dialog, actions[x]);
	 if (dialog->exitType != vEARLY_EXIT)
	 {
	    return ret;
	 }
      }
   }

   /* Set the exit type and exit. */
   dialog->exitType = vEARLY_EXIT;
   return -1;
}

/*
 * This injects a single character into the dialog widget.
 */
int injectCDKDialog (CDKDIALOG *dialog, chtype input)
{
   int firstButton	= 0;
   int lastButton	= dialog->buttonCount - 1;
   int ppReturn		= 1;

   /* Set the exit type. */
   dialog->exitType = vEARLY_EXIT;

   /* Check if there is a pre-process function to be called. */
   if (dialog->preProcessFunction != 0)
   {
      ppReturn = ((PROCESSFN)(dialog->preProcessFunction)) (vDIALOG, dialog, dialog->preProcessData, input);
   }

   /* Should we continue? */
   if (ppReturn != 0)
   {
      /* Check for a key binding. */
      if (checkCDKObjectBind (vDIALOG, dialog, input) != 0)
      {
	 return -1;
      }
      else
      {
	 switch (input)
	 {
	    case KEY_LEFT : case CDK_PREV :
		 if (dialog->currentButton == firstButton)
		 {
		    dialog->currentButton = lastButton;;
		 }
		 else
		 {
		    dialog->currentButton--;
		 }
		 break;

	    case KEY_RIGHT : case CDK_NEXT : case KEY_TAB : case ' ' :
		 if (dialog->currentButton == lastButton)
		 {
		    dialog->currentButton = firstButton;
		 }
		 else
		 {
		    dialog->currentButton++;
		 }
		 break;

	    case KEY_UP : case KEY_DOWN :
		 Beep();
		 break;

	    case KEY_ESC :
		 dialog->exitType = vESCAPE_HIT;
		 return -1;

	    case KEY_RETURN : case KEY_ENTER : case KEY_CR :
		 dialog->exitType = vNORMAL;
		 return dialog->currentButton;

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

	    default :
		 Beep();
		 break;
	 }
      }

      /* Should we call a post-process? */
      if (dialog->postProcessFunction != 0)
      {
	 ((PROCESSFN)(dialog->postProcessFunction)) (vDIALOG, dialog, dialog->postProcessData, input);
      }
   }

   /* Redraw the buttons. */
   drawCDKDialogButtons (dialog);

   /* Exit the dialog box. */
   dialog->exitType = vEARLY_EXIT;
   return -1;
}

/*
 * This moves the dialog field to the given location.
 */
static void _moveCDKDialog (CDKOBJS *object, int xplace, int yplace, boolean relative, boolean refresh_flag)
{
   CDKDIALOG *dialog = (CDKDIALOG *)object;

   /*
    * If this is a relative move, then we will adjust where we want
    * to move to.
    */
   if (relative)
   {
      xplace += getbegx(dialog->win);
      yplace += getbegy(dialog->win);
   }

   /* Adjust the window if we need to. */
   alignxy (WindowOf(dialog), &xplace, &yplace, dialog->boxWidth, dialog->boxHeight);

   /* Move the window to the new location. */
   moveCursesWindow(dialog->win, xplace, yplace);

   /* Redraw the window, if they asked for it. */
   if (refresh_flag)
   {
      drawCDKDialog (dialog, ObjOf(dialog)->box);
   }
}

/*
 * This function draws the dialog widget.
 */
static void _drawCDKDialog (CDKOBJS *object, boolean Box)
{
   CDKDIALOG *dialog = (CDKDIALOG *)object;
   int x = 0;

   /* Box the widget if they asked. */
   if (Box)
   {
      attrbox (dialog->win,
		dialog->ULChar, dialog->URChar,
		dialog->LLChar, dialog->LRChar,
		dialog->HChar,	dialog->VChar,
		dialog->BoxAttrib,
		dialog->shadow);
   }

   /* Draw in the message. */
   for (x=0; x < dialog->messageRows; x++)
   {
      writeChtype (dialog->infoWin,
			dialog->infoPos[x], x,
			dialog->info[x],
			HORIZONTAL, 0,
			dialog->infoLen[x]);
   }

   wnoutrefresh (dialog->infoWin);

   /* Draw in the buttons. */
   drawCDKDialogButtons (dialog);
}

/*
 * This function destroys the dialog widget.
 */
void destroyCDKDialog (CDKDIALOG *dialog)
{
   /* Declare local variables. */
   int x;

   /* Erase the object. */
   eraseCDKDialog (dialog);

   /* Clean up the char pointers. */
   for (x=0; x < dialog->messageRows; x++)
   {
      freeChtype (dialog->info[x]);
   }
   for (x=0; x < dialog->buttonCount; x++)
   {
      freeChtype (dialog->buttonLabel[x]);
   }

   /* Clean up the windows. */
   deleteCursesWindow (dialog->win);

   /* Unregister this object. */
   unregisterCDKObject (vDIALOG, dialog);

   /* Finish cleaning up. */
   free (dialog);
}

/*
 * This function erases the dialog widget from the screen.
 */
static void _eraseCDKDialog (CDKOBJS *object)
{
   CDKDIALOG *dialog = (CDKDIALOG *)object;

   eraseCursesWindow (dialog->win);
}

/*
 * This sets attributes of the dialog box.
 */
void setCDKDialog (CDKDIALOG *dialog, chtype buttonHighlight, boolean separator, boolean Box)
{
   setCDKDialogHighlight (dialog, buttonHighlight);
   setCDKDialogSeparator (dialog, separator);
   setCDKDialogBox (dialog, Box);
}

/*
 * This sets the highlight attribute for the buttons.
 */
void setCDKDialogHighlight (CDKDIALOG *dialog, chtype buttonHighlight)
{
   dialog->buttonHighlight = buttonHighlight;
}
chtype getCDKDialogHighlight (CDKDIALOG *dialog)
{
   return dialog->buttonHighlight;
}

/*
 * This sets whether or not the dialog box will have a separator line.
 */
void setCDKDialogSeparator (CDKDIALOG *dialog, boolean separator)
{
   dialog->separator = separator;
}
boolean getCDKDialogSeparator (CDKDIALOG *dialog)
{
   return dialog->separator;
}

/*
 * This sets the box attribute of the widget.
 */
void setCDKDialogBox (CDKDIALOG *dialog, boolean Box)
{
   ObjOf(dialog)->box = Box;
}
boolean getCDKDialogBox (CDKDIALOG *dialog)
{
   return ObjOf(dialog)->box;
}

/*
 * These functions set the drawing characters of the widget.
 */
void setCDKDialogULChar (CDKDIALOG *dialog, chtype character)
{
   dialog->ULChar = character;
}
void setCDKDialogURChar (CDKDIALOG *dialog, chtype character)
{
   dialog->URChar = character;
}
void setCDKDialogLLChar (CDKDIALOG *dialog, chtype character)
{
   dialog->LLChar = character;
}
void setCDKDialogLRChar (CDKDIALOG *dialog, chtype character)
{
   dialog->LRChar = character;
}
void setCDKDialogVerticalChar (CDKDIALOG *dialog, chtype character)
{
   dialog->VChar = character;
}
void setCDKDialogHorizontalChar (CDKDIALOG *dialog, chtype character)
{
   dialog->HChar = character;
}
void setCDKDialogBoxAttribute (CDKDIALOG *dialog, chtype character)
{
   dialog->BoxAttrib = character;
}

/*
 * This sets the background color of the widget.
 */
void setCDKDialogBackgroundColor (CDKDIALOG *dialog, char *color)
{
   chtype *holder = 0;
   int junk1, junk2;

   /* Make sure the color isn't null. */
   if (color == 0)
   {
      return;
   }

   /* Convert the value of the environment variable to a chtype. */
   holder = char2Chtype (color, &junk1, &junk2);

   /* Set the widgets background color. */
   wbkgd (dialog->win, holder[0]);

   /* Clean up. */
   freeChtype (holder);
}

/*
 * This draws the dialog buttons and the separation line.
 */
void drawCDKDialogButtons (CDKDIALOG *dialog)
{
   /* Declare local variables. */
   int x;

   if (dialog->buttonCount > 0)
   {
      for (x=0; x < dialog->buttonCount; x++)
      {
	 if (x == dialog->currentButton)
	 {
	    writeChtypeAttrib (dialog->buttonWin,
			dialog->buttonPos[x], 0,
			dialog->buttonLabel[x],
			dialog->buttonHighlight,
			HORIZONTAL, 0,
			dialog->buttonLen[x]);
	 }
	 else
	 {
	    writeChtype (dialog->buttonWin,
			dialog->buttonPos[x], 0,
			dialog->buttonLabel[x],
			HORIZONTAL, 0,
			dialog->buttonLen[x]);
	 }
      }

      /* Draw the separation line. */
      if (dialog->separator)
      {
	 mvwaddch (dialog->win, dialog->boxHeight-3, 0, ACS_LTEE | dialog->BoxAttrib);
	 mvwhline (dialog->win, dialog->boxHeight-3, 1, ACS_HLINE | dialog->BoxAttrib, dialog->boxWidth-2);
	 mvwaddch (dialog->win, dialog->boxHeight-3, dialog->boxWidth-1, ACS_RTEE | dialog->BoxAttrib);
      }

      wnoutrefresh (dialog->buttonWin);
   }

   wnoutrefresh (dialog->win);
}

/*
 * This function sets the pre-process function.
 */
void setCDKDialogPreProcess (CDKDIALOG *dialog, PROCESSFN callback, void *data)
{
   dialog->preProcessFunction = callback;
   dialog->preProcessData = data;
}

/*
 * This function sets the post-process function.
 */
void setCDKDialogPostProcess (CDKDIALOG *dialog, PROCESSFN callback, void *data)
{
   dialog->postProcessFunction = callback;
   dialog->postProcessData = data;
}
