/*
 *    (c)Copyright 1992-1997 Obvious Implementations Corp.  Redistribution and
 *    use is allowed under the terms of the DICE-LICENSE FILE,
 *    DICE-LICENSE.TXT.
 */
#include "vmake.h"

Prototype int build_command(char *buf, int len, char *template, char *sel);
Prototype void do_command(char *string);
Prototype int save_current(int perm);
Prototype void reset_options(void);
Prototype int set_option(struct G_OBJECT *object,char **argv);
Prototype void exec_command(char *cmd, char *sel);
Prototype void set_workdir(void);

void unload_symbols(void);
void load_symbols(void);
void add_list(struct G_LIST *list, char *name, int private);
void add_listitem(struct G_LIST *list, char *name, int private, int dirty);

#define MAX_SYMBOL 31
#define LINE_LEN  78
#define MIN_LINE  10
#define SYM_PROJCFG "CFGNAME"

static int showcmd;

/***********************************************************************************
 * Procedure: get_cursel
 * Synopsis:  name = get_cursel();
 * Purpose:   Return the name associated with the current selection
 ***********************************************************************************/
static char *get_cursel(void)
{
   struct G_OBJECT *object;
#define object_list  ((struct G_LIST   *)object)

   /* We need to determine the currently selected file */
   for(object = global.objects; object != NULL; object = object->next)
   {
      if ((object->class == CLASS_LIST) && object_list->sel)
         return(object_list->sel->buf);
#undef object_list
   }
   return("");
}

/*****************************************************************************
 * Procedure: GetPubScrName
 * Synopsis:  bool = GetPubScrName(scr, buff)
 * Purpose:   fill the name of public a screen int a buffer, return TRUE
 *            if found
 *****************************************************************************/
int GetPubScrName( struct Screen * scr, UBYTE * namebuff )
{
   struct List		* scrlist;
   struct Node		* node;

   namebuff[0] = 0;

   if (AslBase) /* need Dos 2.0 or higher for this trick */
      if(scrlist = LockPubScreenList()) 
      {
         /* traverse the public screen node list looking for our screen */
         for ( node = scrlist->lh_Head; node->ln_Succ; node = node->ln_Succ ) 
         {
            if ( ((struct PubScreenNode *)node)->psn_Screen == scr ) 
            {
               strcpy( namebuff, node->ln_Name );
               break;
            }
         }

         UnlockPubScreenList();
      }

   return( namebuff[0] ? TRUE : FALSE );

} // GetPubScrName()

/***********************************************************************************
 * Procedure: build_command
 * Synopsis:  rc = build_command(buf, len, template)
 * Purpose:   Construct a command to be issued
 *            The template may have % strings in it where we will substitute:
 *               %s   - Current selection
 *               %f   - The current DMAKEFILE
 *               %o   - An optional selection
 *               %t   - The object module version of a file
 *               %r   - Relative pathname of file
 *               %(x) - The expansion of symbol X
 *               %%   - insert one literal % in the buffer
 ***********************************************************************************/
int build_command(char *buf, int len, char *template, char *sel)
{
   char *name;
   int nlen;
   int makeobj;
   /* note: MAX_FILENAME is greater than MAXPBUBSCREENNAME */
   char exname[MAX_FILENAME+1];

   while(*template && len)
   {
      if (*template != '%')
      {
         *buf++ = *template++;
         len--;
      }
      else
      {
         int cvalid = 1; /* Assume valid substitution */
         char req;
         makeobj = 0;
         req = toupper(template[1]);
         template += 2; /* skip over %x */
         name = sel;
         switch(req)
         {
            case 'F':
               {
                  BPTR savelock = 0;
                  if (global.homedir)
                     savelock = CurrentDir(global.homedir);
                  expand_filename(Sym_Lookup(SYM_SCRIPT), exname);
                  name = exname;
                  if (savelock)
                     CurrentDir(savelock);
                  break;
               }
            case '(':
               {
                  char symbol[MAX_SYMBOL+1];
                  int i;
                  i = 0;
                  for(i = 0; *template && (*template != ')'); template++)
                     if (i < MAX_SYMBOL) symbol[i++] = *template;
                  symbol[i] = 0;
                  name = Sym_Lookup(symbol);
                  if (*template == ')') template++;
                  if (!*name)
                  {
                     i = atoi(symbol);
                     if ((i > 0) && (i <= NUM_CONFIG))
                     {
                        name = global.text[CONFIG_BASE-1+i];
                        if (name == NULL) name = "";
                        else
                        {
                           int rc;

                           /* We want to recurse to parse out any substitutions     */
                           /* in the string.  Note that in order to prevent runaway */
                           /* recursion, we temporarily kill the config string      */
                           global.text[CONFIG_BASE-1+i] = "";
                           rc = build_command(buf, len, name, sel);
                           global.text[CONFIG_BASE-1+i] = name;

                           /* if something went wrong on the recursion, just get    */
                           /* out of here.                                          */
                           if (rc) return(i);

                           /* This is a little tricky, what we are doing is cheating*/
                           /* by letting it fill in the buffer with what is already */
                           /* there.  This is harmless (the copy is onto itself) and*/
                           /* allows us to get the length checking done for free    */
                           name = buf;
                        }
                     }
                     else
                     {
                        int max, istart;
                        switch (toupper(*symbol))
                        {
                        case 'T':
                           max = NUM_TEXT;
                           istart = 0;
                           break;
                        case 'C':
                           max = NUM_CONFIG;
                           istart = CONFIG_BASE;
                           break;
                        case 'S':
                           max = NUM_SUBRTN;
                           istart = SUBRTN_BASE;
                           break;
                        default:
                           max = 0;
                           break;
                        }
                        i = atoi(symbol+1);
                        if ((i > 0) && (i <= max))
                        {
                           name = global.text[istart+i-1];
                        }
                     }
                  }
               }
               break;

            case 'T':
               {
                  char *p;
                  if (!*name)
                  {
                     p = Sym_Lookup("EXEDIR");
                     name = Sym_Lookup("PROJECT");
                  }
                  else
                  {
                     p = Sym_Lookup("OD");
                     makeobj = 1;
                  }

                  /* we need to first copy over the output directory */
                  nlen = strlen(p);
                  if (nlen > len)
                  {
                     request(1, TEXT_BUFFOVFL, NULL, NULL);
                     return(1);
                  }
                  strcpy(buf, p);
                  buf += nlen;
                  len -= nlen;
               }
               break;

            case 'N':
               if (global.screen && GetPubScrName(global.screen, exname))
                  name = exname;
               else
                  name = "Workbench";
               break;

            case 'R':
            case 'S':
               /* If nothing is selected, and they required a selection, let  */
               /* them know and abort the command                             */
               if (!*sel)
               {
                  request(1, TEXT_NOSEL, NULL, NULL);
                  return(1);
               }

               /* Expand pathname for 'S', not for 'R' */
               if (req == 'R') break;
               /* Else fall through to prepend the path to the name */

            case 'O':
               expand_filename(sel, exname);
               name = exname;
               break;
            case '%':
               /* interpret "%%" as just "%" and stuff it into the command    */
               /* buffer.  Fall through to default processing for literals.   */
               template += 1;
            default:
               cvalid = 0; /* just copy literally, not a substitution */
         }
         if (cvalid) /* Means we're doing a substitution */
         {
            /* name is what we want to fill the current buffer with */
            nlen = strlen(name);
            if (nlen > len)
            {
               request(1, TEXT_BUFFOVFL, NULL, NULL);
               return(1);
            }
            strcpy(buf, name);
            if (makeobj)
            {
               /* If they requested a output file, we need to convert the */
               /* file to an object file name                             */
               if (nlen > 2 && (buf[nlen-2] == '.'))
                  buf[nlen-1] = 'o';
            }
            buf += nlen;
            len -= nlen;
         }
         else /* Just copy over literally */
         {
            /* just do the '%' here, main loop checks length as well */
            *buf++ = '%';
            template -= 1;  /* back up to char after '%' */
            len--;
         }
      }
   }
   *buf++ = 0;  /* Don't forget to null terminate the result */
   return(0);
}

/***********************************************************************************
 * Procedure: parse_keyword
 * Synopsis:  ptr = parse_keyword(str, buf);
 * Purpose:   Parse a keyword into the buffer
 ***********************************************************************************/
char *parse_keyword(char *str, char *buf)
{
   int i;
   /* Gather the string into a buffer */
   i = 0;
   while(*str == ' ') str++;

   while((*str) && (*str != ' '))
   {
      buf[i++] = *str++;
      if (i > MAX_SYMBOL) return(str);
   }
   buf[i] = 0;
   while(*str == ' ') str++;
   return(str);
}

/***********************************************************************************
 * Procedure: exec_command
 * Synopsis:  exec_command(cmd, selection)
 * Purpose:   Execute the given command using appropriate substitutions
 ***********************************************************************************/
void exec_command(char *cmd, char *sel)
{

   go_dir(global.workdir);

   if (!build_command(global.cbuf, global.cbufsize, cmd, sel))
   {
      if (showcmd)
         PostLog(global.cbuf);
      IssueCommand(global.cbuf);
   }
}

/***********************************************************************************
 * Procedure: send_rexx
 * Synopsis:  send_rexx(cmd)
 * Purpose:   Send the rexx command to the given port name
 ***********************************************************************************/
void send_rexx(char *cmd)
{
   char *res;
   long ec;
   char port[MAX_SYMBOL+1];

   go_dir(global.workdir);

   if (!build_command(global.cbuf, global.cbufsize, cmd, get_cursel()))
   {
      if (showcmd)
         PostLog(global.cbuf);
      /* Parse out the port name */
      cmd = parse_keyword(global.cbuf, port);
      /* Send the command off to rexx to be processed */
      /* We will wait here until it is complete       */
      PlaceRexxCommandDirect(NULL, port, cmd, &res, &ec);
      if (res != NULL)
      {
         PostLog(res);
         free(res);
      }
   }

}

/***********************************************************************************
 * Procedure: log_message
 * Synopsis:  log_message(msg, dorexx)
 * Purpose:   Output a message to the session log
 ***********************************************************************************/
void log_message(char *msg, int dorexx)
{
   int gotmsg;

   /* set RC in case build_command() fails and doesn't say why */
   if (dorexx)
      global.rexxrc = TEXT_BADPARM;

   gotmsg = !build_command(global.cbuf, global.cbufsize, msg, get_cursel());
   if (gotmsg)
      PostLog(global.cbuf);
   else
      PostLog("-------------------");

   if (dorexx)
   {
      if (gotmsg)
      {
         /* undo previous default -- build_command() worked */
         global.rexxrc = 0;
         strcpy(global.rexxrs, global.cbuf);
         global.rexxrs[global.cbufsize] = 0;
      }
   }
}

/***********************************************************************************
 * Procedure: set_workdir
 * Synopsis:  set_workdir();
 * Purpose:   Set the working directory based on the current directory and symbol
 *            values
 ***********************************************************************************/
void set_workdir(void)
{
   if (test_dirty())
   {
      load_symbols();
   }

   go_dir(global.homedir);
   UnLock(global.workdir);
   global.workdir = xlockdir(Sym_Lookup("DIR"));
   go_dir(global.workdir);
}

/***********************************************************************************
 * Procedure: save_current
 * Synopsis:  rc = save_current();
 * Purpose:   Save the current file (if it has changed)
 ***********************************************************************************/
int save_current(int perm)
{
   if (test_dirty())
   {
      int rc;

      rc = request(0, TEXT_ASKSAVE,
                   Sym_Lookup(SYM_SCRIPT), global.text[TEXT_SAVE]);

      if (!rc) return(1);

      if (rc == 2)
      {
         load_symbols();
         if (write_file()) return(1);
      }
      mark_clean();
   }
   return(0);
}

/***********************************************************************************
 * Procedure: lookup_keyword
 * Synopsis:  kw = lookup_keyword(&p, keystr);
 * Purpose:   Lookup the keyword given and return the position of the next token
 *            (If the keyword was found).  If the keyword wasn't found, -1 is returned
 ***********************************************************************************/
int lookup_keyword(char **bufp, char *keystr)
{
   int i;
   char *p;
   char buf[MAX_SYMBOL+1];

   p = parse_keyword(*bufp, buf);

   /* Look for the keyword in the table they provided us */
   for(i = 0; *keystr; keystr += strlen(keystr)+1, i++)
   {
      if (!stricmp(buf, keystr))
      {
         /* We got it.  Return the end of the string for them */
         *bufp = p;
         return(i);
      }
   }

   /* We didn't find the keyword, so let them know */
   return(-1);
}

char cmdlist[] =
#define KEY_TRACE     0
           "TRACE\0" /* Debug the program in the current mode         */
#define KEY_EDIT      1
           "EDIT\0"  /* Invoke the editor on the current file         */
#define KEY_EXEC      2
           "EXEC\0"  /* Execute the given command                     */
#define KEY_NEW       3
           "NEW\0"   /* RESET, Prompt for a new file name             */
#define KEY_QUIT      4
           "QUIT\0"  /* Exit                                          */
#define KEY_READ      5
           "READ\0"  /* Read a file                                   */
#define KEY_SAVE      6
           "SAVE\0"  /* Save file                                     */
#define KEY_FRONT     7
           "FRONT\0" /* Pop Window to the front                       */
#define KEY_BACK      8
           "BACK\0"  /* Send Window to the back                       */
#define KEY_JUMP      9
           "JUMP\0"  /* Jump window to a given screen                 */
#define KEY_LOG      10
           "LOG\0"   /* Output a log message to the screen            */
#define KEY_SCAN     11
           "SCAN\0"  /* Scan the directory for a file pattern list    */
#define KEY_ADDR     12
           "ADDR\0"  /* Send a rexx command to a given port           */
#define KEY_SET      13
           "SET\0"   /* Set a symbol to a given value                 */
#define KEY_CONFIG   14
           "CONFIG\0"/* Change a config string                        */
#define KEY_CALL     15
           "CALL\0"  /* Execute a config string                       */
#define KEY_SELECT   16
           "SELECT\0"/* Select an element in the file list            */
#define KEY_ADD      17
           "ADD\0"   /* Add an element to the list                    */
#define KEY_DEL      18
           "DEL\0"   /* Delete the current item from the list         */
#define KEY_LTOP     19
           "LTOP\0"  /* Select top item in list                       */
#define KEY_LBOT     20
           "LBOT\0"  /* Select bottom item in list                    */
#define KEY_LUP      21
           "LUP\0"   /* Select next item in list                      */
#define KEY_LDN      22
           "LDN\0"   /* Select previous item in list                  */
#define KEY_RECFG    23
           "RECFG\0" /* Read in a config file                         */
#define KEY_REXXI    24
           "REXXI\0" /* Set REXX mode to interactive                  */
;

/***********************************************************************************
 * Procedure: scan_dir
 * Synopsis:  (void)scan_dir();
 * Purpose:   Scan the working directory for all potential files
 ***********************************************************************************/
void scan_dir(char *pat)
{
   FILE *fp;
   char buf[32];

   if (!global.filelist) return;

   if (!*pat) pat = global.text[CONFIG_FILES];

   exec_command("LIST >RAM:TEMPFILE \"%r\" LFORMAT=\"%%s%%s\"", pat);
   if ((fp = fopen("RAM:TEMPFILE", "r")) != NULL)
   {
      while(fgets(buf, 64, fp) != NULL)
      {
         int len;
         len = strlen(buf) - 1;  /* Get rid of the \n */
         while(len && (buf[len-1] == ' ')) len--;

         buf[len] = 0; /* Make sure we null terminate it */
         add_listitem(global.filelist, buf, 0, 1);
      }
      fclose(fp);
   }
}

/***********************************************************************************
 * Procedure: do_command
 * Synopsis:  do_command(string);
 * Purpose:   Execute the command associated with a given string
 ***********************************************************************************/
void do_command(char *string)
{
   char *p;
   int  cmdnum;
   int  postlog;
   char *callstr;
   int  lclass;

   /* EDIT                - Invoke the editor on the current file         */
   /* EXEC cmd            - Execute the given command                     */
   /* NEW     [ file | ?] - RESET, Prompt for a new file name             */
   /* QUIT                - Exit                                          */
   /* READ    [ file | ?] - Read a file                                   */
   /* RUN                 - Execute program in current mode               */
   /* SAVE    [ file | ?] - Save file                                     */
   /* FRONT               - Pop Window to the front                       */
   /* BACK                - Send Window to the back                       */
   /* JUMP [screen]       - Jump Window to a given screen                 */
   /* LOG  [string]       - Output a long message to the screen           */

   set_workdir();
   set_busy();
   callstr = NULL;
   postlog = 0;

   while (*string)
   {
      int c;

      cmdnum = lookup_keyword(&string, cmdlist);

      /* Skip over the name to the rest of the command */
      p = string;

      /* Locate any trailing semi-colon so we can have multiple commands */
      while(*string && (*string++ != ';'))
         ;
      if ((c = string[-1]) == ';')
         string[-1] = 0;


      switch(cmdnum)
      {
         case KEY_TRACE: /*             - Toggle the trace state */
            showcmd = !showcmd;
            break;
         case KEY_EXEC:  /* cmd         - Execute a given command               */
            InitSession();
            if (save_current(0)) break;
            exec_command(p, get_cursel());
            break;
         case KEY_NEW:   /* [ file | ?] - RESET, Prompt for a new file name     */
            {
               char *ext;
               int  len, elen;
               char buf[MAX_FILENAME+1];

               global.rexxrc = TEXT_BADPROJ; /* unless another error occurs */
               if (save_current(1)) break;
               if (!get_filename(p, buf, FALSE)) break;

               /* Now we need to see if the project already exists and warn them */
               /* that they will be overwriting it.                              */
               {
                  BPTR lock;
                  lock = Lock(buf, ACCESS_READ);
                  if (lock != NULL)
                  {
                     UnLock(lock);
                     if (!request(0, TEXT_ASKKILL, buf, NULL))
                     {
                        break;
                     }
                  }
               }

               /* no errors, no cancellations */
               global.rexxrc = 0;
               commit_filename(buf);
               set_gadgets(0);
               reset_options();
               /* indicate that a new icon will be required                */
               global.oldproject = 0;
               /* really trying to load a project, need to fix up ghosting */
               global.unghost = 1;

               /* If they didn't put an extension on the end of it, we want to     */
               /* automatically do that for them.  We should also seed the default */
               /* project name for them.                                           */
               p = Sym_Lookup(SYM_SCRIPT);
               ext = global.text[CONFIG_EXT];
               elen = strlen(ext);
               len = strlen(p) - elen;

               /* If the name is shorter than the extension OR it doesn't end with */
               /* the extension, then we will put the extension on for them        */
               if ((len < 0) || stricmp(p+len, ext))
               {
                  Sym_Set("PROJECT", p, NULL);
                  Sym_Set(SYM_SCRIPT, NULL, ext);
                  strcpy(global.filename, Sym_Lookup(SYM_SCRIPT));
               }
               else
               {
                  p[len] = 0;
                  Sym_Set("PROJECT", p, NULL);
                  p[len] = *ext;
               }
               Sym_Set("TYPE", "Normal", NULL);
               Sym_Set("CFLAGS", "-R -// -f -2.0 -d1", NULL);

               unload_symbols();
               set_gadgets(1);
            }
            break;
         case KEY_QUIT:  /*             - Exit                                  */
            if (!save_current(1))
               global.done = 1;
            break;
         case KEY_EDIT:  /*             - Invoke the editor on the current file */
            global.rexxrc = TEXT_NOSEL; /* default error for interactive REXX */
            if (save_current(0)) break;
            {
               struct stat stat_buf;
               char *sel;

               /* If the file is read-only, we want to see if we can check it out  */
               p = global.text[CONFIG_CO];
               sel = get_cursel();
               if (sel && (stat(sel, &stat_buf) >= 0))
               {
                  if (!(stat_buf.st_mode & S_IWRITE))
                  {
                     int rc;

                     /* The file exists, but is read-only.  See if they want us */
                     /* to check it out for them to work on                     */
                     rc = request(0, TEXT_ASKCO, sel, global.text[TEXT_CO]);
                     if (rc == 0) break;  /* Don't do the edit */
                     if (rc == 2)
                     {
                        exec_command(global.text[CONFIG_CO], sel);
                     }
                  }
               }
               /* not aborting, can't be any errors... */
               global.rexxrc = 0;
               /* if EDPROJ config string is filled, we can do special      */
               /* handling for project file.                                */
               if ((global.text[CONFIG_EDPROJ] == 0) || 
                   (stricmp(sel, Sym_Lookup(SYM_SCRIPT))))
               {
                  exec_command(global.text[CONFIG_EDIT], sel);
                  break;
               }
               else
               {
                  exec_command(global.text[CONFIG_EDPROJ], sel);
                 if (sel && (stat(sel, &stat_buf) < 0))
                 {
                    /* Don't try to read if it doesn't exist */
                    break;
                 }
               }
            }
            /* Fall through to READ to use modified project file             */
            p = ""; /* READ will default to default to current project       */
         case KEY_READ:  /* [ file | ?] - Read a file                        */
            {
               char *symcfg, *symnew;
               char rbuff[200];
               char buf[MAX_FILENAME+1];
               struct stat stat_buf;

               global.rexxrc = TEXT_BADPROJ; /* just in case... */
               if (save_current(1)) break;
               if (build_command(rbuff, 200, p, get_cursel())) break;

               p = rbuff;
               if (!get_filename(p, buf, FALSE)) break;
               if (*buf && (stat(buf, &stat_buf) < 0))
               {
                  request(1, TEXT_BADFILE, buf, NULL);
                  break;
               }
               commit_filename(buf);
               set_gadgets(0);
               reset_options();
               if (read_file())
               {
                  /* For some reason we couldn't read the file,       */
                  /* just reset all the options to be a null value.   */
                  reset_options();
                  commit_filename("");
               }
               else
               {
                  global.rexxrc = 0;     /* Everything's hunky dory now */
                  /* we're really trying to do it, lock all the gadgets */
                  global.unghost = 1;
               }
               global.oldproject = 1;

               /* now see if we need to change the config file          */
               symcfg = Sym_Lookup(SYM_CONFIG);
               symnew = Sym_Lookup(SYM_PROJCFG);
               /* if the project doesn't specify a config file          */
               if (!symnew || (symnew[0] == '\0'))
                  /* revert to original default                         */
                  symnew = Sym_Lookup(SYM_ORIG_CFG);
               /* if configuration file name has changed                */
               if (stricmp(symcfg, symnew))
               {
                  Sym_Set(SYM_CONFIG, symnew, 0);
                  global.newscreen = 1; /* configuration redefines window */
                  global.parsefail = parse_config(symnew);
                  if (global.parsefail)
                  {
                     global.rexxrc = TEXT_BADPROJ;
                     break;   /* error display in title bar             */
                  }
               }

               unload_symbols();
               /* If we're about to redisplay the window, defer         */
               /* repainting the gadgets until then                     */
               if (!global.newscreen)
                  set_gadgets(1);
               break;
            }
         case KEY_SAVE:  /* [ file | ?] - Save file                     */
            {
               int  changed;
               char *ext;
               int  len, elen;
               char svproj[MAX_FILENAME+1];
               char newproj[MAX_FILENAME+1];

               global.rexxrc = TEXT_BADPROJ; /* in case of unnamed perils */
               /* save project name in case they change it */
               strncpy(svproj, Sym_Lookup(SYM_SCRIPT), MAX_FILENAME);
               svproj[MAX_FILENAME] = '\0';
               if (!get_filename(p, newproj, TRUE)) 
                  break;
               /* Now see if they have chosen a new name that already exists */
               /* and warn them that they will be overwriting it.            */
               changed = stricmp(svproj, newproj);
               if (changed)
               {
                  BPTR lock;

                  lock = Lock(newproj, ACCESS_READ);
                  if (lock != NULL)
                  {
                     UnLock(lock);
                     if (!request(0, TEXT_ASKKILL, newproj, NULL))
                     {
                        break;
                     }
                  }
                  /* indicate that a new icon will be required               */
                  global.oldproject = 0;
               }

               /* looks like they want to go through with this               */
               global.rexxrc = 0;  /* didn't fail, don't lie to rexx         */

               /* If they didn't put an extension on the end of it, we want  */
               /* to automatically do that for them.                         */
               ext = global.text[CONFIG_EXT];
               len = strlen(newproj);
               elen = strlen(ext);

               /* If the name is shorter than the extension OR it doesn't end with */
               /* the extension, then we will put the extension on for them        */
               if ((len < elen) || stricmp(newproj+len-elen, ext))
               {
                  strncat(newproj, ext, MAX_FILENAME-len);
                  newproj[MAX_FILENAME] = 0;
               }
               commit_filename(newproj);

               (void)test_dirty();
               load_symbols();
               if (!write_file())
               {
                  /* JAT's usual negative logic -- means it worked           */
                  if (changed)
                  {
                     /* Select the old project.                              */
                     handle_list(global.filelist, NULL, CLASS_SELECT, svproj, 0);
                     if (global.filelist->sel != NULL) /* we got it */
                     {
                        /* If it was in the list, delete it                  */
                        handle_list(global.filelist, NULL, CLASS_DEL, NULL, 0);
                     }
                     /* Now put the new project at the end or the list       */
                     set_gadgets(0);
                     add_listitem(global.filelist, newproj, 1, 0);
                     set_gadgets(1);
                  }
                  /* Now the current state is saved in the new project file. */
                  mark_clean();
               }

               break;
            }
         case KEY_ADDR:  /* port cmd    - Send an arexx command to a given port */
            InitSession();
            if (save_current(0)) break;
            send_rexx(p);
            break;
         case KEY_FRONT: /*             - Pop Window to the front               */
            WindowToFront(global.window);
            ScreenToFront(global.window->WScreen);
            ActivateWindow(global.window);
            break;
         case KEY_BACK:  /*             - Send Window to the back               */
            WindowToBack(global.window);
            break;
         case KEY_JUMP:  /* [screen]    - Jump Window to a given screen         */
            /* This should only be done for 2.0..... */
            if (AslBase)
            {
               char buf[MAXPUBSCREENNAME+1];
               if (*p == 0)
               {
                  if (NextPubScreen(global.window->WScreen, buf))
                     p = buf;
               }
               if (p)
                  global.screen = LockPubScreen(p);
               else
                  global.screen = 0;
               /* force main event loop to close & reopen the window */
               global.newscreen = 1;
               break;
            }
            request(1, TEXT_BADCMD, "--- JUMP Dos 1.3---", NULL);
            break;
         case KEY_RECFG:
            if (save_current(0) == 0) /* didn't change their mind? */
            {
               char cfname[MAX_FILENAME+1];
               char *cfp = p;

               if (*cfp == 0)
                  if (get_work_filename(cfname, CONFIG_CFGFPAT))
                     cfp = cfname;

               if (*cfp != 0) /* got a name from somewhere */
               {
                  Sym_Set(SYM_CONFIG, cfp, 0);
                  global.newscreen = 1; /* configuration redefines window */
                  set_gadgets(0);
                  load_symbols();
                  global.parsefail = parse_config(cfp);
                  if (!global.parsefail)
                  {
                     unload_symbols();
                     Sym_Set(SYM_PROJCFG, cfp, 0);
                     global.dirtysym = DOSTRUE;
                  }
               }
            }
            break;
         case KEY_LOG:   /* message     - Output message to the log             */
            log_message("\n---------------------------", 0);
            log_message(p, global.rexxmsgs);
            postlog = 1;
            break;
         case KEY_SCAN:  /*             - Scan for a list of files              */
            set_gadgets(0);
            scan_dir(p);
            set_gadgets(1);
            break;
         case KEY_SET:
            {
               char buf[MAX_SYMBOL+1];

               p = parse_keyword(p, buf);
               if (buf[0] == 0)
               {
                  request(1, TEXT_BADPARM, NULL, NULL);
                  break;                  
               }
               Sym_Set(buf, p, NULL);
               if (buf[0] != '_')
                  global.dirtysym = DOSTRUE; /* to flag save_current()  */
            }
            break;
         case KEY_CONFIG:
            {
               char buf[MAX_SYMBOL+1];

               p = parse_keyword(p, buf);

               cmdnum = atoi(buf);
               if ((cmdnum > 0) && (cmdnum <= NUM_CONFIG))
               {
                  if (global.text[CONFIG_BASE-1+cmdnum])
                     free(global.text[CONFIG_BASE-1+cmdnum]);
                  global.text[CONFIG_BASE-1+cmdnum] = strdup(p);
               }
               else
                  request(1, TEXT_BADPARM, NULL, NULL);
            }
            break;
         case KEY_SELECT:
            /* Select the given name. */
            handle_list(global.filelist, NULL, CLASS_SELECT, p, 0);
            if (global.filelist->sel == NULL) /* we didn't select anything */
               if (*p != '\0') /* and we weren't just trying to deselect   */
                  request(1, TEXT_SELFAIL, p, NULL);
            break;
         case KEY_LTOP:
            lclass = CLASS_LTOP;
            goto listmove;
         case KEY_LBOT:
            lclass = CLASS_LBOT;
            goto listmove;
         case KEY_LUP:
            lclass = CLASS_LUP;
            goto listmove;
         case KEY_LDN:
            lclass = CLASS_LDN;
      listmove:
            handle_list(global.filelist, NULL, lclass, p, 0);
            if (global.inrexx) /* don't nag but do rexx error codes        */ 
               if (global.filelist->sel == NULL) /* didn't select anything */
                  request(1, TEXT_SELFAIL, p, NULL);
            break;
         case KEY_ADD:
            /* add an item to the file list, can specify name with command */
            handle_list(global.filelist, NULL, CLASS_ADD, p, 0);
            break;
         case KEY_DEL:
            /* Delete the current selection from the file list             */
            if (global.inrexx) /* error msg helpful in rexx, else irritating */
            { 
               if (global.filelist->sel == NULL) /* can't delete nothing   */
               {
                  request(1, TEXT_NOSEL, NULL, NULL);
                  break; /* so don't call delete routine                   */
               }
               if (*p != '\0') /* DEl doesn't take parameters              */
               {
                  request(1, TEXT_BADPARM, NULL, NULL);
                  break; /* so don't call delete routine                   */
               }
            }
            handle_list(global.filelist, NULL, CLASS_DEL, p, 0);
            break;
         case KEY_REXXI:
            /* set REXX interactive mode on or off                         */
            if (!stricmp(p, "on"))
            {
               /* Interactive, allows requesters */
               global.rexxinter = 1;
               Sym_Set(SYM_REXXINTER, "ON", NULL);
            }
            else
            {
               if (!stricmp(p, "off"))
               {
                  /* set REXX mode to fail instead of displaying requesters */
                  global.rexxinter = 0;
                  Sym_Set(SYM_REXXINTER, "OFF", NULL);
               }
               else 
               {
                  request(1, TEXT_BADRMODE, p, NULL);
               }
            }
            break;
         case KEY_CALL:
            if (callstr == NULL)
            {
               char buf[MAX_SYMBOL+1];

               p = parse_keyword(p, buf);

               cmdnum = atoi(buf);
               if ((cmdnum > 0)           &&
                   (cmdnum <= NUM_SUBRTN) &&
                   ((p = global.text[cmdnum+SUBRTN_BASE-1]) != NULL) &&
                   *p)
               {
                  string[-1] = c;
                  callstr = string;
                  string = p;
                  continue;
               }
               break;
            }
         default:
            /* We have an invalid option, ignore the command and let them know  */
            /* about the problem with the option.                               */
            request(1, TEXT_BADCMD, p, NULL);
            /* We also don't want to process any more commands...               */
            string[-1] = c;
            goto doneall;
      }
      string[-1] = c;
      if ((*string == '\0') && (callstr != NULL))
      {
         string = callstr;
         callstr = NULL;
      }
   }

doneall:
   if (postlog)
   {
      log_message("---------------------------", 0);
      if (!global.inrexx)
         ActivateWindow(global.window);
   }
   set_idle();
}

/***********************************************************************************
 * Procedure: reset_options
 * Synopsis:  reset_options()
 * Purpose:   Reset all the options to the default values
 ***********************************************************************************/
void reset_options(void)
{
#define object_list  ((struct G_LIST *)object)
#define object_str   ((struct G_STRING *)object)
#define object_cycle ((struct G_CYCLE *)object)
   struct G_OBJECT *object;
   char *p;

   /* First make sure that all the right symbols are in the symbol table in the */
   /* correct order.  Since the symbol table routines guarentee that it will    */
   /* keep the order of the entries once they are there, we only need to touch  */
   /* the order once to keep it correct for the dmakefile.                      */
   Sym_Clear();

   p = "PROJECT\0"
       "DIR\0"
       "SRCS\0"
       "HDRS\0"
       "EXTRAS\0"
       "PDEFAULT\0"
       "EXEDIR\0"
       "OD\0"
       "PRECOMP\0"
       "TYPE\0"
       "RUN\0"
       "CLIARGS\0"
       "CFLAGS\0";

   while(*p)
   {
      Sym_Set(p, NULL, "");
      p += strlen(p)+1;
   }

   object = global.objects;

   while(object != NULL)
   {
      switch(object->class)
      {
         case CLASS_STRING:
            object_str->buf[0] = 0;
            break;
         case CLASS_CYCLE:
            {
               struct G_VALUE *val;

               val = object_cycle->curval = object_cycle->values;
               while(val != NULL)
               {
                  if (val->string)
                     val->string->buf[0] = 0;
                  val = val->next;
               }
            }
            break;
         case CLASS_LIST:
            {
               struct G_ENTRY *ent;
               ent = object_list->first;
               object_list->first = object_list->top = NULL;
               object_list->sel = NULL;
               object_list->maxent = 0;

               while(ent != NULL)
               {
                  struct G_ENTRY *nextent;

                  nextent = (struct G_ENTRY *)ent->base.next;
                  free_mem(ent, sizeof(struct G_ENTRY));
                  ent = nextent;
               }
            }
            break;
         case CLASS_BUTTON:
            break;
         default:
            object->state = 0;
            break;
      }
      object = object->next;
   }
#undef object_list
#undef object_str
#undef object_cycle
   mark_clean();

   UnLock(global.workdir);
   global.workdir = 0;
}

/***********************************************************************************
 * Procedure: unload_symbols
 * Synopsis:  unload_symbols();
 * Purpose:   Unload the symbols into the objects
 ***********************************************************************************/
void unload_symbols()
{
   struct G_OBJECT *object, *savelist;
   char *p;

#define object_list  ((struct G_LIST   *)object)
#define object_str   ((struct G_STRING *)object)
#define object_check ((struct G_CHECK  *)object)
#define object_cycle ((struct G_CYCLE  *)object)

   for(object = global.objects; object != NULL; object = object->next)
   {
      switch(object->class)
      {
         case CLASS_STRING:
            strcpy(object_str->buf, Sym_Lookup(object_str->option));
            break;
         case CLASS_CYCLE:
            {
               char buf[MAX_SYMBOL+1];
               struct G_VALUE *val;

               for(val = object_cycle->values;
                   val != NULL;
                   val = (struct G_VALUE *)val->next)
               {
                  char *arg;
                  /* Gather the current name into a buffer */
                  p = parse_keyword(val->option, buf);
                  arg = Sym_Lookup(buf);
                  if (val->string)
                  {
                     /* If the next character is a % sign, we have an exact */
                     /* match that we can just fill in.   Otherwise it is   */
                     /* the name of a string that we should lookup for the  */
                     /* final symbol to fill it in with                     */
                     if (*p != '%')
                     {
                        /* Parse out the next token - this is the secondary */
                        /* Mode.  This must match the value of the primary  */
                        /* option if we are to take it                      */
                        parse_keyword(p, buf);
                        if (strcmp(buf, arg)) continue;  /* Sorry, try the next one */

                        /* We have a match, look up the substitution value  */
                        /* for the string buffer                            */
                        arg = Sym_Lookup(buf);
                     }
                     /* Copy the string into the object cycle buffer */
                     strcpy(val->string->buf, arg);
                     object_cycle->curval = val;
                     break;
                  }
                  else
                  {
                     /* Not a string entry.  It must match exactly */
                     if (!strcmp(p, arg))
                     {
                        object_cycle->curval = val;
                        break;
                     }
                  }
               }
            }
            break;
         case CLASS_LIST:
            if (!stricmp(object_list->option, "Files"))
            {
               savelist = object;
               /* Populate it with all the strings from the list */
               add_list(object_list, Sym_Lookup("SRCS"),      0);
               add_list(object_list, Sym_Lookup("HDRS"),      0);
               add_list(object_list, Sym_Lookup("EXTRAS"),    0);
               add_list(object_list, Sym_Lookup(SYM_SCRIPT),  1);
            }
            else
            {
               add_list(object_list, Sym_Lookup(object_list->option), 0);
            }
            break;
      }
   }

   mark_clean();
#undef object_check
#undef object_list
#undef object_str
#undef object_cycle
}

/***********************************************************************************
 * Procedure: load_symbols
 * Synopsis:  load_symbols();
 * Purpose:   load the symbols from the current objects
 ***********************************************************************************/
void load_symbols()
{
   struct G_OBJECT *object;
   char *p, *arg;

#define object_list  ((struct G_LIST   *)object)
#define object_str   ((struct G_STRING *)object)
#define object_check ((struct G_CHECK  *)object)
#define object_cycle ((struct G_CYCLE  *)object)

   for(object = global.objects; object != NULL; object = object->next)
   {
      /* Don't even bother with this object if it hasn't changed */
      if (!object->state & DIRTY_BIT) continue;

      switch(object->class)
      {
         case CLASS_STRING:
            Sym_Set(object_str->option, object_str->buf, NULL);
            break;

         case CLASS_CYCLE:
            {
               struct G_VALUE *val;
               char buf[MAX_SYMBOL+1];
               char sec[MAX_SYMBOL+1];

               val = object_cycle->curval;
               p = parse_keyword(val->option, buf);

               if (val->string)
               {
                  /* If the next token is a '%', we just use the string buffer */
                  /* as the result value                                       */
                  if (*p == '%')
                  {
                     p = val->string->buf;
                  }
                  else
                  {
                     /* Special case of a cascade definition */
                     /* We need to set the primary definition to the secondary */
                     /* name and the secondary definition to the string        */
                     parse_keyword(p, sec);
                     Sym_Set(sec, val->string->buf, NULL);
                     p = sec;
                  }
               }
               Sym_Set(buf, p, NULL);

               /* Handle the one special case that we know about:  */
               /*   TYPE <whatever> causes us to read in a new script */
               if (!strcmp(buf, "TYPE"))
                  read_script();
            }
            break;

         case CLASS_LIST:
            {
               int firsttime;
               struct G_ENTRY *ent;
               Sym_Set("EXTRAS", "", NULL);
               Sym_Set("SRCS",   "", NULL);
               Sym_Set("HDRS",   "", NULL);
               /* Files                                                                           */
               /*  - List of C and O files.  Always relative to the current directory             */
               /*  - Generates the SRCS and HDRS directories by splitting out based on extension. */
               /*  - Things ending in .C or .A go into SRCS                                       */
               /*  - Things ending in .H go into HDRS                                             */
               /*  - All others go into FILES_ext where ext is the extension of the file          */
               for(firsttime = 1, ent = object_list->first; ent != NULL;
                   ent = (struct G_ENTRY *)ent->base.next, firsttime = 0)
               {
                  arg = "EXTRAS";
                  p = strrchr(ent->buf, '.');
                  if (p != NULL)
                  {
                     if ((!stricmp(p, ".c")) || (!stricmp(p, ".a"))) arg = "SRCS";
                     else if (!stricmp(p, ".h"))
                     {
                        char *t;
                        t = strrchr(ent->buf, '/');
                        if (t == NULL) t = strrchr(ent->buf, ':');
                        if (t == NULL) t = ent->buf;
                        arg = "HDRS";
                        if (p > t)
                        {
                           *p = 0;
                           if (!stricmp(t, Sym_Lookup("PROJECT")))
                           {
                              *p = '.';
                              Sym_Set("PDEFAULT", ent->buf, NULL);
                           }
                           *p = '.';
                        }
                     }
                  }
                  /* Make sure that we don't put the script in there more than */
                  /* once......  It is already present as the DMAKEFILE one    */
                  if (!(ent->base.state & STATE_MASK))
                  {
                     if (stricmp(object_list->option, "Files"))
                        arg = object_list->option;

                     if (*Sym_Lookup(arg)) Sym_Set(arg, NULL, " ");

                     /* If the filename has spaces, we need to put it in quotes */
                     if (strchr(ent->buf, ' '))
                     {
                        Sym_Set(arg, NULL, "\'");
                        Sym_Set(arg, NULL, ent->buf);
                        Sym_Set(arg, NULL, "\'");
                     }
                     else
                     {
                        Sym_Set(arg, NULL, ent->buf);
                     }
                  }
               }
            }
            break;
      }
   }

#undef object_check
#undef object_list
#undef object_str
#undef object_cycle

   /* A couple of symbols MUST be a directory with the appropriate */
   /* Directory separator character on the end of it               */
   /*   DIR                                                        */
   /*   EXEDIR                                                     */
   /*   OD                                                         */
   p = "DIR\0"
       "EXEDIR\0"
       "OD\0";
   for (;*p; p += strlen(p)+1)
   {
      char *val;
      int len;

      val = Sym_Lookup(p);

      len = strlen(val);
      if (len && (val[len-1] != ':') && (val[len-1] != '/'))
         Sym_Set(p, NULL, "/");
   }
}

/***********************************************************************************
 * Procedure: add_list
 * Synopsis:  add_list(list, name)
 * Purpose:   Add all the filenames in the named symbol table into the list object
 ***********************************************************************************/
void add_list(struct G_LIST *list,
              char *name,
              int private)
{
   char buf[MAX_STRING+1];
   int pos, c, state;

   pos = 0;
   state = 0;

   do
   {
      c = *name++;
      if (((state == 1) && (c == '\'')) || (pos && (c == ' ' || c == 0)))
      {
         buf[pos] = 0;
         add_listitem(list, buf, private, 0);
         state = 0;
         pos = 0;
      }
      else
      {
         if (c == '\'')     state = 1;
         else if (state || (c != ' ')) buf[pos++] = c;
         if (pos == MAX_STRING) pos = MAX_STRING-1;
      }
   } while(c);
}

/***********************************************************************************
 * Procedure: add_listitem
 * Synopsis:  add_listitem(list, name, private, dirty)
 * Purpose:   Add the given name into the current list
 ***********************************************************************************/
void add_listitem(struct G_LIST *list, char *name, int private, int dirty)
{
   struct G_ENTRY *ent, *prevent;

   ent = get_mem(sizeof(struct G_ENTRY));
   if (ent == NULL) return;

   strcpy(ent->buf, name);
   ent->base.state = private;
   if (dirty)
      list->base.state |= DIRTY_BIT;

   /* Figure out where to add the name */

   /* link the new name into the list */
   if (list->first == NULL)
   {
      list->top = list->first = ent;
   }
   else
   {
      int i;
      for (i = 0, prevent = list->first; prevent->base.next;
           prevent = (struct G_ENTRY *)prevent->base.next, i++)
      {
         /* Make sure it isn't already in the list */
         if (!stricmp(prevent->buf, name))
         {
            free_mem(ent, sizeof(struct G_ENTRY));
            return;
         }
      }
      prevent->base.next = (struct G_OBJECT *)ent;
      ent->base.prev = (struct G_OBJECT *)prevent;
   }
}