Code:
#include "main.h"
/*
===============================================================================
Author: Hammer, May 2002, For www.cprogramming.com/cboard/
File: UserInterfaceUtilities.c
Contents: Menu structs
ui_DisplayMenu
ui_AddNewEntry
ui_UpdateEntry
ui_AddOrUpdateEntry
ui_DeleteEntry
ui_UndeleteEntry
ui_SearchAll
ui_SearchByID
ui_DisplayAll
ui_Save
ui_PurgeDeleted
ui_ShowInfo
ui_ToggleAutoSave
ui_SetLinesPerDisplay
===============================================================================
*/
/*
===============================================================================
The following are the structs that make up the menus.
===============================================================================
*/
static struct menu_item OptionsMenu[] =
{
{"Auto-Save Setup", ui_ToggleAutoSave, NULL },
{"Set Lines Per Display", ui_SetLinesPerDisplay, NULL },
{ NULL, NULL, NULL }
};
static struct menu_item SearchByFieldMenu[] =
{
{ "Search By ID", ui_SearchAll, (void *)RECF_ID },
{ "Search By Name", ui_SearchAll, (void *)RECF_NAME },
{ "Search By Phone Num 1", ui_SearchAll, (void *)RECF_PHONENUM1 },
{ "Search By Phone Num 2", ui_SearchAll, (void *)RECF_PHONENUM2 },
{ "Search By Phone Num 3", ui_SearchAll, (void *)RECF_PHONENUM3 },
{ "Search By Phone Num 3", ui_SearchAll, (void *)RECF_PHONENUM3 },
{ "Search By Address Line 1", ui_SearchAll, (void *)RECF_ADDRLINE1 },
{ "Search By Address Line 2", ui_SearchAll, (void *)RECF_ADDRLINE2 },
{ "Search By Address Line 3", ui_SearchAll, (void *)RECF_ADDRLINE3 },
{ "Search By Address Line 4", ui_SearchAll, (void *)RECF_ADDRLINE4 },
{ "Search By Address Line 5", ui_SearchAll, (void *)RECF_ADDRLINE5 },
{ "Search By Email Address", ui_SearchAll, (void *)RECF_EMAILADDR },
{ "Search By Misc Field", ui_SearchAll, (void *)RECF_MISC },
{ NULL, NULL, NULL }
};
static struct menu_item SortByFieldMenu[] =
{
{ "Sort By ID", ui_SetSortBy, (void *)RECF_ID },
{ "Sort By Name", ui_SetSortBy, (void *)RECF_NAME },
{ "Sort By Phone Num 1", ui_SetSortBy, (void *)RECF_PHONENUM1 },
{ "Sort By Phone Num 2", ui_SetSortBy, (void *)RECF_PHONENUM2 },
{ "Sort By Phone Num 3", ui_SetSortBy, (void *)RECF_PHONENUM3 },
{ "Sort By Phone Num 3", ui_SetSortBy, (void *)RECF_PHONENUM3 },
{ "Sort By Address Line 1", ui_SetSortBy, (void *)RECF_ADDRLINE1 },
{ "Sort By Address Line 2", ui_SetSortBy, (void *)RECF_ADDRLINE2 },
{ "Sort By Address Line 3", ui_SetSortBy, (void *)RECF_ADDRLINE3 },
{ "Sort By Address Line 4", ui_SetSortBy, (void *)RECF_ADDRLINE4 },
{ "Sort By Address Line 5", ui_SetSortBy, (void *)RECF_ADDRLINE5 },
{ "Sort By Email Address", ui_SetSortBy, (void *)RECF_EMAILADDR },
{ "Sort By Misc Field", ui_SetSortBy, (void *)RECF_MISC },
{ NULL, NULL, NULL }
};
static struct menu_item Page2Menu[] =
{
{ "Search By Chosen Field", ui_DisplayMenu, SearchByFieldMenu },
{ "Display All Entries", ui_DisplayAll, NULL },
{ "Set Sort Order", ui_DisplayMenu, SortByFieldMenu },
{ "Undelete An Entry", ui_UndeleteEntry, NULL },
{ "Save Changes", ui_Save, NULL },
{ "Purge Entries Marked For Deletion", ui_PurgeDeleted, NULL },
{ "Application Options", ui_DisplayMenu, OptionsMenu },
{ NULL, NULL, NULL }
};
struct menu_item MainMenu[] =
{
{ "Add New Entry", ui_AddNewEntry, NULL },
{ "Delete Entry", ui_DeleteEntry, NULL },
{ "Update Entry", ui_UpdateEntry, NULL },
{ "Display Entry", ui_SearchAll, (void *)RECF_ID},
{ "Search By Name", ui_SearchAll, (void *)RECF_NAME },
{ "Save Changes", ui_Save, NULL },
{ "More Options", ui_DisplayMenu, Page2Menu },
{ "Help/About", ui_ShowInfo, NULL },
{ NULL, NULL, NULL }
};
/*
===============================================================================
Function: ui_DisplayMenu
Args: Pointer to a menu to be displayed
Returns: Nothing
Purpose: Displays the menu, gets the users option and processes it.
===============================================================================
*/
void ui_DisplayMenu(void *pmenu)
{
int i, choice;
struct menu_item *menu = pmenu;
struct menu_item *item;
for (;;)
{
printf ("---> PhoneBook Menu <---\n");
for (i = 1, item = menu; item->Text != NULL; item++, i++)
{
printf("%2d %s\n", i, item->Text);
}
printf(" x Exit Menu\nEnter choice >");
if ((choice = io_GetInt(i-1)) == RC_BAD)
break;
item = menu + choice - 1;
if (item->Text == NULL) break;
(void) (*item->fptr) (item->args);
}
}
/*
===============================================================================
Function: ui_AddNewEntry
Args: None (NULL pointer)
Returns: Nothing
Purpose: Caller function to process a new entry request.
The menu is capable of calling the next function directly,
but I left this in to assist future developments.
===============================================================================
*/
void ui_AddNewEntry(void *Dummy)
{
ui_AddOrUpdateEntry(Dummy);
}
/*
===============================================================================
Function: ui_UpdateEntry
Args: None (NULL Pointer)
Returns: Nothing
Purpose: Asks user for an ID, then calls another function to do record updates
===============================================================================
*/
void ui_UpdateEntry(void *Dummy)
{
struct Record *rec;
int choice;
if (gl_HighestID == 0)
{
printf ("No entries in the database!\n");
io_EnterToContinue();
return;
}
for (;;)
{
printf("Enter Record ID Number (1-%d, x to exit) >", gl_HighestID);
if ((choice = io_GetInt(gl_HighestID)) == RC_BAD)
return;
if ((rec = li_GetDataPtrByID(gl_AppData.MainList, choice)) == NULL)
{
printf("No such entry: %d\n", choice);
}
else break;
}
ui_AddOrUpdateEntry(rec);
}
/*
===============================================================================
Function: ui_AddOrUpdateEntry
Args: Pointer to a struct to be updated, OR NULL is request is to ADD.
Returns: Nothing
Purpose: Obtains user data to complete a Record structure, then
adds or updates a record in the list.
===============================================================================
*/
void ui_AddOrUpdateEntry(struct Record *newrec)
{
struct Record rec;
char buffer[MAXL_FIELD+1];
struct Node *tmpListPtr;
memset (&rec, 0, sizeof(struct Record));
/*
* If newrec is not NULL, the it must be pointing to a Record struct and
* therefore we are updating an existing record. In this case, we show
* the user the existing data before asking for new. If they press <enter>
* on a blank line, we leave the old data in place, this saves them retyping
* each field when they are only changing one.
*/
if (newrec != NULL)
{
rec = *newrec;
printf("(Press <Enter> on blank line to keep existing text)\nCurrent Name: %s\n", newrec->Name);
}
printf ("Enter Name (max %d chars) >", MAXL_NAME);
if (io_GetLine(buffer, MAXL_NAME+1, stdin) == 0)
{
if (newrec == NULL)
return; /* allow user to break out by entering a new record with no name */
}
else strncpy (rec.Name, buffer, MAXL_NAME+1);
if (newrec != NULL) printf("Current 1st Phone: %s\n", newrec->PhoneNum1);
printf("Enter 1st Phone (max %d chars) >", MAXL_PHONENUM);
if (io_GetLine(buffer, MAXL_PHONENUM+1, stdin) > 0)
strncpy (rec.PhoneNum1, buffer, MAXL_PHONENUM+1);
if (newrec != NULL) printf("Current 2nd Phone: %s\n", newrec->PhoneNum2);
printf("Enter 2nd Phone (max %d chars) >", MAXL_PHONENUM);
if (io_GetLine(buffer, MAXL_PHONENUM+1, stdin) > 0)
strncpy (rec.PhoneNum2, buffer, MAXL_PHONENUM+1);
if (newrec != NULL) printf("Current 3rd Phone: %s\n", newrec->PhoneNum3);
printf("Enter 3rd Phone (max %d chars) >", MAXL_PHONENUM);
if (io_GetLine(buffer, MAXL_PHONENUM+1, stdin) > 0)
strncpy (rec.PhoneNum3, buffer, MAXL_PHONENUM+1);
if (newrec != NULL) printf("Current Address Line 1: %s\n", newrec->AddrLine1);
printf("Enter Address Line 1 of 5 (max %d chars) >", MAXL_ADDR);
if (io_GetLine(buffer, MAXL_ADDR+1, stdin) > 0)
strncpy (rec.AddrLine1, buffer, MAXL_ADDR+1);
if (newrec != NULL) printf("Current Address Line 2: %s\n", newrec->AddrLine2);
printf("Enter Address Line 2 of 5 (max %d chars) >", MAXL_ADDR);
if (io_GetLine(buffer, MAXL_ADDR+1, stdin) > 0)
strncpy (rec.AddrLine2, buffer, MAXL_ADDR+1);
if (newrec != NULL) printf("Current Address Line 3: %s\n", newrec->AddrLine3);
printf("Enter Address Line 3 of 5 (max %d chars) >", MAXL_ADDR);
if (io_GetLine(buffer, MAXL_ADDR+1, stdin) > 0)
strncpy (rec.AddrLine3, buffer, MAXL_ADDR+1);
if (newrec != NULL) printf("Current Address Line 4: %s\n", newrec->AddrLine4);
printf("Enter Address Line 4 of 5 (max %d chars) >", MAXL_ADDR);
if (io_GetLine(buffer, MAXL_ADDR+1, stdin) > 0)
strncpy (rec.AddrLine4, buffer, MAXL_ADDR+1);
if (newrec != NULL) printf("Current Address Line 5: %s\n", newrec->AddrLine5);
printf("Enter Address Line 5 of 5 (max %d chars) >", MAXL_ADDR);
if (io_GetLine(buffer, MAXL_ADDR+1, stdin) > 0)
strncpy (rec.AddrLine5, buffer, MAXL_ADDR+1);
if (newrec != NULL) printf("Current Email Address: %s\n", newrec->EmailAddr);
printf("Enter Email Address (max %d chars) >", MAXL_ADDR);
if (io_GetLine(buffer, MAXL_ADDR+1, stdin) > 0)
strncpy (rec.EmailAddr, buffer, MAXL_ADDR+1);
if (newrec != NULL) printf("Current Misc data: %s\n", newrec->Misc);
printf("Enter Misc Details (max %d chars) >", MAXL_MISC);
if (io_GetLine(buffer, MAXL_MISC+1, stdin) > 0)
strncpy (rec.Misc, buffer, MAXL_MISC+1);
if (newrec == NULL) {rec.ID = gl_HighestID+1; rec.Status = 0;};
/*
* Display new/changed details for user to review
*/
printf ("Record Details Now:\n");
rec_PrintLong(&rec);
if (io_GetYesNo("Confirm Details (y/n) >") == TRUE)
{ /* User has confirmed new details, so update/add record and add to list */
if (newrec) /* updating an existing record */
{
*newrec = rec;
}
else
{ /* Adding a new record */
if ((newrec = malloc(sizeof (struct Record))) == NULL)
{
perror ("Add/Update failed on malloc");
return;
}
*newrec = rec;
if ((tmpListPtr = li_Insert (gl_AppData.MainList, newrec, rec_Compare)) == NULL)
{
printf("Error performing insert on list. Item not addded.\n");
free(newrec); /* throw the record away */
}
else
{
gl_AppData.MainList = tmpListPtr;
newrec->ID = gl_HighestID;
}
}
gl_AppData.DataChanged = TRUE;
if (gl_AppCfg.AutoSave) (void)io_WriteAllRecordsToFile(gl_AppData.MainList);
}
}
/*
===============================================================================
Function: ui_DeleteEntry
Args: None (NULL pointer)
Returns: Nothing
Purpose: Gets an ID from the user, displays the record then marks it
for deletion if the user agrees. If autosave is on, the
list will be written to disk and the record will be erased
completely (undelete not available in this case).
===============================================================================
*/
void ui_DeleteEntry(void *Dummy)
{
struct Record *rec;
int choice;
if (gl_HighestID == 0)
{
printf ("No entries in the database!\n");
io_EnterToContinue();
return;
}
printf("Enter Record ID Number (1-%d, x to exit) >", gl_HighestID);
if ((choice = io_GetInt(gl_HighestID)) == RC_BAD)
return;
if ((rec = li_GetDataPtrByID(gl_AppData.MainList, choice)) == NULL)
{
printf("No such entry: %d\n", choice);
io_EnterToContinue();
return;
}
rec_PrintLong(rec);
if (io_GetYesNo("Confirm Delete (y/n) >") == TRUE)
{
rec->Status = rec->Status | ST_DELETED;
gl_AppData.DataChanged = TRUE;
if (gl_AppCfg.AutoSave) (void)io_WriteAllRecordsToFile(gl_AppData.MainList);
}
}
/*
===============================================================================
Function: ui_UndeleteEntry
Args: None (NULL Pointer)
Returns: Nothing
Purpose: Gets an ID from the user, and unsets the deleted flag.
===============================================================================
*/
void ui_UndeleteEntry(void *Dummy)
{
struct Record *rec;
int choice;
if (gl_HighestID == 0)
{
printf ("No entries in the database!\n");
io_EnterToContinue();
return;
}
printf("Enter Record ID Number (1-%d, x to exit) >", gl_HighestID);
if ((choice = io_GetInt(gl_HighestID)) == RC_BAD)
return;
if ((rec = li_GetDataPtrByID(gl_AppData.MainList, choice)) == NULL)
{
printf("No such entry: %d\n", choice);
io_EnterToContinue();
return;
}
if (rec->Status & ST_DELETED)
{
rec->Status = rec->Status ^ ST_DELETED;
printf ("Record undeleted\n");
gl_AppData.DataChanged = TRUE;
if (gl_AppCfg.AutoSave) (void)io_WriteAllRecordsToFile(gl_AppData.MainList);
}
else
printf ("Record was not marked for deletion\n");
io_EnterToContinue();
}
/*
===============================================================================
Function: ui_SearchByID
Args: None
Returns: Nothing
Purpose: Gets an ID from the user, then display that Record in detail
===============================================================================
*/
void ui_SearchByID(void)
{
struct Record *rec;
int choice;
if (gl_HighestID == 0)
{
printf ("No entries in the database!\n");
io_EnterToContinue();
return;
}
printf("Enter Record ID Number (1-%d, x to exit) >", gl_HighestID);
if ((choice = io_GetInt(gl_HighestID)) == RC_BAD)
return;
if ((rec = li_GetDataPtrByID(gl_AppData.MainList, choice)) == NULL)
printf("No such entry: %d\n", choice);
else rec_PrintLong(rec);
io_EnterToContinue();
}
/*
===============================================================================
Function: ui_SearchAll
Args: Field type
Returns: Nothing
Purpose: Gets search criteria from user, the traverses the list, short
printing each record that matches the criteria.
===============================================================================
*/
void ui_SearchAll(void *WhichField)
{
char buffer[MAXL_FIELD + 1];
int i, len;
if (gl_HighestID == 0)
{
printf ("No entries in the database!\n");
io_EnterToContinue();
return;
}
if ((enum RECORD_FIELDS)WhichField == RECF_ID)
{
ui_SearchByID();
return;
}
printf("Enter Criteria >");
if ((len = io_GetLine(buffer, MAXL_FIELD + 1, stdin)) != 0)
{
gl_AppData.SearchField = (enum RECORD_FIELDS)WhichField;
for (i = 0; i < len; i++) /* convert to lower case for search */
if (isupper(buffer[i])) buffer[i] = tolower(buffer[i]);
strncpy(gl_AppData.SearchText, buffer, MAXL_FIELD + 1);
gl_AppData.LinesDisplayedSoFar = 0;
(void)li_Traverse(gl_AppData.MainList, rec_SearchAndPrint);
}
io_EnterToContinue();
}
/*
===============================================================================
Function: ui_DisplayAll
Args: None
Returns: Nothing
Purpose: Traverses the list and does a short print of each record
===============================================================================
*/
void ui_DisplayAll(void *Dummy)
{
if (gl_HighestID == 0)
{
printf ("No entries in the database!\n");
io_EnterToContinue();
return;
}
gl_AppData.LinesDisplayedSoFar = 0;
gl_AppData.SearchField = RECF_DUMMY;
(void)li_Traverse(gl_AppData.MainList, rec_SearchAndPrint);
io_EnterToContinue();
}
/*
===============================================================================
Function: ui_Save
Args: None (NULL Pointer)
Returns: Nothing
Purpose: Gets confirmation from the user, then saves the current list to disk
===============================================================================
*/
void ui_Save(void *Dummy)
{
if (io_GetYesNo("Confirm Save (including purge of deleted records) (y/n) >") == TRUE)
{
(void)io_WriteAllRecordsToFile(gl_AppData.MainList);
}
}
/*
===============================================================================
Function: ui_PurgeDeleted
Args: None (NULL Pointer)
Returns: Nothing
Purpose: Gets confirmation from the user, then traverses the list,
removing (free'ing) any records that are marked as deleted
===============================================================================
*/
void ui_PurgeDeleted(void *Dummy)
{
unsigned char status = ST_DELETED;
if (gl_HighestID == 0)
{
printf ("No entries in the database!\n");
io_EnterToContinue();
return;
}
if (io_GetYesNo("Confirm Purge (y/n) >") == TRUE)
gl_AppData.MainList = li_DeleteNodeAndData (gl_AppData.MainList, &status, rec_CompareStatus);
}
/*
===============================================================================
Function: ui_ShowInfo
Args: None (NULL Pointer)
Returns: Nothing
Purpose: Displays stats about the program and the PhoneBook
===============================================================================
*/
void ui_ShowInfo(void *Dummy)
{
printf ("--->\n---> **PhoneBook** - Version: %s\n", PRG_VERSION);
printf ("---> Written By *Hammer* for the C Programming Contest\n");
printf ("---> held by http://www.cprogramming.com/cboard/ in May 2002\n");
printf ("---> You can contact the author via email: claw_hammer@hotmail.com\n");
printf ("--->\n---> The PhoneBook datafile is %s\n", STR_FILENAME);
printf ("---> The PhoneBook configuration file is %s\n", STR_CFG_FILENAME);
printf ("--->\n---> There are currently %d entries loaded.\n", li_Count(gl_AppData.MainList));
printf ("--->\n---> This program accepts command line information\n");
printf ("---> For example (assuming the program name is phone.exe):\n");
printf ("---> >phone.exe hammer tommy\n");
printf ("---> will result in the database being searched for the\n");
printf ("---> names hammer and tommy and the results being displayed.\n");
printf ("---> In this case, the menu is not loaded, and the\n");
printf ("---> program terminates. This is useful for quick lookups.\n");
printf ("--->\n");
io_EnterToContinue();
}
/*
===============================================================================
Function: ui_ToggleAutoSave
Args: None (NULL Pointer)
Returns: Nothing
Purpose: Allows the user to turn auto-save off and on.
===============================================================================
*/
void ui_ToggleAutoSave(void *Dummy)
{
printf ("Auto-Save is currently %s\n", (gl_AppCfg.AutoSave)?"On":"Off");
if (io_GetYesNo("Toggle auto-save (y/n) >") == TRUE)
{
gl_AppCfg.AutoSave = ~gl_AppCfg.AutoSave;
printf ("Auto-Save is now %s\n", (gl_AppCfg.AutoSave)?"On":"Off");
io_EnterToContinue();
}
}
/*
===============================================================================
Function: ui_SetLinesPerDisplay
Args: None (NULL Pointer)
Returns: Nothing
Purpose: Allows the user to set the number of lines displayed in lists
before a io_EnterToContinue is called.
===============================================================================
*/
void ui_SetLinesPerDisplay(void *Dummy)
{
int Lines;
printf ("This option determines how many lines are output to the screen\nbefore a \"hit enter to continue\" message is displayed\n");
printf ("The current setting is %d\n", gl_AppCfg.LinesPerDisplay);
printf ("Enter a new number or x to exit >");
if ((Lines = io_GetInt(MAX_LINES_PER_PAGE)) == RC_BAD)
return;
gl_AppCfg.LinesPerDisplay = Lines;
printf ("The new setting is %d\n", gl_AppCfg.LinesPerDisplay);
io_EnterToContinue();
}
/*
===============================================================================
Function: ui_SetSortBy
Args: Field type
Returns: Nothing
Purpose: Allows the user to set the sort order
===============================================================================
*/
void ui_SetSortBy(void *WhichField)
{
gl_AppCfg.SortField = (enum RECORD_FIELDS)WhichField;
printf ("Sorting the data...");
li_Sort (gl_AppData.MainList, rec_Compare);
printf ("Done!\n");
io_EnterToContinue();
}
main.h