Showing posts with label Menu in C. Show all posts
Showing posts with label Menu in C. Show all posts

Saturday, February 16, 2013

Simple interactive menu

 it turned into something a bit more serious and may be useful as a code snippet.
The code shows three files:
  • menu.h: The header file for the menu library.
  • menu.c: Implementation of the menu library functions.
  • main.c: A sample driver that uses the library.
The menu library is fairly generic in that you can provide a format string of menu options and possible matching input values. See main.c for an example of how to use it.

   /* menu.h */  
   #ifndef MENU_H  
   #define MENU_H  
   #define PROMPT_SIZ 128  
   #define ALT_SIZ 128  
   #define MENU_ALTS 11  
   typedef struct menuitem {  
   char alt[MENU_ALTS][ALT_SIZ];  
   char prompt[PROMPT_SIZ];  
   } menuitem_t;  
   #ifdef __cplusplus  
   extern "C" {  
   #endif  
   extern int menu(menuitem_t **items, char *selection, size_t limit);  
   extern menuitem_t **extract_menu_items(const char *options);  
   extern void destroy_menu_items(menuitem_t **items);  
   #ifdef __cplusplus  
   }  
   #endif  
   #endif  
   /* menu.c */  
   #include <stdio.h>  
   #include <stdlib.h>  
   #include <string.h>  
   #include "menu.h"  
   /*  
   @description:  
   Displays the prompts for a collection of menu items and gathers an interactive selection.  
   @returns: True if a matching selection was detected, false otherwise.  
   */  
   extern int menu(menuitem_t **items, char *selection, size_t limit)  
   {  
   size_t i, j;  
   if (items) {  
   /* Display the prompts */  
   for (i = 0; items[i]; i++)  
   puts(items[i]->prompt);  
   fputs("> ", stdout);  
   fflush(stdout);  
   /* Process an interactive selection */  
   if (fgets(selection, limit, stdin)) {  
   char *newline = strchr(selection, '\n');  
   if (newline)  
   *newline = '\0'; /* Trim the newline for convenience */  
   else {  
   int ch;  
   /* Discard extraneous line characters */  
   do  
   ch = fgetc(stdin);  
   while (ch != '\n' && ch != EOF);  
   clearerr(stdin);  
   }  
   for (i = 0; items[i]; i++) {  
   /* Check all of the alternatives for a matching prefix */  
   for (j = 0; j < MENU_ALTS && items[i]->alt[j][0]; j++) {  
   size_t alt_len = strlen(items[i]->alt[j]);  
   /* If the first part of the string matches an alternative, it's valid */  
   if (strncmp(selection, items[i]->alt[j], alt_len) == 0)  
   return 1;  
   }  
   }  
   }  
   }  
   return 0; /* No matches, end-of-file, or stream error */  
   }  
   /*  
   @description:  
   Parses and extracts a collection of menu items from a formatted options string.  
   The options string shall be in the following format:  
   <prompt1>;<prompt1.match1>|<prompt1.match2>;<prompt2>;<prompt2.match1>|<prompt2.match2>...  
   The prompt may not be an empty field, but the match strings may be empty.  
   Example 1 (prompts and matches): "A) Option A;A|a;B) Option B;B|b;Q) Quit;Q|q"  
   Example 2 (prompts and no matches): "A) Option A;;B) Option B;;Q) Quit;"  
   @remarks:  
   Memory is dynamically allocated for the item collection. destroy_menu_items  
   is provided as a convenience function for releasing this memory.  
   */  
   extern menuitem_t **extract_menu_items(const char *options)  
   {  
   menuitem_t **mem;  
   /* Always allocate at least one spot for a trailing null pointer */  
   mem = (menuitem_t**)malloc(sizeof *mem);  
   if (mem) {  
   char copy[BUFSIZ] = "";  
   size_t args = 0;  
   char *tok;  
   strcat(copy, options);  
   tok = strtok(copy, ";");  
   while (tok) {  
   size_t tok_len = strlen(tok);  
   menuitem_t **temp;  
   mem[args] = (menuitem_t*)malloc(sizeof *mem[args]);  
   if (!mem[args])  
   break;  
   /* Assume the first hit is a prompt */  
   strcpy(mem[args]->prompt, tok);  
   /* Assume the second hit is the alt list */  
   if (tok[tok_len + 1] == ';') {  
   /*  
   Kind of tricky. Introspectively looked ahead because strtok won't,  
   and handle an empty alt list field by setting the first alt to "".  
   */  
   mem[args]->alt[0][0] = '\0';  
   }  
   else {  
   char *alt_tok;  
   size_t n = 0;  
   /* Not an empty alt list, so we can grab the token with strtok */  
   tok = strtok(NULL, ";");  
   tok_len = strlen(tok);  
   /*  
   Now reuse strtok to process the alt list (awkward).  
   */  
   alt_tok = strtok(tok, "|");  
   while (n < MENU_ALTS - 1 && alt_tok) {  
   strcpy(mem[args]->alt[n++], alt_tok);  
   alt_tok = strtok(NULL, "|");  
   }  
   /* Make sure to terminate the alt list with a blank string */  
   mem[args]->alt[n][0] = '\0';  
   }  
   /* Make room for the next menu item (or null pointer if we're done) */  
   temp = (menuitem_t**)realloc(mem, (++args + 1) * sizeof *temp);  
   if (!temp)  
   break;  
   mem = temp;  
   /*  
   Restart strtok at the next option token because we  
   may have used strtok to handle a non-empty alt list.  
   */  
   tok = strtok(tok + tok_len + 1, ";");  
   }  
   mem[args] = NULL;  
   }  
   return mem;  
   }  
   /*  
   @description:  
   Releases memory for a collection of menu items returned by extract_menu_items.  
   */  
   extern void destroy_menu_items(menuitem_t **items)  
   {  
   size_t i;  
   for (i = 0; items[i]; i++)  
   free(items[i]);  
   free(items);  
   }  
   /* main.c (sample usage) */  
   #include <ctype.h>  
   #include <stdio.h>  
   #include "menu.h"  
   int main(void)  
   {  
   menuitem_t **items = extract_menu_items("A) Option A;A|a;B) Option B;B|b;Q) Quit;Q|q");  
   int done = 0;  
   while (!done) {  
   char selection[BUFSIZ];  
   int rc = menu(items, selection, sizeof selection);  
   printf("Full selection: '%s'\n", selection);  
   if (!rc)  
   puts("Invalid selection");  
   else {  
   switch (toupper(selection[0])) {  
   case 'A':  
   puts("You chose option A");  
   break;  
   case 'B':  
   puts("You chose option B");  
   break;  
   case 'Q':  
   puts("You chose to quit");  
   done = 1;  
   break;  
   }  
   }  
   }  
   destroy_menu_items(items);  
   return 0;  
   }