/* $Id: rolodex.c,v 1.31 2025/01/09 00:20:21 tom Exp $ */
#include "rolodex.h"

#ifdef HAVE_XCURSES
char *XCursesProgramName = "rolodex";
#endif

#define MYSIZE 256
static void fmt1s (char *target, const char *format, const char *source)
{
   int limit = MYSIZE - (int)(strlen (format) + strlen (source));
   if (limit > 0)
      sprintf (target, format, limit, source);
   else
      *target = '\0';
}

/*
 * This is the main part of the loop.
 */
int main (void)
{
   /* Declare variables. */
   CDKSCREEN *cdkscreen;
   CDKMENU *rolodexMenu;
   CDKLABEL *rolodexTitle;
   SRolodex groupList[MAXGROUPS];
   const char *menulist[MAX_MENU_ITEMS][MAX_SUB_ITEMS];
   const char *title[5];
   char *home, temp[MYSIZE];
   const char *mesg[15];
   int subMenuSize[10], menuLocations[10];
   int group, ret, x;
   int groupCount = 0;

   cdkscreen = initCDKScreen (NULL);

   /* Start CDK color. */
   initCDKColor ();

   /* Create the menu lists. */
   menulist[0][0] = "</U>File";
   menulist[0][1] = "</B/5>Open   ";
   menulist[0][2] = "</B/5>Save   ";
   menulist[0][3] = "</B/5>Save As";
   menulist[0][4] = "</B/5>Quit   ";

   menulist[1][0] = "</U>Groups";
   menulist[1][1] = "</B/5>New   ";
   menulist[1][2] = "</B/5>Open  ";
   menulist[1][3] = "</B/5>Delete";

   menulist[2][0] = "</U>Print";
   menulist[2][1] = "</B/5>Print Groups";

   menulist[3][0] = "</U>Help";
   menulist[3][1] = "</B/5>About Rolodex     ";
   menulist[3][2] = "</B/5>Rolodex Statistics";

   /* Set up the sub-menu sizes and their locations. */
   subMenuSize[0] = 5;
   menuLocations[0] = LEFT;
   subMenuSize[1] = 4;
   menuLocations[1] = LEFT;
   subMenuSize[2] = 2;
   menuLocations[2] = LEFT;
   subMenuSize[3] = 3;
   menuLocations[3] = RIGHT;

   /* Create the menu. */
   rolodexMenu = newCDKMenu (cdkscreen, menulist, 4,
			     subMenuSize, menuLocations,
			     TOP, A_BOLD | A_UNDERLINE, A_REVERSE);

   /* Create the title. */
   title[0] = "<C></U>Cdk Rolodex";
   title[1] = "<C></B/24>Written by Mike Glover";
   rolodexTitle = newCDKLabel (cdkscreen, CENTER, CENTER,
			       (CDK_CSTRING2)title, 2,
			       FALSE, FALSE);

   /* Define the help key binding. */
   bindCDKObject (vMENU, rolodexMenu, '?', helpCB, NULL);

   /* Draw the CDK screen. */
   refreshCDKScreen (cdkscreen);

   /* Check the value of the HOME env var. */
   home = getenv ("HOME");
   if (home != NULL)
   {
      /* Make sure the $HOME/.rolodex directory exists. */
      fmt1s (temp, "%.*s/.rolodex", home);

      /* Set the value of the global rolodex DBM directory. */
      GDBMDir = copyChar (temp);

      /* Set the value of the global RC filename. */
      fmt1s (temp, "%.*s/.rolorc", home);
      GRCFile = copyChar (temp);
   }
   else
   {
      /* Set the value of the global rolodex DBM directory. */
      GDBMDir = copyChar (".rolodex");

      /* Set the value of the global RC filename. */
      GRCFile = copyChar (".rolorc");
   }

   /* Make the rolodex directory. */
#if defined (__MINGW32__)
   mkdir (GDBMDir);
#else
   mkdir (GDBMDir, 0755);
#endif

   /* Open the rolodex RC file. */
   groupCount = readRCFile (GRCFile, groupList);

   /* Check the value of groupCount. */
   if (groupCount < 0)
   {
      /* The RC file seems to be corrupt. */
      fmt1s (temp, "<C></B/16>The RC file (%.*s) seems to be corrupt.", GRCFile);
      mesg[0] = temp;
      mesg[1] = "<C></B/16>No rolodex groups were loaded.";
      mesg[2] = "<C>Press any key to continue.";
      popupLabel (cdkscreen, (CDK_CSTRING2)mesg, 3);
      groupCount = 0;
   }
   else if (groupCount == 0)
   {
      mesg[0] = "<C></B/24>Empty rolodex RC file. No groups loaded.";
      mesg[1] = "<C>Press any key to continue.";
      popupLabel (cdkscreen, (CDK_CSTRING2)mesg, 2);
   }
   else
   {
      if (groupCount == 1)
      {
	 sprintf (temp, "<C></24>There was 1 group loaded from the RC file.");
      }
      else
      {
	 sprintf (temp,
		  "<C></24>There were %d groups loaded from the RC file.", groupCount);
      }
      mesg[0] = temp;
      mesg[1] = "<C>Press any key to continue.";
      popupLabel (cdkscreen, (CDK_CSTRING2)mesg, 2);
   }

   /* Loop until we are done. */
   for (;;)
   {
      /* Activate the menu */
      int selection = activateCDKMenu (rolodexMenu, NULL);

      /* Check the return value of the selection. */
      if (selection == 0)
      {
	 /* Open the rolodex RC file. */
	 groupCount = openNewRCFile (cdkscreen, groupList, groupCount);
      }
      else if (selection == 1)
      {
	 /* Write out the RC file. */
	 ret = writeRCFile (cdkscreen, GRCFile, groupList, groupCount);

	 /* Reset the saved flag if the rc file saved ok. */
	 if (ret != 0)
	 {
	    GGroupModified = 0;
	 }
      }
      else if (selection == 2)
      {
	 /* Save as. */
	 ret = writeRCFileAs (cdkscreen, groupList, groupCount);

	 /* Reset the saved flag if the rc file saved ok. */
	 if (ret != 0)
	 {
	    GGroupModified = 0;
	 }
      }
      else if (selection == 3)
      {
	 /* Has anything changed??? */
	 if (GGroupModified != 0)
	 {
	    /* Write out the RC file. */
	    writeRCFile (cdkscreen, GRCFile, groupList, groupCount);
	 }

	 /* Remove the CDK widget pointers. */
	 destroyCDKMenu (rolodexMenu);
	 destroyCDKLabel (rolodexTitle);
	 destroyCDKScreen (cdkscreen);

	 /* Free up other pointers. */
	 freeChar (GCurrentGroup);
	 freeChar (GRCFile);
	 freeChar (GDBMDir);

	 /* Clean up the group information. */
	 for (x = 0; x < groupCount; x++)
	 {
	    freeChar (groupList[x].name);
	    freeChar (groupList[x].desc);
	    freeChar (groupList[x].dbm);
	 }

	 /* Shutdown CDK. */
	 endCDK ();

	 /* Exit cleanly. */
	 ExitProgram (EXIT_SUCCESS);
      }
      else if (selection == 100)
      {
	 /* Add a new group to the list. */
	 groupCount = addRolodexGroup (cdkscreen, groupList, groupCount);
      }
      else if (selection == 101)
      {
	 /* If there are no groups, ask them if they want to create a new one. */
	 if (groupCount == 0)
	 {
	    const char *buttons[] =
	    {
	       "<Yes>",
	       "<No>"
	    };
	    mesg[0] = "<C>There are no groups defined.";
	    mesg[1] = "<C>Do you want to define a new group?";

	    /* Add the group if they said yes. */
	    if (popupDialog (cdkscreen,
			     (CDK_CSTRING2)mesg, 2,
			     (CDK_CSTRING2)buttons, 2) == 0)
	    {
	       groupCount = addRolodexGroup (cdkscreen, groupList, groupCount);
	    }
	 }
	 else
	 {
	    /* Get the number of the group to open. */
	    group = pickRolodexGroup (cdkscreen,
				      "<C></B/29>Open Rolodex Group",
				      groupList, groupCount);

	    /* Make sure a group was picked. */
	    if (group >= 0)
	    {
	       /* Set the global variable GCurrentGroup */
	       freeChar (GCurrentGroup);
	       GCurrentGroup = copyChar (groupList[group].name);

	       /* Try to open the DBM file and read the contents. */
	       useRolodexGroup (cdkscreen,
				groupList[group].name,
				groupList[group].desc,
				groupList[group].dbm);
	    }
	 }
      }
      else if (selection == 102)
      {
	 /* Delete the group chosen. */
	 groupCount = deleteRolodexGroup (cdkscreen, groupList, groupCount);
      }
      else if (selection == 200)
      {
	 /* Print Phone Number Group. */
	 printGroupNumbers (cdkscreen, groupList, groupCount);
      }
      else if (selection == 300)
      {
	 /* About Rolodex. */
	 aboutCdkRolodex (cdkscreen);
      }
      else if (selection == 301)
      {
	 displayRolodexStats (ScreenOf (rolodexMenu), groupCount);
      }
   }
}

/*
 * This writes out the new RC file.
 */
int writeRCFile (CDKSCREEN *screen, char *filename, SRolodex * groupList, int groupCount)
{
   char *mesg[5];
   char temp[MYSIZE];
   time_t clck;
   FILE *fd;
   int x;

   /* Can we open the file?                                     */
   if ((fd = fopen (filename, "w")) == NULL)
   {
      fmt1s (temp, "</B/16>The file <%.*s> could not be opened.", filename);
      mesg[0] = copyChar (temp);
#ifdef HAVE_STRERROR
      fmt1s (temp, "<C></B/16>%.*s", strerror (errno));
#else
      sprintf (temp, "<C></B/16>Unknown reason.");
#endif
      mesg[1] = copyChar (temp);
      mesg[2] = copyChar ("<C>Press any key to continue.");

      popupLabel (screen, (CDK_CSTRING2)mesg, 3);

      freeCharList (mesg, 3);

      return (0);
   }

   /* Get the current time. */
   time (&clck);

   /* Put some comments at the top of the header. */
   fprintf (fd, "#\n");
   fprintf (fd, "# This file was automatically generated on %s", ctime (&clck));
   fprintf (fd, "#\n");

   /* Start writing the RC file. */
   for (x = 0; x < groupCount; x++)
   {
      fprintf (fd, "%s%c%s%c%s\n",
	       groupList[x].name, CTRL ('V'),
	       groupList[x].desc, CTRL ('V'),
	       groupList[x].dbm);
   }
   fclose (fd);

   /* Pop up a message stating that it has been saved. */
   if (groupCount == 1)
   {
      sprintf (temp, "There was 1 group saved to file");
      mesg[0] = copyChar (temp);
   }
   else
   {
      sprintf (temp, "There were %d groups saved to file", groupCount);
      mesg[0] = copyChar (temp);
   }

   fmt1s (temp, "<C>%.*s", filename);
   mesg[1] = copyChar (temp);
   mesg[2] = copyChar ("<C>Press any key to continue.");

   popupLabel (screen, (CDK_CSTRING2)mesg, 3);

   freeCharList (mesg, 3);
   return (1);
}

/*
 * This allows the user to pick a DBM file to open.
 */
int pickRolodexGroup (CDKSCREEN *screen,
		      const char *title,
		      SRolodex * groupList,
		      int groupCount)
{
   /* *INDENT-EQLS* */
   CDKSCROLL *roloList  = NULL;
   int height           = groupCount;
   char *mesg[MAXGROUPS];
   char temp[MYSIZE];
   int selection, x;

   /* Determine the height of the scrolling list. */
   if (groupCount > 5)
   {
      height = 5;
   }
   height += 3;

   /* Copy the names of the scrolling list into an array. */
   for (x = 0; x < groupCount; x++)
   {
      fmt1s (temp, "<C></B/29>%.*s", groupList[x].name);
      mesg[x] = copyChar (temp);
   }

   /* Create the scrolling list. */
   roloList = newCDKScroll (screen, CENTER, CENTER, NONE,
			    height, 50, title,
			    (CDK_CSTRING2)mesg, groupCount,
			    NONUMBERS, A_REVERSE, TRUE, FALSE);

   /* Create a callback to the scrolling list. */
   bindCDKObject (vSCROLL, roloList, '?', groupInfoCB, groupList);

   /* Activate the scrolling list. */
   selection = activateCDKScroll (roloList, NULL);

   /* Destroy the scrolling list. */
   destroyCDKScroll (roloList);
   freeCharList (mesg, (unsigned)groupCount);

   /* Return the item selected. */
   return selection;
}

/*
 * This allows the user to add a rolo group to the list.
 */
int addRolodexGroup (CDKSCREEN *screen, SRolodex * groupList, int groupCount)
{
   /* *INDENT-EQLS* */
   CDKENTRY *newName = NULL;
   CDKENTRY *newDesc = NULL;
   const char *mesg[4];
   char *desc;
   char *newGroupName;
   char temp[MYSIZE];
   int x;

   /* Create the name widget. */
   newName = newCDKEntry (screen, CENTER, 8,
			  "<C></B/29>New Group Name",
			  "</B/29>   Name: ",
			  A_NORMAL, '_', vMIXED,
			  20, 2, MYSIZE, TRUE, FALSE);


   /* Get the name. */
   newGroupName = activateCDKEntry (newName, NULL);

   /* Make sure they didn't hit escape. */
   if (newName->exitType == vESCAPE_HIT)
   {
      mesg[0] = "<C></B/16>Add Group Canceled.";
      destroyCDKEntry (newName);
      popupLabel (screen, (CDK_CSTRING2)mesg, 1);
      return groupCount;
   }

   /* Make sure that group name does not already exist. */
   for (x = 0; x < groupCount; x++)
   {
      if (strcmp (newGroupName, groupList[x].name) == 0)
      {
	 fmt1s (temp, "<C></B/16>Sorry the group (%.*s) already exists.", newGroupName);
	 mesg[0] = temp;
	 popupLabel (screen, (CDK_CSTRING2)mesg, 1);
	 destroyCDKEntry (newName);
	 return groupCount;
      }
   }

   /* Keep the name. */
   groupList[groupCount].name = copyChar (newGroupName);

   /* Create the description widget. */
   newDesc = newCDKEntry (screen, CENTER, 13,
			  "<C></B/29>Group Description",
			  "</B/29>Descriprion: ",
			  A_NORMAL, '_', vMIXED,
			  20, 2, MYSIZE, TRUE, FALSE);

   /* Get the description. */
   desc = activateCDKEntry (newDesc, NULL);

   /* Check if they hit escape or not. */
   if (newDesc->exitType == vESCAPE_HIT)
   {
      groupList[groupCount].desc = copyChar ("No Description Provided.");
   }
   else
   {
      groupList[groupCount].desc = copyChar (desc);
   }

   /* Create the DBM filename. */
   sprintf (temp, "%s/%s.phl", GDBMDir, groupList[groupCount].name);
   groupList[groupCount].dbm = copyChar (temp);

   /* Inrement the group count. */
   groupCount++;
   GGroupModified = 1;

   /* Destroy the widgets. */
   destroyCDKEntry (newName);
   destroyCDKEntry (newDesc);
   return groupCount;
}

/*
 * This displays rolodex information.
 */
void displayRolodexStats (CDKSCREEN *screen, int groupCount)
{
   /* Declare local variables. */
   char *mesg[6], temp[MYSIZE];

   /* Create the information to display. */
   sprintf (temp, "<C></U>Rolodex Statistics");
   mesg[0] = copyChar (temp);
   fmt1s (temp, "</B/5>Read Command Filename<!B!5> </U>%.*s<!U>", GRCFile);
   mesg[1] = copyChar (temp);
   sprintf (temp, "</B/5>Group Count          <!B!5> </U>%d<!U>", groupCount);
   mesg[2] = copyChar (temp);

   /* Display the message. */
   popupLabel (screen, (CDK_CSTRING2)mesg, 3);

   freeCharList (mesg, 3);
}

/*
 * This function gets a new rc filename and saves the contents of the
 * groups under that name.
 */
int writeRCFileAs (CDKSCREEN *screen, SRolodex * groupList, int groupCount)
{
   /* Declare local variables. */
   CDKENTRY *newRCFile;
   char *newFilename;
   int ret;

   /* Create the entry field. */
   newRCFile = newCDKEntry (screen, CENTER, CENTER,
			    "<C></R>Save As",
			    "Filename: ",
			    A_NORMAL, '_', vMIXED,
			    20, 2, MYSIZE, TRUE, FALSE);

   /* Add a pre-process function so no spaces are introduced. */
   setCDKEntryPreProcess (newRCFile, entryPreProcessCB, NULL);

   /* Get the filename. */
   newFilename = activateCDKEntry (newRCFile, NULL);

   /* Check if they hit escape or not. */
   if (newRCFile->exitType == vESCAPE_HIT)
   {
      destroyCDKEntry (newRCFile);
      return 1;
   }

   /* Call the function to save the RC file. */
   ret = writeRCFile (screen, newFilename, groupList, groupCount);

   /* Reset the saved flag if the rc file saved ok. */
   if (ret != 0)
   {
      /* Change the default filename. */
      freeChar (GRCFile);
      GRCFile = copyChar (newFilename);
      GGroupModified = 0;
   }

   /* Clean up. */
   destroyCDKEntry (newRCFile);
   return 1;
}

/*
 * This opens a new RC file.
 */
int openNewRCFile (CDKSCREEN *screen, SRolodex * groupList, int groupCount)
{
   /* Declare local variables. */
   CDKFSELECT *fileSelector;
   char *filename, *mesg[10];
   int x;

   /* Get the filename. */
   fileSelector = newCDKFselect (screen, CENTER, CENTER, 20, 55,
				 "<C>Open RC File",
				 "Filename: ",
				 A_NORMAL, '.', A_REVERSE,
				 "</5>", "</48>", "</N>", "</N>",
				 TRUE, FALSE);

   /* Activate the file selector. */
   filename = activateCDKFselect (fileSelector, NULL);

   /* Check if the file selector left early. */
   if (fileSelector->exitType == vESCAPE_HIT)
   {
      destroyCDKFselect (fileSelector);
      mesg[0] = copyChar ("Open New RC File Aborted.");
      popupLabel (screen, (CDK_CSTRING2)mesg, 1);
      freeCharList (mesg, 1);
      return groupCount;
   }

   /* Clean out the old information. */
   for (x = 0; x < groupCount; x++)
   {
      freeChar (groupList[x].name);
      freeChar (groupList[x].desc);
      freeChar (groupList[x].dbm);
   }

   /* Open the RC file. */
   groupCount = readRCFile (filename, groupList);

   /* Check the return value. */
   if (groupCount < 0)
   {
      char temp[MYSIZE];

      /* This file does not appear to be a rolodex file. */
      mesg[0] = copyChar ("<C></B/16>The file<!B!16>");
      fmt1s (temp, "<C></B/16>(%.*s)<!B!16>", filename);
      mesg[1] = copyChar (temp);
      mesg[2] = copyChar ("<C>does not seem to be a rolodex RC file.");
      mesg[3] = copyChar ("<C>Press any key to continue.");
      popupLabel (screen, (CDK_CSTRING2)mesg, 4);
      freeCharList (mesg, 4);
      groupCount = 0;
   }

   /* Clean up. */
   destroyCDKFselect (fileSelector);
   return groupCount;
}

/*
 * This reads the users rc file.
 */
int readRCFile (char *filename, SRolodex * groupList)
{
   /* *INDENT-EQLS* */
   int groupsFound = 0;
   int errorsFound = 0;
   char **lines    = NULL;
   char **items;
   int linesRead, chunks, x;

   /* Open the file and start reading. */
   linesRead = CDKreadFile (filename, &lines);

   /* Check the number of lines read. */
   if (linesRead == 0)
   {
      return (0);
   }

   /*
    * Cycle through what was given to us and save it.
    */
   for (x = 0; x < linesRead; x++)
   {
      /* Strip white space from the line. */
      stripWhiteSpace (vBOTH, lines[x]);

      /* Only split lines which do not start with a # */
      if (strlen (lines[x]) != 0 && lines[x][0] != '#')
      {
	 items = CDKsplitString (lines[x], CTRL ('V'));
	 chunks = (int)CDKcountStrings ((CDK_CSTRING2)items);

	 /* Only take the ones which fit the format. */
	 if (chunks == 3)
	 {
	    /* Clean off the name and DB name. */
	    stripWhiteSpace (vBOTH, items[0]);
	    stripWhiteSpace (vBOTH, items[1]);
	    stripWhiteSpace (vBOTH, items[2]);

	    /* Set the group name and DB name. */
	    groupList[groupsFound].name = items[0];
	    groupList[groupsFound].desc = items[1];
	    groupList[groupsFound].dbm = items[2];
	    groupsFound++;
	    free (items);
	 }
	 else
	 {
	    CDKfreeStrings (items);
	    errorsFound++;
	 }
      }
   }

   /* Clean up. */
   CDKfreeStrings (lines);

   /* Check the number of groups to the number of errors. */
   if (errorsFound > 0 && groupsFound == 0)
   {
      /* This does NOT look like the rolodex RC file. */
      return -1;
   }
   return groupsFound;
}

/*
 * This function allows the user to add/delete/modify/save the
 * contents of a rolodex group.
 */
void useRolodexGroup (CDKSCREEN *screen, char *groupName, char *groupDesc
		      GCC_UNUSED, char *groupDBM)
{
   /* *INDENT-EQLS* */
   CDKSCROLL *nameList  = NULL;
   CDKLABEL *helpWindow = NULL;
   SPhoneData phoneData;
   char *Index[MAX_ITEMS];
   const char *title[3];
   const char *mesg[3];
   char temp[MYSIZE];
   int phoneCount, selection, height, x;

   /* Set up the help window at the bottom of the screen. */
   /* *INDENT-EQLS* */
   title[0]     = "<C><#HL(30)>";
   title[1]     = "<C>Press </B>?<!B> to get detailed help.";
   title[2]     = "<C><#HL(30)>";
   helpWindow   = newCDKLabel (screen, CENTER, BOTTOM,
			       (CDK_CSTRING2)title, 3,
			       FALSE, FALSE);
   drawCDKLabel (helpWindow, FALSE);

   /* Open the DBM file and read in the contents of the file */
   phoneCount = readPhoneDataFile (groupDBM, &phoneData);
   phoneData.recordCount = phoneCount;

   /* Check the number of entries returned. */
   if (phoneCount == 0)
   {
      /*
       * They tried to open an empty group, maybe they want to
       * add a new entry to this number.
       */
      const char *buttons[] =
      {
	 "<Yes>",
	 "<No>"
      };
      mesg[0] = "<C>There were no entries in this group.";
      mesg[1] = "<C>Do you want to add a new listng?";
      if (popupDialog (screen,
		       (CDK_CSTRING2)mesg, 2,
		       (CDK_CSTRING2)buttons, 2) == 1)
      {
	 destroyCDKLabel (helpWindow);
	 return;
      }

      /* Get the information for a new number. */
      if (addPhoneRecord (screen, &phoneData) != 0)
      {
	 return;
      }
   }
   else if (phoneCount < 0)
   {
      mesg[0] = "<C>Could not open the database for this group.";
      popupLabel (screen, (CDK_CSTRING2)mesg, 1);
      destroyCDKLabel (helpWindow);
      return;
   }

   /* Set up the data needed for the scrolling list. */
   for (x = 0; x < phoneData.recordCount; x++)
   {
      SPhoneRecord *phoneRecord = &phoneData.record[x];
      sprintf (temp, "</B/29>%s (%s)", phoneRecord->name, GLineType[phoneRecord->lineType]);
      Index[x] = copyChar (temp);
   }
   fmt1s (temp, "<C>Listing of Group </U>%.*s", groupName);
   height = (phoneData.recordCount > 5 ? 8 : phoneData.recordCount + 3);

   /* Create the scrolling list. */
   nameList = newCDKScroll (screen, CENTER, CENTER, RIGHT,
			    height, 50, temp,
			    (CDK_CSTRING2)Index,
			    phoneData.recordCount,
			    NUMBERS, A_REVERSE, TRUE, FALSE);

   /* Clean up. */
   for (x = 0; x < phoneData.recordCount; x++)
   {
      freeChar (Index[x]);
   }

   /* Create key bindings. */
   bindCDKObject (vSCROLL, nameList, 'i', insertPhoneEntryCB, &phoneData);
   bindCDKObject (vSCROLL, nameList, 'd', deletePhoneEntryCB, &phoneData);
   bindCDKObject (vSCROLL, nameList, KEY_DC, deletePhoneEntryCB, &phoneData);
   bindCDKObject (vSCROLL, nameList, '?', phoneEntryHelpCB, NULL);

   /* Let them play. */
   selection = 0;
   while (selection >= 0)
   {
      /* Get the information they want to view. */
      selection = activateCDKScroll (nameList, NULL);

      /* Display the information. */
      if (selection >= 0)
      {
	 /* Display the information. */
	 displayPhoneInfo (screen, phoneData.record[selection]);
      }
   }

   /* Save the rolodex information to file. */
   if (savePhoneDataFile (groupDBM, &phoneData) == 0)
   {
      /* Something happened. */
      mesg[0] = "<C>Could not save phone data to data file.";
      mesg[1] = "<C>All changes have been lost.";
      popupLabel (screen, (CDK_CSTRING2)mesg, 2);
   }

   /* Clean up. */
   for (x = 0; x < phoneData.recordCount; x++)
   {
      freeChar (phoneData.record[x].name);
      freeChar (phoneData.record[x].phoneNumber);
      freeChar (phoneData.record[x].address);
      freeChar (phoneData.record[x].city);
      freeChar (phoneData.record[x].province);
      freeChar (phoneData.record[x].postalCode);
      freeChar (phoneData.record[x].desc);
   }
   destroyCDKScroll (nameList);
   destroyCDKLabel (helpWindow);
}

/*
 * This opens a phone data file and returns the number of elements read.
 */
int readPhoneDataFile (char *dataFile, SPhoneData * phoneData)
{
   /* *INDENT-EQLS* */
   char **lines   = NULL;
   char **items;
   int linesRead  = 0;
   int chunks     = 0;
   int linesFound = 0;
   int x;

   /* Open the file and start reading. */
   linesRead = CDKreadFile (dataFile, &lines);

   /* Check the number of lines read. */
   if (linesRead <= 0)
   {
      return (0);
   }

   /*
    * Cycle through what was given to us and save it.
    */
   for (x = 0; x < linesRead; x++)
   {
      if (lines[x][0] != '#')
      {
	 /* Split the string. */
	 items = CDKsplitString (lines[x], CTRL ('V'));
	 chunks = (int)CDKcountStrings ((CDK_CSTRING2)items);

	 /* Copy the chunks. */
	 if (chunks == 8)
	 {
	    int myType = atoi (items[1]);
	    /* *INDENT-EQLS* */
	    phoneData->record[linesFound].name        = items[0];
	    phoneData->record[linesFound].lineType    = (ELineType) myType;
	    phoneData->record[linesFound].phoneNumber = items[2];
	    phoneData->record[linesFound].address     = items[3];
	    phoneData->record[linesFound].city        = items[4];
	    phoneData->record[linesFound].province    = items[5];
	    phoneData->record[linesFound].postalCode  = items[6];
	    phoneData->record[linesFound].desc        = items[7];
	    freeChar (items[1]);
	    free (items);
	    linesFound++;
	 }
	 else
	 {
	    /* Bad line in the file; recover the memory. */
	    CDKfreeStrings (items);
	 }
      }
   }

   /* Clean up. */
   CDKfreeStrings (lines);

   /* Keep the record count and return. */
   phoneData->recordCount = linesFound;
   return linesFound;
}

/*
 * This opens a phone data file and returns the number of elements read.
 */
int savePhoneDataFile (char *filename, SPhoneData * phoneData)
{
   time_t clck;
   FILE *fd;
   int x;

   /* Can we open the file? */
   if ((fd = fopen (filename, "w")) == NULL)
   {
      return 0;
   }

   /* Get the current time. */
   time (&clck);

   /* Add the header to the file. */
   fprintf (fd, "#\n");
   fprintf (fd, "# This file was automatically saved on %s", ctime (&clck));
   fprintf (fd, "# There should be %d phone numbers in this file.\n", phoneData->recordCount);
   fprintf (fd, "#\n");

   /* Cycle through the data and start writing it to the file. */
   for (x = 0; x < phoneData->recordCount; x++)
   {
      SPhoneRecord *phoneRecord = &phoneData->record[x];

      /* Check the phone type. */
      if (phoneRecord->lineType == vCell || phoneRecord->lineType == vPager)
      {
	 fprintf (fd, "%s%c%d%c%s%c-%c-%c-%c-%c%s\n",
		  phoneRecord->name, CTRL ('V'),
		  phoneRecord->lineType, CTRL ('V'),
		  phoneRecord->phoneNumber, CTRL ('V'),
		  CTRL ('V'), CTRL ('V'), CTRL ('V'), CTRL ('V'),
		  phoneRecord->desc);
      }
      else
      {
	 fprintf (fd, "%s%c%d%c%s%c%s%c%s%c%s%c%s%c%s\n",
		  phoneRecord->name, CTRL ('V'),
		  phoneRecord->lineType, CTRL ('V'),
		  phoneRecord->phoneNumber, CTRL ('V'),
		  phoneRecord->address, CTRL ('V'),
		  phoneRecord->city, CTRL ('V'),
		  phoneRecord->province, CTRL ('V'),
		  phoneRecord->postalCode, CTRL ('V'),
		  phoneRecord->desc);
      }
   }
   fclose (fd);
   return 1;
}

/*
 * This displays the information about the phone record.
 */
void displayPhoneInfo (CDKSCREEN *screen, SPhoneRecord record)
{
   /* Declare local variables. */
   char *mesg[10], temp[MYSIZE];

   /* Check the type of line it is. */
   if (record.lineType == vVoice ||
       record.lineType == vData1 ||
       record.lineType == vData2 ||
       record.lineType == vData3 ||
       record.lineType == vFAX1 ||
       record.lineType == vFAX2 ||
       record.lineType == vFAX3)
   {
      /* Create the information to display. */
      fmt1s (temp, "<C></U>%.*s Phone Record", GLineType[record.lineType]);
      mesg[0] = copyChar (temp);

      fmt1s (temp, "</B/29>Name        <!B!29>%.*s", record.name);
      mesg[1] = copyChar (temp);

      fmt1s (temp, "</B/29>Phone Number<!B!29>%.*s", record.phoneNumber);
      mesg[2] = copyChar (temp);

      fmt1s (temp, "</B/29>Address     <!B!29>%.*s", record.address);
      mesg[3] = copyChar (temp);

      fmt1s (temp, "</B/29>City        <!B!29>%.*s", record.city);
      mesg[4] = copyChar (temp);

      fmt1s (temp, "</B/29>Province    <!B!29>%.*s", record.province);
      mesg[5] = copyChar (temp);

      fmt1s (temp, "</B/29>Postal Code <!B!29>%.*s", record.postalCode);
      mesg[6] = copyChar (temp);

      fmt1s (temp, "</B/29>Comment     <!B!29>%.*s", record.desc);
      mesg[7] = copyChar (temp);

      /* Pop the information up on the screen. */
      popupLabel (screen, (CDK_CSTRING2)mesg, 8);

      /* Clean up. */
      freeChar (mesg[0]);
      freeChar (mesg[1]);
      freeChar (mesg[2]);
      freeChar (mesg[3]);
      freeChar (mesg[4]);
      freeChar (mesg[5]);
      freeChar (mesg[6]);
      freeChar (mesg[7]);
   }
   else if (record.lineType == vPager || record.lineType == vCell)
   {
      /* Create the information to display. */
      fmt1s (temp, "<C></U>%.*s Phone Record", GLineType[record.lineType]);
      mesg[0] = copyChar (temp);

      fmt1s (temp, "</B/29>Name        <!B!29>%.*s", record.name);
      mesg[1] = copyChar (temp);

      fmt1s (temp, "</B/29>Phone Number<!B!29>%.*s", record.phoneNumber);
      mesg[2] = copyChar (temp);

      fmt1s (temp, "</B/29>Comment     <!B!29>%.*s", record.desc);
      mesg[3] = copyChar (temp);

      /* Pop the information up on the screen. */
      popupLabel (screen, (CDK_CSTRING2)mesg, 4);

      freeCharList (mesg, 4);
   }
   else
   {
      mesg[0] = copyChar ("<C></R>Error<!R> </U>Unknown Phone Line Type");
      mesg[1] = copyChar ("<C>Can not display information.");
      popupLabel (screen, (CDK_CSTRING2)mesg, 2);
      freeCharList (mesg, 2);
   }
}

/*
 * This function displays a little pop up window discussing this demo.
 */
void aboutCdkRolodex (CDKSCREEN *screen)
{
   const char *mesg[15];

   mesg[0] = "<C></U>About Cdk Rolodex";
   mesg[1] = " ";
   mesg[2] = "</B/24>This demo was written to demonstrate the widgets";
   mesg[3] = "</B/24>available with the Cdk library. Not all of the";
   mesg[4] = "</B/24>Cdk widgets are used, but most of them have been.";
   mesg[5] = "</B/24>I hope this little demonstration helps give you an";
   mesg[6] = "</B/24>understanding of what the Cdk library offers.";
   mesg[7] = " ";
   mesg[8] = "<C></B/24>Have fun with it.";
   mesg[9] = " ";
   mesg[10] = "</B/24>ttfn,";
   mesg[11] = "<C></B/24>Mike";
   mesg[12] = "<C><#HL(35)>";
   mesg[13] = "<R></B/24>March 1996";

   popupLabel (screen, (CDK_CSTRING2)mesg, 14);
}

/*
 * This deletes a rolodex group.
 */
int deleteRolodexGroup (CDKSCREEN *screen, SRolodex * groupList, int groupCount)
{
   /* Declare local variables. */
   char *mesg[10];
   const char *buttons[5];
   char temp[MYSIZE];
   int selection, choice, x;

   /* If there are no groups, pop up a message telling them. */
   if (groupCount == 0)
   {
      mesg[0] = copyChar ("<C>Error");
      mesg[1] = copyChar ("<C>There as no groups defined.");
      popupLabel (screen, (CDK_CSTRING2)mesg, 2);
      freeCharList (mesg, 2);

      /* Return the current group count. */
      return groupCount;
   }

   /* Get the number of the group to delete. */
   selection = pickRolodexGroup (screen,
				 "<C></U>Delete Which Rolodex Group?",
				 groupList, groupCount);

   /* Check the results. */
   if (selection < 0)
   {
      mesg[0] = copyChar ("<C>   Delete Canceled   ");
      mesg[1] = copyChar ("<C>No Group Deleted");
      popupLabel (screen, (CDK_CSTRING2)mesg, 2);
      freeCharList (mesg, 2);
      return groupCount;
   }

   /* Let's make sure they want to delete the group. */
   mesg[0] = copyChar ("<C></U>Confirm Delete");
   mesg[1] = copyChar ("<C>Are you sure you want to delete the group");
   fmt1s (temp, "<C></R>%.*s<!R>?", groupList[selection].name);
   mesg[2] = copyChar (temp);
   buttons[0] = "<No>";
   buttons[1] = "<Yes>";
   choice = popupDialog (screen,
			 (CDK_CSTRING2)mesg, 3,
			 (CDK_CSTRING2)buttons, 2);
   freeCharList (mesg, 3);

   /* Check the results of the confirmation. */
   if (choice == 0)
   {
      mesg[0] = copyChar ("<C>   Delete Canceled   ");
      mesg[1] = copyChar ("<C>No Group Deleted");
      popupLabel (screen, (CDK_CSTRING2)mesg, 2);
      freeCharList (mesg, 2);
      return groupCount;
   }

   /* We need to delete the group file first. */
   unlink (groupList[selection].dbm);

   /* OK, lets delete the group. */
   freeChar (groupList[selection].name);
   freeChar (groupList[selection].desc);
   freeChar (groupList[selection].dbm);
   for (x = selection; x < groupCount - 1; x++)
   {
      groupList[x].name = groupList[x + 1].name;
      groupList[x].desc = groupList[x + 1].desc;
      groupList[x].dbm = groupList[x + 1].dbm;
   }
   groupCount--;
   GGroupModified = 1;

   /* Clean up. */
   return groupCount;
}

/*
 * This function gets information about a new phone number.
 */
int addPhoneRecord (CDKSCREEN *screen, SPhoneData * phoneData)
{
   /* Declare local variables. */
   CDKLABEL *title;
   CDKITEMLIST *itemList;
   SPhoneRecord *phoneRecord;
   const char *titleMesg[3];
   char *types[GLINETYPECOUNT];
   char temp[MYSIZE];
   int ret, x;
   int myType;

   /* Get the phone record pointer. */
   phoneRecord = &phoneData->record[phoneData->recordCount];

   /* Create a title label to display. */
   titleMesg[0] = "<C></B/16>Add New Phone Record";
   title = newCDKLabel (screen, CENTER, TOP,
			(CDK_CSTRING2)titleMesg, 1,
			FALSE, FALSE);
   drawCDKLabel (title, FALSE);

   /* Create the phone line type list. */
   for (x = 0; x < GLINETYPECOUNT; x++)
   {
      fmt1s (temp, "<C></U>%.*s", GLineType[x]);
      types[x] = copyChar (temp);
   }

   /* Get the phone line type. */
   itemList = newCDKItemlist (screen, CENTER, CENTER,
			      "<C>What Type Of Line Is It?",
			      "Type: ",
			      (CDK_CSTRING2)types, GLINETYPECOUNT, 0,
			      TRUE, FALSE);
   myType = activateCDKItemlist (itemList, NULL);
   phoneRecord->lineType = (ELineType) myType;
   destroyCDKItemlist (itemList);

   /* Clean up. */
   for (x = 0; x < GLINETYPECOUNT; x++)
   {
      freeChar (types[x]);
   }

   /* Check the return code of the line type question. */
   if (phoneRecord->lineType == -1)
   {
      phoneRecord->lineType = (ELineType) 0;
      return 1;
   }
   else if (phoneRecord->lineType == vPager || phoneRecord->lineType == vCell)
   {
      ret = getSmallPhoneRecord (screen, phoneRecord);
   }
   else
   {
      ret = getLargePhoneRecord (screen, phoneRecord);
   }

   /* Check the return value from the getXXXPhoneRecord function. */
   if (ret == 0)
   {
      phoneData->recordCount++;
   }

   /* Clean up. */
   destroyCDKLabel (title);

   /* Return the new phone list count. */
   return ret;
}

/*
 * This gets a phone record with all of the details.
 */
int getLargePhoneRecord (CDKSCREEN *screen, SPhoneRecord * phoneRecord)
{
   /* Declare local variables. */
   CDKENTRY *nameEntry, *addressEntry, *cityEntry;
   CDKENTRY *provEntry, *postalEntry, *descEntry;
   CDKTEMPLATE *phoneTemplate;
   const char *buttons[5];
   const char *mesg[15];

   /* Define the widgets. */
   nameEntry = newCDKEntry (screen, LEFT, 5,
			    NULL, "</B/5>Name: ",
			    A_NORMAL,
			    '_', vMIXED, 20, 2, MYSIZE,
			    TRUE, FALSE);
   addressEntry = newCDKEntry (screen, RIGHT, 5,
			       NULL, "</B/5>Address: ",
			       A_NORMAL,
			       '_', vMIXED, 40, 2, MYSIZE,
			       TRUE, FALSE);
   cityEntry = newCDKEntry (screen, LEFT, 8,
			    NULL, "</B/5>City: ",
			    A_NORMAL,
			    '_', vMIXED, 20, 2, MYSIZE,
			    TRUE, FALSE);
   provEntry = newCDKEntry (screen, 29, 8,
			    NULL, "</B/5>Province: ",
			    A_NORMAL,
			    '_', vMIXED, 15, 2, MYSIZE,
			    TRUE, FALSE);
   postalEntry = newCDKEntry (screen, RIGHT, 8,
			      NULL, "</B/5>Postal Code: ",
			      A_NORMAL,
			      '_', vUMIXED, 8, 2, MYSIZE,
			      TRUE, FALSE);
   phoneTemplate = newCDKTemplate (screen, LEFT, 11,
				   NULL,
				   "</B/5>Number: ",
				   "(###) ###-####",
				   "(___) ___-____",
				   TRUE, FALSE);
   descEntry = newCDKEntry (screen, RIGHT, 11,
			    NULL,
			    "</B/5>Description: ",
			    A_NORMAL,
			    '_', vMIXED, 20, 2, MYSIZE,
			    TRUE, FALSE);

   /* Get the phone information. */
   for (;;)
   {
      int ret;

      /* Draw the widgets on the screen. */
      drawCDKEntry (nameEntry, ObjOf (nameEntry)->box);
      drawCDKEntry (addressEntry, ObjOf (addressEntry)->box);
      drawCDKEntry (cityEntry, ObjOf (cityEntry)->box);
      drawCDKEntry (provEntry, ObjOf (provEntry)->box);
      drawCDKEntry (postalEntry, ObjOf (postalEntry)->box);
      drawCDKTemplate (phoneTemplate, ObjOf (phoneTemplate)->box);
      drawCDKEntry (descEntry, ObjOf (descEntry)->box);

      /* Activate the entries to get the information. */
      /* *INDENT-EQLS* */
      phoneRecord->name         = copyChar (activateCDKEntry (nameEntry, NULL));
      phoneRecord->address      = copyChar (activateCDKEntry (addressEntry, NULL));
      phoneRecord->city         = copyChar (activateCDKEntry (cityEntry, NULL));
      phoneRecord->province     = copyChar (activateCDKEntry (provEntry, NULL));
      phoneRecord->postalCode   = copyChar (activateCDKEntry (postalEntry, NULL));
      activateCDKTemplate (phoneTemplate, NULL);
      phoneRecord->phoneNumber  = mixCDKTemplate (phoneTemplate);
      phoneRecord->desc         = copyChar (activateCDKEntry (descEntry, NULL));

      /* Determine if the user wants to submit the info. */
      mesg[0] = "<C></U>Confirm New Phone Entry";
      mesg[1] = "<C>Do you want to add this phone number?";
      buttons[0] = "</B/24><Add Phone Number>";
      buttons[1] = "</B/16><Cancel>";
      buttons[2] = "</B/32><Modify Information>";
      ret = popupDialog (screen,
			 (CDK_CSTRING2)mesg, 2,
			 (CDK_CSTRING2)buttons, 3);

      /* Check the response of the popup dialog box. */
      if (ret == 0)
      {
	 /* The user wants to submit the information. */
	 destroyCDKEntry (nameEntry);
	 destroyCDKEntry (addressEntry);
	 destroyCDKEntry (cityEntry);
	 destroyCDKEntry (provEntry);
	 destroyCDKEntry (postalEntry);
	 destroyCDKEntry (descEntry);
	 destroyCDKTemplate (phoneTemplate);
	 return ret;
      }
      else if (ret == 1)
      {
	 /* The user does not want to submit the information. */
	 freeChar (phoneRecord->name);
	 freeChar (phoneRecord->address);
	 freeChar (phoneRecord->city);
	 freeChar (phoneRecord->province);
	 freeChar (phoneRecord->postalCode);
	 freeChar (phoneRecord->phoneNumber);
	 freeChar (phoneRecord->desc);
	 destroyCDKEntry (nameEntry);
	 destroyCDKEntry (addressEntry);
	 destroyCDKEntry (cityEntry);
	 destroyCDKEntry (provEntry);
	 destroyCDKEntry (postalEntry);
	 destroyCDKEntry (descEntry);
	 destroyCDKTemplate (phoneTemplate);
	 return ret;
      }
      else
      {
	 /* The user wants to edit the information again. */
	 freeChar (phoneRecord->name);
	 freeChar (phoneRecord->address);
	 freeChar (phoneRecord->city);
	 freeChar (phoneRecord->province);
	 freeChar (phoneRecord->postalCode);
	 freeChar (phoneRecord->phoneNumber);
	 freeChar (phoneRecord->desc);
      }
   }
}

/*
 * This gets a small phone record.
 */
int getSmallPhoneRecord (CDKSCREEN *screen, SPhoneRecord * phoneRecord)
{
   CDKENTRY *nameEntry, *descEntry;
   CDKTEMPLATE *phoneTemplate;
   const char *buttons[5];
   const char *mesg[15];

   /* Define the widgets. */
   nameEntry = newCDKEntry (screen, CENTER, 8,
			    NULL, "</B/5>Name: ",
			    A_NORMAL,
			    '_', vMIXED, 20, 2, MYSIZE,
			    TRUE, FALSE);
   phoneTemplate = newCDKTemplate (screen, CENTER, 11,
				   NULL, "</B/5>Number: ",
				   "(###) ###-####",
				   "(___) ___-____",
				   TRUE, FALSE);
   descEntry = newCDKEntry (screen, CENTER, 14,
			    NULL, "</B/5>Description: ",
			    A_NORMAL,
			    '_', vMIXED, 20, 2, MYSIZE,
			    TRUE, FALSE);

   /* Get the phone information. */
   for (;;)
   {
      int ret;

      /* Draw the widgets on the screen. */
      drawCDKEntry (nameEntry, ObjOf (nameEntry)->box);
      drawCDKTemplate (phoneTemplate, ObjOf (phoneTemplate)->box);
      drawCDKEntry (descEntry, ObjOf (descEntry)->box);

      /* Activate the entries to get the information. */
      /* *INDENT-EQLS* */
      phoneRecord->name         = copyChar (activateCDKEntry (nameEntry, NULL));
      activateCDKTemplate (phoneTemplate, NULL);
      phoneRecord->phoneNumber  = mixCDKTemplate (phoneTemplate);
      phoneRecord->desc         = copyChar (activateCDKEntry (descEntry, NULL));
      phoneRecord->address      = copyChar ("-");
      phoneRecord->city         = copyChar ("-");
      phoneRecord->province     = copyChar ("-");
      phoneRecord->postalCode   = copyChar ("-");

      /* Determine if the user wants to submit the info. */
      mesg[0] = "<C></B/5>Confirm New Phone Entry";
      mesg[1] = "<C>Do you want to add this phone number?";
      buttons[0] = "</B/24><Add Phone Number>";
      buttons[1] = "</B/16><Cancel>";
      buttons[2] = "</B/8><Modify Information>";
      ret = popupDialog (screen,
			 (CDK_CSTRING2)mesg, 2,
			 (CDK_CSTRING2)buttons, 3);

      /* Check the response of the popup dialog box. */
      if (ret == 0)
      {
	 /* The user wants to submit the information. */
	 destroyCDKEntry (nameEntry);
	 destroyCDKEntry (descEntry);
	 destroyCDKTemplate (phoneTemplate);
	 return ret;
      }
      else if (ret == 1)
      {
	 /* The user does not want to submit the information. */
	 freeChar (phoneRecord->name);
	 freeChar (phoneRecord->phoneNumber);
	 freeChar (phoneRecord->desc);
	 freeChar (phoneRecord->address);
	 freeChar (phoneRecord->city);
	 freeChar (phoneRecord->province);
	 freeChar (phoneRecord->postalCode);

	 destroyCDKEntry (nameEntry);
	 destroyCDKEntry (descEntry);
	 destroyCDKTemplate (phoneTemplate);
	 return ret;
      }
      else
      {
	 /* The user wants to edit the information again. */
	 freeChar (phoneRecord->name);
	 freeChar (phoneRecord->phoneNumber);
	 freeChar (phoneRecord->desc);
	 freeChar (phoneRecord->address);
	 freeChar (phoneRecord->city);
	 freeChar (phoneRecord->province);
	 freeChar (phoneRecord->postalCode);
      }
   }
}

/*
 * This prints a groups phone numbers.
 */
void printGroupNumbers (CDKSCREEN *screen, SRolodex * groupList, int groupCount)
{
   /* Declare local variables. */
   CDKSELECTION *selectionList;
   CDKENTRY *entry;
   CDKLABEL *title;
   char *itemList[MAX_ITEMS], *mesg[10], temp[MYSIZE];
   const char *choices[] =
   {
      "Print to Printer ",
      "Print to File",
      "Don't Print"
   };
   char *filename = NULL;
   char *printer = NULL;
   char *defaultPrinter = NULL;
   int height = groupCount;
   int x;

   /* Create the group list. */
   for (x = 0; x < groupCount; x++)
   {
      itemList[x] = copyChar (groupList[x].name);
   }

   /* Set the height of the selection list. */
   if (groupCount > 5)
   {
      height = 5;
   }
   height += 3;

   /* Create the selection list. */
   selectionList = newCDKSelection (screen, CENTER, CENTER, RIGHT,
				    height, 40,
				    "<C></U>Select Which Groups To Print",
				    (CDK_CSTRING2)itemList, groupCount,
				    (CDK_CSTRING2)choices, 3,
				    A_REVERSE, TRUE, FALSE);

   /* Activate the selection list. */
   if (activateCDKSelection (selectionList, NULL) == -1)
   {
      /* Tell the user they exited early. */
      destroyCDKSelection (selectionList);
      mesg[0] = copyChar ("<C>Print Canceled.");
      popupLabel (screen, (CDK_CSTRING2)mesg, 1);

      freeCharList (mesg, 1);
      freeCharList (itemList, (unsigned)groupCount);
      return;
   }
   eraseCDKSelection (selectionList);

   /* Determine which groups we want to print. */
   for (x = 0; x < groupCount; x++)
   {
      if (selectionList->selections[x] == 0)
      {
	 /* Create a title. */
	 fmt1s (temp, "<C></R>Printing Group [%.*s] to Printer",
		groupList[x].name);
	 mesg[0] = copyChar (temp);
	 title = newCDKLabel (screen, CENTER, TOP,
			      (CDK_CSTRING2)mesg, 1,
			      FALSE, FALSE);
	 drawCDKLabel (title, FALSE);
	 freeChar (mesg[0]);

	 /* Get the printer name to print to. */
	 entry = newCDKEntry (screen, CENTER, 8,
			      NULL, "</R>Printer Name: ",
			      A_NORMAL,
			      '_', vMIXED, 20, 2, MYSIZE, TRUE, FALSE);

	 /* Set the printer name to the default printer. */
	 defaultPrinter = getenv ("PRINTER");
	 setCDKEntry (entry, defaultPrinter, 2, MYSIZE, TRUE);
	 printer = copyChar (activateCDKEntry (entry, NULL));
	 destroyCDKEntry (entry);

	 /* Print the group. */
	 if (printGroup (groupList[x], "/tmp/rolodex.tmp", printer) == 0)
	 {
	    /* The group could not be printed. */
	    fmt1s (temp, "<C>Sorry the group '%.*s' could not be printed.",
		   groupList[x].name);
	    mesg[0] = strdup (temp);
	    popupLabel (screen, (CDK_CSTRING2)mesg, 1);
	    freeChar (mesg[0]);
	 }

	 /* Clean up. */
	 destroyCDKLabel (title);
	 freeChar (printer);
	 unlink ("/tmp/rolodex.tmp");
      }
      else if (selectionList->selections[x] == 1)
      {
	 /* Create a title. */
	 fmt1s (temp, "<C></R>Printing Group [%.*s] to File", groupList[x].name);
	 mesg[0] = copyChar (temp);
	 title = newCDKLabel (screen, CENTER, TOP,
			      (CDK_CSTRING2)mesg, 1,
			      FALSE, FALSE);
	 drawCDKLabel (title, FALSE);
	 freeChar (mesg[0]);

	 /* Get the filename to print to. */
	 entry = newCDKEntry (screen, CENTER, 8,
			      NULL, "</R>Filename: ",
			      A_NORMAL, '_', vMIXED,
			      20, 2, MYSIZE, TRUE, FALSE);
	 filename = copyChar (activateCDKEntry (entry, NULL));
	 destroyCDKEntry (entry);

	 /* Print the group. */
	 if (printGroup (groupList[x], filename, printer) == 0)
	 {
	    /* The group could not be printed. */
	    fmt1s (temp, "<C>Sorry the group '%.*s' could not be printed.",
		   groupList[x].name);
	    mesg[0] = strdup (temp);
	    popupLabel (screen, (CDK_CSTRING2)mesg, 1);
	    freeChar (mesg[0]);
	 }

	 /* Clean up. */
	 destroyCDKLabel (title);
	 freeChar (filename);
      }
   }

   /* Clean up. */
   destroyCDKSelection (selectionList);
   for (x = 0; x < groupCount; x++)
   {
      freeChar (itemList[x]);
   }
}

/*
 * This actually prints the phone record.
 */
int printGroup (SRolodex groupRecord, const char *filename, char *printer)
{
   /* Declare local variables. */
#if defined (__MINGW32__)
   int uid = 0;
#else
   uid_t uid = getuid ();
#endif
   char tempFilename[MYSIZE];
   SPhoneData phoneData;
   int phoneCount, x;
   FILE *fd;

   /* Read the data file. */
   phoneCount = readPhoneDataFile (groupRecord.dbm, &phoneData);

   /* Create the temporary filename. */
   if (filename != NULL)
   {
      fmt1s (tempFilename, "%.*s", filename);
   }
   else
   {
      sprintf (tempFilename, "/tmp/rolodex.%d", (int)uid);
   }

   /* Open the file. */
   if ((fd = fopen (tempFilename, "a+")) == NULL)
   {
      /* Clean up. */
      for (x = 0; x < phoneCount; x++)
      {
	 freeChar (phoneData.record[x].name);
	 freeChar (phoneData.record[x].phoneNumber);
	 freeChar (phoneData.record[x].address);
	 freeChar (phoneData.record[x].city);
	 freeChar (phoneData.record[x].province);
	 freeChar (phoneData.record[x].postalCode);
	 freeChar (phoneData.record[x].desc);
      }
      return 0;
   }

   /* Start writing the group information to the temp file. */
   fprintf (fd, "Group Name: %40s\n", groupRecord.name);
   fprintf (fd,
	    "==============================================================================\n");
   for (x = 0; x < phoneCount; x++)
   {
      SPhoneRecord *phoneRecord = &phoneData.record[x];
      fprintf (fd, "Name        : %s\n", phoneRecord->name);
      fprintf (fd, "Phone Number: %s (%s)\n", phoneRecord->phoneNumber,
	       GLineType[phoneRecord->lineType]);
      if (phoneRecord->lineType != vPager && phoneRecord->lineType != vCell)
      {
	 fprintf (fd, "Address     : %-20s, %-20s\n", phoneRecord->address, phoneRecord->city);
	 fprintf (fd, "            : %-10s, %-10s\n", phoneRecord->province, phoneRecord->postalCode);
      }
      fprintf (fd, "Description : %-30s\n", phoneRecord->desc);
      fprintf (fd,
	       "------------------------------------------------------------------------------\n");
   }

   /* Determine if the information is going to a file or printer. */
   if (printer != NULL)
   {
      char command[MYSIZE * 4];
      /* Print the file to the given printer. */
      sprintf (command, "lpr -P%s %s", printer, tempFilename);
      system (command);

      /* We have to unkink the temp file. */
      unlink (tempFilename);
   }

   /* Clean up some memory. */
   for (x = 0; x < phoneCount; x++)
   {
      freeChar (phoneData.record[x].name);
      freeChar (phoneData.record[x].phoneNumber);
      freeChar (phoneData.record[x].address);
      freeChar (phoneData.record[x].city);
      freeChar (phoneData.record[x].province);
      freeChar (phoneData.record[x].postalCode);
      freeChar (phoneData.record[x].desc);
   }

   /* Close the filename. */
   fclose (fd);
   return 1;
}

/*
 ****************************************************************
 *		    Start of callback functions.
 ****************************************************************
 */
int entryPreProcessCB (EObjectType cdkType GCC_UNUSED, void *object
		       GCC_UNUSED, void *clientData GCC_UNUSED, chtype input)
{
   if (input == ' ')
   {
      Beep ();
      return 0;
   }
   return 1;
}

/*
 * This allows the user to insert a new phone entry into the database.
 */
int insertPhoneEntryCB (EObjectType cdkType GCC_UNUSED, void *object, void
			*clientData, chtype key GCC_UNUSED)
{
   /* Declare local variables. */
   CDKSCROLL *scrollp = (CDKSCROLL *)object;
   SPhoneData *phoneData = (SPhoneData *) clientData;
   SPhoneRecord *phoneRecord = &phoneData->record[phoneData->recordCount];
   char temp[MYSIZE];

   /* Make the scrolling list disappear. */
   eraseCDKScroll (scrollp);

   /* Call the function which gets phone record information. */
   if (addPhoneRecord (ScreenOf (scrollp), phoneData) == 0)
   {
      sprintf (temp, "%s (%s)", phoneRecord->name, GLineType[phoneRecord->lineType]);
      addCDKScrollItem (scrollp, temp);
   }

   /* Redraw the scrolling list. */
   drawCDKScroll (scrollp, ObjOf (scrollp)->box);
   return (FALSE);
}

/*
 * This allows the user to delete a phone entry from the database.
 */
int deletePhoneEntryCB (EObjectType cdkType GCC_UNUSED, void *object, void
			*clientData, chtype key GCC_UNUSED)
{
   /* Declare local variables. */
   CDKSCROLL *scrollp = (CDKSCROLL *)object;
   SPhoneData *phoneData = (SPhoneData *) clientData;
   const char *mesg[3];
   char temp[MYSIZE], *hold;
   const char *buttons[] =
   {
      "</B/16><No>",
      "</B/24><Yes>"
   };
   int position = scrollp->currentItem;
   int x;

   /* Make the scrolling list disappear. */
   eraseCDKScroll (scrollp);

   /* Check the number of entries left in the list. */
   if (scrollp->listSize == 0)
   {
      mesg[0] = "There are no more numbers to delete.";
      popupLabel (ScreenOf (scrollp), (CDK_CSTRING2)mesg, 1);
      return (FALSE);
   }

   /* Ask the user if they really want to delete the listing. */
   mesg[0] = "<C>Do you really want to delete the phone entry";
   hold = chtype2Char (scrollp->item[scrollp->currentItem]);
   fmt1s (temp, "<C></B/16>%.*s", hold);
   freeChar (hold);
   mesg[1] = copyChar (temp);
   if (popupDialog (ScreenOf (scrollp),
		    (CDK_CSTRING2)mesg, 2,
		    (CDK_CSTRING2)buttons, 2) == 1)
   {
      /* Remove the item from the phone data record. */
      for (x = position; x < phoneData->recordCount - 1; x++)
      {
	 /* *INDENT-EQLS* */
	 phoneData->record[x].name        = phoneData->record[x + 1].name;
	 phoneData->record[x].lineType    = phoneData->record[x + 1].lineType;
	 phoneData->record[x].phoneNumber = phoneData->record[x + 1].phoneNumber;
	 phoneData->record[x].address     = phoneData->record[x + 1].address;
	 phoneData->record[x].city        = phoneData->record[x + 1].city;
	 phoneData->record[x].province    = phoneData->record[x + 1].province;
	 phoneData->record[x].postalCode  = phoneData->record[x + 1].postalCode;
	 phoneData->record[x].desc        = phoneData->record[x + 1].desc;
      }
      phoneData->recordCount--;

      /* Nuke the entry. */
      deleteCDKScrollItem (scrollp, position);
   }

   /* Redraw the scrolling list. */
   drawCDKScroll (scrollp, ObjOf (scrollp)->box);
   return (FALSE);
}

/*
 * This function provides help for the phone list editor.
 */
int phoneEntryHelpCB (EObjectType cdkType GCC_UNUSED, void *object, void
		      *clientData GCC_UNUSED, chtype key GCC_UNUSED)
{
   /* Declare local variables. */
   CDKSCROLL *scrollp = (CDKSCROLL *)object;
   char *mesg[10], temp[100];

   /* Create the help title. */
   sprintf (temp, "<C></R>Rolodex Phone Editor");
   mesg[0] = copyChar (temp);

   sprintf (temp, "<B=i     > Inserts a new phone entry.");
   mesg[1] = copyChar (temp);

   sprintf (temp, "<B=d     > Deletes the currently selected phone entry.");
   mesg[2] = copyChar (temp);

   sprintf (temp, "<B=Escape> Exits the scrolling list.");
   mesg[3] = copyChar (temp);

   sprintf (temp, "<B=?     > Pops up this help window.");
   mesg[4] = copyChar (temp);

   popupLabel (ScreenOf (scrollp), (CDK_CSTRING2)mesg, 5);

   freeCharList (mesg, 5);
   return (FALSE);
}

/*
 * This is a callback to the menu widget. It allows the user to
 * ask for help about any sub-menu item.
 */
int helpCB (EObjectType cdkType GCC_UNUSED, void *object, void *clientData
	    GCC_UNUSED, chtype key GCC_UNUSED)
{
   /* *INDENT-EQLS* */
   CDKMENU *menu   = (CDKMENU *)object;
   int menuList    = menu->currentTitle;
   int submenuList = menu->currentSubtitle;
   int selection   = ((menuList * 100) + submenuList);
   const char *mesg[20];
   char *msg_0;
   char *name;
   char temp[100];

   /* Create the help title. */
   name = chtype2Char (menu->sublist[menuList][submenuList]);
   stripWhiteSpace (vBOTH, name);
   fmt1s (temp, "<C></R>Help<!R> </U>%.*s<!U>", name);
   mesg[0] = msg_0 = copyChar (temp);
   freeChar (name);

   /* Set the default value for the message. */
   mesg[1] = "<C>No help defined for this menu.";

   /* Given the current menu item, create a message. */
   if (selection == 0)
   {
      mesg[1] = "<C>This reads a new rolodex RC file.";
   }
   else if (selection == 1)
   {
      mesg[1] = "<C>This saves the current group information in the default RC file.";
   }
   else if (selection == 2)
   {
      mesg[1] = "<C>This saves the current group information in a new RC file.";
   }
   else if (selection == 3)
   {
      mesg[1] = "<C>This exits this program.";
   }
   else if (selection == 100)
   {
      mesg[1] = "<C>This creates a new rolodex group.";
   }
   else if (selection == 101)
   {
      mesg[1] = "<C>This opens a rolodex group.";
   }
   else if (selection == 102)
   {
      mesg[1] = "<C>This deletes a rolodex group.";
   }
   else if (selection == 200)
   {
      mesg[1] = "<C>This prints out selected groups phone numbers.";
   }
   else if (selection == 300)
   {
      mesg[1] = "<C>This gives a little history on this program.";
   }
   else if (selection == 301)
   {
      mesg[1] = "<C>This provides information about the rolodex.";
   }

   /* Pop up the message. */
   popupLabel (ScreenOf (menu), (CDK_CSTRING2)mesg, 2);
   freeChar (msg_0);

   /* Redraw the submenu window. */
   drawCDKMenuSubwin (menu);
   return (FALSE);
}

/*
 * This is a callback to the group list scrolling list.
 */
int groupInfoCB (EObjectType cdkType GCC_UNUSED, void *object, void
		 *clientData, chtype key GCC_UNUSED)
{
   /* *INDENT-EQLS* */
   CDKSCROLL *scrollp  = (CDKSCROLL *)object;
   SRolodex *groupList = (SRolodex *) clientData;
   int selection       = scrollp->currentItem;
   char *mesg[5];
   char temp[100];

   /* Create the message to be displayed. */
   mesg[0] = copyChar ("<C></U>Detailed Group Information.");

   fmt1s (temp, "</R>Group Name         <!R> %.*s", groupList[selection].name);
   mesg[1] = copyChar (temp);

   fmt1s (temp, "</R>Group Description  <!R> %.*s", groupList[selection].desc);
   mesg[2] = copyChar (temp);

   fmt1s (temp, "</R>Group Database File<!R> %.*s", groupList[selection].dbm);
   mesg[3] = copyChar (temp);

   /* Display the message. */
   popupLabel (ScreenOf (scrollp), (CDK_CSTRING2)mesg, 4);
   freeCharList (mesg, 4);

   /* Redraw the scrolling list. */
   drawCDKScroll (scrollp, ObjOf (scrollp)->box);
   return (FALSE);
}
