/*
**  TC-MENU.C
**
**  Turbo C version of James Pinson's Lattice C program PULLDOWN.C
**
**  I edited his program to suit my indentation style, cleaned up the
**  code in a few places, and made adjustments for Turbo C.
**
**  Compile with   tcc -K tc-menu
**  or in TC with O/C/C/Default char type...Unsigned
**  Ignore the three compiler warnings.
**
**  If you get snow on your monitor, type in    tc-menu s    to prevent
**  the program from using direct screen writes.
**
**    -- Scott Houck
*/

#include <stdio.h>
#include <ctype.h>
#include <dos.h>
#include <conio.h>
#include <mem.h>

#define BLACK   0       /* These are for color cards */
#define BLUE    1
#define GREEN   2
#define CYAN    3
#define RED     4
#define MAGENTA 5
#define BROWN   6
#define WHITE   7
#define L_BLUE  9       /* Light-blue foreground only */
#define L_GREEN 10      /* Light-green foreground only */
#define YELLOW  14
#define IWHITE  15      /* Intense-white foreground only */

#define UNDERLINE 1     /* These are for monochrome cards */
#define NORMAL    7
#define HI_INTEN  15
#define REVERSE   112

#define TRUE  1
#define FALSE 0

unsigned int page;        /* extern decl. for functions */
unsigned int attribute;
unsigned int mon_type;
char wrt_meth = 'f';

#define  NU_MAIN 5        /* number of main menu options */
#define  NU_SUB  5        /* number of sub menu options  */

int menu();
int pull_down();
void make_help();
void make_inst();
void mono_help();
void demo();
void help();
void gotoxy();
int wherexy();
void d_page();
void win_save();
int set_color();
void clear_window();
void box();
int what_mon();
void cls();
void make_window();
int cursor();
void print();
void dos_prt();
void fast_write();
int get_key();


struct menu_str           /* change this if you need more options */
{
   char *head;
   char *body[NU_SUB];
   void (*fun1)();
   void (*fun2)();
   void (*fun3)();
   void (*fun4)();
   void (*fun5)();
};


/*-----------*/
/*  M A I N  */
/*-----------*/

main(argc, argv)
int argc;
char **argv;
{
   extern unsigned int page;
   extern unsigned int attribute;
   extern unsigned int mon_type;

   int hi_attr, nor_attr;

   static struct menu_str m_menu[NU_MAIN] =
   {
      "   File   ",     /*  The first menu option */
      "   Dir      ",   /*  Menu sub options */
      "   Load     ",
      "   Save     ",
      "   dElete   ",
      "   Path     ",
      demo,             /* The functions each sub-option call */
      demo,
      demo,             /* these all call the same fake function */
      demo,
      demo,

      "   fiNd   ",     /* The second menu option */
      " All-words  ",
      " First-word ",
      "\0",
      "\0",             /* space filler for unused option names */
      "\0",
      demo,
      demo,
      0,                /* unused function pointer */
      0,
      0,

      "   Configure   ",    /* The third option */
      " Modem ",
      " Screen ",
      " Printer ",
      "\0",
      "\0",
      demo,
      demo,
      demo,
      0,
      0,

      "        Output       ",    /* The fourth menu option */
      "  Screen  ",
      "  Printer ",
      "  Disk    ",
      "  Modem   ",
      "\0",
      demo,
      demo,
      demo,
      demo,
      0,


      " Help ",                       /* The fifth option */
      " Instant help (really works) ",
      "\0",
      "\0",
      "\0",
      "\0",
      help,
      0,
      0,
      0,
      0,
   };


   /* was a slow write requested? */
   if (tolower(*argv[1]) == 's')
      wrt_meth = 's';

   page = 0;
   mon_type = what_mon();

   if (mon_type == 1)             /* Find out if you have a color card */
      {
      hi_attr = set_color(BLACK, CYAN); /* and set attributes accordingly */
      nor_attr = set_color(WHITE, BLACK);
      }
   else
      {
      hi_attr = REVERSE;
      nor_attr = NORMAL;
      }

   attribute = nor_attr;
   cursor(0);               /* hide cursor */
   win_save('s');
   cls();

   if (mon_type == 1)
      make_help();

   make_inst();          /* Show instructions */

   menu(m_menu, NU_MAIN, NU_SUB, hi_attr, nor_attr);

   win_save('r');        /* restore text display*/
   cursor(1);            /* restore cursor      */
}


/*-----------*/
/*  M E N U  */
/*-----------*/

int menu(m_menu, nu_main, nu_sub, hi_attr, nor_attr)
struct menu_str m_menu[];
int nu_main, nu_sub, hi_attr, nor_attr;
{
   extern unsigned int page;
   extern unsigned int attribute;
   extern unsigned int mon_type;

   int i, j, cur_x, cur_y, cur_opt, found, expert = 1;
   char ch, ext, ltr;

   ch = ' ';
   ext = ' ';
   cur_opt = 0;
   found = 0;

   if (mon_type == 1)
      attribute = set_color(YELLOW, BLACK);
   else
      attribute = nor_attr;

   make_window(1, 1, 78, 1, 1);

   for (;;)               /* endless loop */
      {
      for (i = 0; i < nu_main; i++)
         {
         j = 0;
         while (ltr = m_menu[i].head[j++])
            {
            if (ch == ltr && ch != ' ')
               {
               found = TRUE;
               cur_opt = i;
               }
            }
         }
         if (ch == 13)
            {
            found = TRUE;
            expert = FALSE;
            }

         ch = ' ';
         cur_x = 2;
         cur_y = 2;

         for (i = 0; i < nu_main; i++)
            {
            if (i == cur_opt)
               attribute = hi_attr;
            else
               attribute= nor_attr;
            print(cur_x, cur_y, m_menu[i].head);
            cur_x += strlen(m_menu[i].head) + 3;
            }

         if (!expert)
            found = TRUE;

         if (found)
            {
            ext = pull_down(m_menu, nu_sub, cur_opt); /* pull-down options */
            if (ext == 27)
               expert = TRUE;
            if (ext == 'r' || ext == 'l')
               expert = FALSE;
            if (ext == 'r')
               ++cur_opt;
            if (ext == 'l')
               --cur_opt;
            ch = ' ';
            ext = ' ';
            }

            if (!found)
               {
               ch = ' ';
               get_key(&ch, &ext);
               ch = toupper(ch);
               }

            if (ch == 27)
               return;

            if (ext == 'r' || ext == 'l')
               expert = 0;
            if (ext == 'r')
               ++cur_opt;
            if (ext == 'l')
               --cur_opt;
            if (cur_opt >= nu_main)
               cur_opt = 0;
            if (cur_opt < 0)
               cur_opt = nu_main - 1;
            ext = ' ';
            found = 0;

      }  /* end for (;;) */
}



/*---------------------*/
/*  P U L L _ D O W N  */
/*---------------------*/

int pull_down(m_menu, nu_sub, position)
struct menu_str m_menu[];
int position;
{
   extern unsigned int page;
   extern unsigned int attribute;
   char ch = ' ', ltr;
   int ext = ' ', hi_attr, nor_attr;
   int i, j, tx, ty, start, width, nu_opt, cur_opt = 0, found = FALSE;

   nu_opt = nu_sub;

   /* nu_sub = number of possible pull-down options */
   /* find out how many are in use */

   for (i = 0; i < nu_opt; i++)
      {
      if (m_menu[position].body[i][0] == '\0')
         {
         nu_opt = i;
         break;
         }
      }

   if (mon_type == 1)
      {
      hi_attr = set_color(BLACK, CYAN);
      nor_attr = set_color(WHITE, BLACK);
      }
   else
      {
      hi_attr = REVERSE;
      nor_attr = NORMAL;
      }

   attribute = nor_attr;

   start = 2;     /* Figure where to draw pull-down box */
                  /* 2 is column to start 1st box */
                  /* Add up length of menu heads */
        
   for (i = 0; i < position; i++)
      start += strlen(m_menu[i].head) + 3;

   width = 0;     /* figure max length of window */

   for (i = 0; i < nu_opt; i++)
      if (strlen(m_menu[position].body[i]) > width)
         width = strlen(m_menu[position].body[i]);

   /* move box to left if it will spill off right side */

   if (start + width + 1 > 80)
      start = 80 - width - 2;

   win_save('s');

   if (mon_type == 1)
      attribute = set_color(YELLOW, BLACK);

   make_window(start++, 3, width, nu_opt, 0);   /*make a window */
   attribute = nor_attr;

   tx = start;   /* reposition for writing */
   ty = 4;

   for (;;)
      {
      for (i = 0; i < nu_opt; i++)
         {
         if (i == cur_opt)
            attribute = hi_attr;
         else
            attribute = nor_attr;
         print(tx, ty++, m_menu[position].body[i]);
         }

      attribute = nor_attr;

      if (found)
         {
         win_save('r');     /* remove box */

         /* If you want more than 5 menu options */
         /* change this next switch statement    */

         switch(cur_opt)    /* call function */
            {
            case 0: (*m_menu[position].fun1)(); break;
            case 1: (*m_menu[position].fun2)(); break;
            case 2: (*m_menu[position].fun3)(); break;
            case 3: (*m_menu[position].fun4)(); break;
            case 4: (*m_menu[position].fun5)(); break;
            }

         /* found = FALSE; */
         if (kbhit())
            getch();  /* make sure keyboard buffer is clear */
         return(' ');
         }

         tx = start;
         ty = 4;
         get_key(&ch, &ext);  /* get a character */
         ch = toupper(ch);
         if (ext == 'd')
            ++cur_opt;
         if (ext == 'u')
            --cur_opt;
         if (cur_opt >= nu_opt)
            cur_opt = 0;
         if (cur_opt < 0)
            cur_opt = nu_opt - 1;

         if (ch == 13)
            found = TRUE;

         for (i = 0; i < nu_opt; i++)   /* does it match an option? */
            {
            j = 0;
            while (ltr = m_menu[position].body[i][j++])
               {
               if (ch == ltr)
                  {
                  cur_opt = i;
                  found = TRUE;
                  }
               }
            }

         if (ext == 'l'|| ext == 'r')
            break;

         if (ch == 27)   /* EXIT IF ESCAPE KEY */
            {
            ext = ch;
            break;
            }

         ext = ' ';
         ch = ' ';

      }   /* end for (;;) */

   win_save('r');
   return (ext);
}


/*---------------------*/
/*  M A K E _ H E L P  */
/*---------------------*/

void make_help()
{
   extern unsigned int page, attribute;

   page = 1;

   print( 1,  1, "Hello - This is a sample of an instant help screen.");
   print(10,  5, "This screen was printed to the second page of graphics");
   print(10,  7, "while you were looking at the main menu.");
   print(10,  9, "This help screen can be left undisturbed");
   print(10, 11, "and redisplayed at any time.");
   print( 1, 20, "Please touch any key to return to the main menu.");

   page = 0;
}


/*---------------------*/
/*  M A K E _ I N S T  */
/*---------------------*/

void make_inst()
{
   extern unsigned int attribute;

   if (mon_type == 1)
      attribute = set_color(GREEN, BLACK);
   else
      attribute = NORMAL;

   print( 1,  4, "INSTRUCTIONS:");
   print( 1,  6, "EXPERT MODE: Select by touching the key which represents each option.");
   print(15,  7, "(the capital letter)");

   print( 1, 10, "ASSIST MODE: Pull-down menu by touching 'enter' or a cursor key.");
   print(14, 11, "Select by highlighting with cursor keys- then touch return");
   print(14, 13, "Return to Expert mode by touching 'escape'");

   print( 1, 15, "EXIT:        Touch 'Escape' while in expert mode.");
}


/*---------------------*/
/*  M O N O _ H E L P  */
/*---------------------*/

void mono_help()
{
   attribute = NORMAL;
   win_save('s');

   clear_window(1, 4, 80, 21);
   print(1,  7, "This is a demonstration of a help screen.");
   print(1,  9, "This text was written by means of direct memory address.");
   print(1, 10, "The original screen has been saved and will be restored ");
   print(1, 11, "when you exit this 'help' screen.");
   print(1, 14, "Please touch any key to continue.");

   getch();
   win_save('r');
}


/*-----------*/
/*  D E M O  */
/*-----------*/

void demo()
{
   win_save('s');
   make_window(20, 10, 40, 5, 1);

   print(21, 11, "Put your favorite routine here ");
   print(21, 14, "touch any key to return to menu");
   getch();

   win_save('r');
}


/*-----------*/
/*  H E L P  */
/*-----------*/

void help()
{
   if (mon_type == 1)     /* If color card flip page to */
      {
      page = 1;           /* Show text else write to current screen */
      d_page();
      getch();
      page = 0;
      d_page();
      }
   else
      mono_help();
}


         /* Screen Function Library */

/*
**  Declare the extern variables page, attribute, and 
**  mon_type (monitor type) in your main program.
*/


/*---------------*/
/*  G O T O X Y  */
/*---------------*/

void gotoxy(x, y)    /* Puts cursor at x,y position on selected page */
unsigned int x, y;   /* 1,1 is upper left corner */
{
   extern unsigned int page;
   union REGS regist;

   if (x < 1 || x > 80)
      return;
   if (y < 1 || y > 25)
      return;
   --x;
   --y;                  /* BIOS starts coordinates at 0,0 */

   regist.h.ah = 0x02;
   regist.x.dx = (y << 8) | x;
   regist.h.bh = page;
   int86(0x10, &regist, &regist);
}


/*-----------------*/
/*  W H E R E X Y  */
/*-----------------*/

int wherexy(x, y)  /* Returns the x,y position of cursor */
int *x, *y;
{
   extern unsigned int page;
   union REGS regist;

   regist.h.ah = 0x03;
   regist.h.bh = page;
   int86(0x10, &regist, &regist);
   *x = regist.h.dl + 1;
   *y = regist.h.dh + 1;
}


/*---------------*/
/*  D _ P A G E  */
/*---------------*/

/* Displays the page indicated by extern var page */
/* Use only with color card */

void d_page()
{
   extern unsigned int page;
   union REGS regist;

   regist.h.ah = 0x05;
   regist.h.al = page;
   int86(0x10, &regist,&regist);
}


/*-------------------*/
/*  W I N _ S A V E  */
/*-------------------*/

void win_save(action)               /* Saves or restores primary  */
int action;                         /* display screen.            */
{                                   /* (page 0 for color display) */
   extern unsigned int page;        /*   's' = save               */
   extern unsigned int mon_type;    /*   'r' = restore            */
   int position;                    /* Saves cursor position too  */
   static int ptr;                  /* May make snow on CGA       */

   static struct
   {
      int x;
      int y;
      unsigned int buffer[4000];
   }
      window[2];


   if (mon_type == 1)
      position = 0xB800;  /* Color card */
   else
      position = 0xB000;  /* Monochrome */

   if (action == 's')     /* save */
      {
      if (ptr > 1)
         {
         ptr = 2;
         return;
         }

      /*
      **  Note:  I used movedata because the peek function is specific
      **         to Lattice C.
      **
      **  peek(position, 0x00, &window[ptr].buffer, 4000);
      **
      **  Also see poke below.
      */

      /* save screen */
      movedata(position, 0, _DS, (int)&window[ptr].buffer, 4000);

      /* save cursor location */
      wherexy(&window[ptr].x, &window[ptr].y);
      ptr++;
      }

   if (action == 'r')   /* restore */
      {
      if (ptr < 1)
         {
         ptr = 0;
         return;
         }
      --ptr;

      /* poke(position, 0, &window[ptr].buffer, 4000); */

      /* restore screen */
      movedata(_DS, (int)&window[ptr].buffer, position, 0, 4000);

      /* restore cursor location */
      gotoxy(window[ptr].x, window[ptr].y);
      }
}



/*---------------------*/
/*  S E T _ C O L O R  */
/*---------------------*/

/* Call with foreground and background colors.  Returns attribute. */

int set_color(foreground, background)
int foreground,background;
{
   return (background << 4 | foreground);
}



/*---------------------------*/
/*  C L E A R _ W I N D O W  */
/*---------------------------*/

void clear_window(x, y, width, height)  /* Call with x,y of upper left   */
unsigned int x, y, width, height;       /* corner of window area.        */
{                                       /* Clears down and to right      */
   extern unsigned int page;            /* for width and height.         */
   extern unsigned int attribute;       /* Cleared with active attribute */
   union REGS regist;                   /* Use on displayed page only!   */

   regist.x.ax = 0x0600;
   regist.h.ch = --y;
   regist.h.cl = --x;
   regist.x.dx = (y + height - 1) << 8 | x + width - 1;
   regist.h.bh = attribute;
   int86(0x10, &regist, &regist);
}


/*---------*/
/*  B O X  */
/*---------*/

void box(x, y, width, height, type)    /* type 0 = pull-down box */
int x, y, width, height, type;         /* type 1 = regular box   */
{
   int i, u_right, u_left;
   char string[82];

   if (type == 0)           /* following sets corners */
      {
      u_left  = '\xC2';
      u_right = '\xC2';
      }
   else if (type == 1)
      {
      u_left  = '\xDA';
      u_right = '\xBF';
      }

   string[0] = u_left;
   for (i = 1; i <= width; string[i++] = '\xC4');
   string[i++] = u_right;
   string[i] = '\0';
   print(x, y++, string);

   for (i = 0; i < height; i++)
      {
      print(x, y, "\xB3");
      print(x + width + 1, y++, "\xB3");
      }

   string[0]='\xC0';
   for(i = 1; i <= width; string[i++] = '\xC4');
   string[i++] = '\xD9';
   string[i] = '\0';
   print(x, y++, string);
}


/*-------------------*/
/*  W H A T _ M O N  */
/*-------------------*/

int what_mon()  /* Returns a 1 if color card present, 0 if monochrome */
{
   return (peekb(0x0040, 0x0049) != 7);
}


/*---------*/
/*  C L S  */
/*---------*/

void cls()  /* Same as DOS cls */
{
   clear_window(1, 1, 80, 25);
   gotoxy(1, 1);
}


/*-------------------------*/
/*  M A K E _ W I N D O W  */
/*-------------------------*/

void make_window(x, y, width, height, type)  /* Draws and clears a box */
unsigned int x, y, width, height, type;
{
   box(x++, y++, width, height, type);    /* Draw box */
   clear_window(x, y, width, height);     /* Clear interior */
}


/*---------------*/
/*  C U R S O R  */
/*---------------*/

int cursor(size)    /* Sets cursor size */
int size;           /* 0 = no cursor, 1 = normal, 2 = big cursor */
{
   union REGS regist;

   regist.h.ah = 0x01;

   if (mon_type == 1 )                /* color */
      {
      if (size == 0)
         regist.x.cx = 0x0F0F;
      else if (size == 1)
         regist.x.cx = 0x0607;
      else if (size == 2)
         regist.x.cx = 0x0107;
      }
   else                               /* monochrome */
      {
      if (size == 0)
         regist.x.cx = 0x0F0F;
      else if (size == 1)
         regist.x.cx = 0x0C0D;
      else if (size == 2)
         regist.x.cx = 0x010D;
      }

   int86(0x10, &regist, &regist);
}


/*-------------*/
/*  P R I N T  */
/*-------------*/

void print(x, y, str)        /* A switcher - routes to fast_write */
unsigned int x, y;           /* or to dos_prt depending on argv   */
char *str;                   /* passed to program and stored in   */
{                            /* extern char wr_meth               */
   extern char wrt_meth;

   if (wrt_meth == 'f')        /* Fast (direct poking) */
      fast_write(x, y, str);
   else if (wrt_meth == 's')   /* Slow (DOS method) */
      {
      gotoxy(x, y);
      dos_prt(str);
      }
}

                            
/*-----------------*/    /*  Asks DOS to write a string with attribute    */
/*  D O S _ P R T  */    /*  defined.  an alternative to fast write in    */
/*-----------------*/    /*  that it is "well behaved" (goes through DOS) */
                         /*  Specify page and set cursor position before  */
void dos_prt(str)        /*  calling */
char *str;                  
{
   extern unsigned int page, attribute;
   unsigned int x, y;
   union REGS regist;

   wherexy(&x, &y);
   while (*str)
      {
      if (x > 80)
         {
         x = 1;
         ++y;
         }
      if (y > 25)
         break;
      gotoxy(x++, y);
      regist.x.bx = (page << 8 | attribute);
      regist.x.cx = 1;
      regist.x.ax = 0x0900 | *str++;
      int86(0x10, &regist, &regist);
      }
   gotoxy(x, y);     /* put cursor at end of string */
}


/*-----------------------*/
/*  F A S T _ W R I T E  */
/*-----------------------*/

void fast_write(x, y, string)    /* Directly pokes string at x,y position */
int x, y;                        /* Uses attribute and page.              */
char *string;                    /* May cause snow on some graphic cards  */
{
   extern unsigned int page;
   extern unsigned int attribute;
   extern unsigned int mon_type;   /* Monitor type */
   int position, offset, orig;

   if (page <= 3 && page >= 0)
      offset = 4000 * page + 96 * page;
   orig = offset;
   offset += (y-1) * 160 + 2 * (x-1);
   if (mon_type == 0)
      position = 0xB000;
   else
      position = 0xB800;

   while (*string)
      {
      pokeb(position, offset, *string++);          /* Poke character */
      pokeb(position, offset+1, (char)attribute);  /* Poke attribute */
      offset += 2;
      }
   offset -= orig;                /* Figure where I am */
   x = (offset % 160) / 2 + 1;
   y = offset / 160 + 1;          /* and move cursor   */
   gotoxy(x, y);
}


/*-----------------*/
/*  G E T _ K E Y  */
/*-----------------*/

int get_key(ch, ext)          /* Read a char             */
char *ch;                     /* Return character in ch  */
int *ext;                     /* If it is a function key */
{                             /* return following in ext */
   *ch = getch();             /*   up-arrow    = 'u'     */
   if (!*ch)                  /*   down-arrow  = 'd'     */
      {                       /*   right-arrow = 'r'     */
      *ext = getch();         /*   left-arrow  = 'l'     */
      switch (*ext)
         {
         case 'H': *ext = 'u'; break;  /*  up      */
         case 'P': *ext = 'd'; break;  /*  down    */
         case 'M': *ext = 'r'; break;  /*  right   */
         case 'K': *ext = 'l'; break;  /*  left    */
         case 'G': *ext = 'h'; break;  /*  home    */
         case 'O': *ext = 'e'; break;  /*  end     */
         case 'R': *ext = 'I'; break;  /*  insert  */
         case 'S': *ext = 'D'; break;  /*  delete  */
         }
      }
}
