text: /* $Id: syb.c,v 1.13 2016/12/04 15:22:16 tom Exp $ */
  
  #include <cdk_test.h>
  #include <sybfront.h>
  #include <sybdb.h>
  
  #ifdef HAVE_XCURSES
  char *XCursesProgramName="syb";
  #endif
  
  #define MAXWIDTH	5000
  #define MAXHISTORY	1000
  
  /*
   * This structure is used for keeping command history.
   */
  struct history_st {
     unsigned used;
     int count;
     int current;
     char **cmd_history;
  };
  
  /*
   * Define some global variables.
   */
  char *GPUsage		= "[-p Command Prompt] [-U User] [-P Password] [-S Server] [-h help]";
  char *GPCurrentDatabase = 0;
  extern char *dberrstr;
  
  /*
   * Because the error/message callback do not allow you to pass in
   * data of your own, we have to make the screen pointer global. :(
   */
  CDKSCREEN *GPCdkScreen = 0;
  
  /*
   * Define function prototypes.
   */
  DBPROCESS *loginToSybase (CDKSCREEN *screen, char *login, char *password);
  DBPROCESS *sybaseLogin (CDKSCREEN *screen, char *login, char *password, int attempts);
  char *assembleTitle (DBPROCESS *dbProcess);
  char *assembleRow (DBPROCESS *dbProcess);
  char *uc (char *word);
  int getColWidth (DBPROCESS *dbProcess, int col);
  void runIsqlCommand (CDKSCREEN *cdkscreen, CDKSWINDOW *swindow, DBPROCESS *process);
  void useDatabase (CDKSWINDOW *swindow, DBPROCESS *dbProc, char *command);
  void intro (CDKSCREEN *screen);
  void help (CDKENTRY *entry);
  void loadHistory (struct history_st *history);
  void saveHistory (struct history_st *history, int count);
  
  /*
   * Define callback prototypes.
   */
  BINDFN_PROTO(viewHistoryCB);
  BINDFN_PROTO(swindowHelpCB);
  BINDFN_PROTO(historyUpCB);
  BINDFN_PROTO(historyDownCB);
  BINDFN_PROTO(listHistoryCB);
  
  /*
   * Define Sybase error/message callbacks. This is required by DBLib.
   */
  int err_handler (DBPROCESS *dbProcess, DBINT mesgNumber, int mesgState,
  			int severity, char *mesgText);
  int msg_handler (DBPROCESS *dbProcess, DBINT mesgNumber, int mesgState,
  			int severity, char *mesgText);
  
  /*
   * Written by:	Mike Glover
   * Purpose:
   *		This creates a very simple interface to Sybase.
   */
  int main (int argc, char **argv)
  {
     /* Declare variables. */
     CDKSWINDOW *commandOutput	= 0;
     CDKENTRY *commandEntry	= 0;
     DBPROCESS *dbProcess		= 0;
     char *dsquery		= 0;
     char *command		= 0;
     char *prompt			= 0;
     char *upper			= 0;
     char *login			= 0;
     char *password		= 0;
     char *server			= 0;
     int count			= 0;
     int width			= 0;
     int ret			= 0;
     struct history_st history;
     char *mesg[5], temp[1000];
  
     /* Set up the history. */
     GPCurrentDatabase	= copyChar ("master");
     history.used   	= 0;
     history.current	= 0;
     history.count	= 0;
  
     /* Check the command line for options. */
     while (1)
     {
        /* Are there any more command line options to parse. */
        if ((ret = getopt (argc, argv, "p:U:P:S:h")) == -1)
        {
  	 break;
        }
  
        switch (ret)
        {
  	 case 'p':
  	      prompt = copyChar (optarg);
  	      break;
  
  	 case 'U':
  	      login = copyChar (optarg);
  	      break;
  
  	 case 'P':
  	      password = copyChar (optarg);
  	      break;
  
  	 case 'S':
  	      server = copyChar (optarg);
  	      break;
  
  	 case 'h':
  	      printf ("Usage: %s %s\n", argv[0], GPUsage);
  	      ExitProgram (EXIT_SUCCESS);
  	      break;
        }
     }
  
     /* Set up the command prompt. */
     if (prompt == 0)
     {
        if (server == 0)
        {
  	 if ((dsquery = getenv ("DSQUERY")) != 0)
  	 {
  	    sprintf (temp, "</B/24>[%s] Command >", dsquery);
  	    prompt = copyChar (temp);
  	 }
  	 else
  	 {
  	    prompt = copyChar ("</B/24>Command >");
  	 }
        }
        else
        {
  	 sprintf (temp, "</B/24>[%s] Command >", server);
  	 prompt = copyChar (temp);
        }
     }
  
     GPCdkScreen = initCDKScreen (NULL);
  
     /* Start color. */
     initCDKColor();
  
     /* Initialize DB-Library. */
     if (dbinit() == FAIL)
     {
        mesg[0] = "<C></U>Fatal Error";
        mesg[1] = "<C>Could not connect to the Sybase database.";
        popupLabel (GPCdkScreen, mesg, 2);
        ExitProgram (EXIT_FAILURE);
     }
  
     /* Load the history. */
     loadHistory (&history);
  
     /* Create the scrolling window. */
     commandOutput = newCDKSwindow (GPCdkScreen, CENTER, TOP, -8, -2,
  					"<C></B/5>Command Output Window",
  					MAXWIDTH, TRUE, FALSE);
  
     /* Create the entry field. */
     width	= COLS - strlen (prompt) - 1;
     commandEntry = newCDKEntry (GPCdkScreen, CENTER, BOTTOM,
  				0, prompt, A_BOLD|COLOR_PAIR(8),
  				COLOR_PAIR(24)|'_', vMIXED,
  				width, 1, 512, FALSE, FALSE);
  
     /* Create the key bindings. */
     bindCDKObject (vENTRY, commandEntry, KEY_UP, &historyUpCB, &history);
     bindCDKObject (vENTRY, commandEntry, KEY_DOWN, &historyDownCB, &history);
     bindCDKObject (vENTRY, commandEntry, CTRL('^'), &listHistoryCB, &history);
     bindCDKObject (vENTRY, commandEntry, KEY_TAB, &viewHistoryCB, commandOutput);
     bindCDKObject (vSWINDOW, commandOutput, '?', swindowHelpCB, commandEntry);
  
     /* Draw the screen. */
     refreshCDKScreen (GPCdkScreen);
  
     /* Display the introduction window. */
     intro (GPCdkScreen);
  
     /* Make them login first. */
     dbProcess = sybaseLogin (GPCdkScreen, login, password, 3);
     if (dbProcess == 0)
     {
        destroyCDKScreen (GPCdkScreen);
        endCDK ();
        ExitProgram (EXIT_FAILURE);
     }
  
     /* Do this forever. */
     for (;;)
     {
        /* Get the command. */
        command = activateCDKEntry (commandEntry, 0);
  
        /* Strip off leading and trailing white space. */
        stripWhiteSpace (vBOTH, command);
  
        /* Upper case the command. */
        upper = uc (command);
  
        /* Check the output of the command. */
        if (strcmp (upper, "QUIT") == 0 ||
  		strcmp (upper, "EXIT") == 0 ||
  		strcmp (upper, "Q") == 0 ||
  		strcmp (upper, "E") == 0 ||
  		commandEntry->exitType == vESCAPE_HIT)
        {
  	 /* Save the history. */
  	 saveHistory (&history, 100);
  
  	 /* Exit. */
  	 dbclose (dbProcess);
  	 dbexit();
  
  	 /* All done. */
  	 destroyCDKEntry (commandEntry);
  	 destroyCDKSwindow (commandOutput);
  	 freeChar (upper);
  	 endCDK();
  	 ExitProgram (EXIT_SUCCESS);
        }
        else if (strcmp (command, "login") == 0)
        {
  	 DBPROCESS *newLogin = sybaseLogin (GPCdkScreen, 0, 0, 3);
  	 if (newLogin == 0)
  	 {
  	    addCDKSwindow (commandOutput, "Login Error: Could not switch to new user.", BOTTOM);
  	 }
  	 else
  	 {
  	    /* Close the old connection. */
  	    dbclose (dbProcess);
  	    dbProcess = newLogin;
  
  	    /* Add a message to the scrolling window. */
  	    addCDKSwindow (commandOutput,
  				"Logged into database as new user.",
  				BOTTOM);
  	    count = 0;
  	 }
        }
        else if (strcmp (command, "logout") == 0)
        {
  	 /* Close the old connection. */
  	 dbclose (dbProcess);
  	 dbProcess = 0;
  
  	 /* Add a message to the scrolling window. */
  	 addCDKSwindow (commandOutput, "Logged out.", BOTTOM);
  	 count = 0;
        }
        else if (strcmp (command, "clear") == 0)
        {
  	 /* Clear the scrolling window. */
  	 cleanCDKSwindow (commandOutput);
        }
        else if (strcmp (command, "history") == 0)
        {
  	 listHistoryCB (vENTRY, (void *)commandEntry, (void *)&history, 0);
        }
        else if (strcmp (command, "tables") == 0)
        {
  	 /* Check if we are logged in. */
  	 if (dbProcess == 0)
  	 {
  	    addCDKSwindow (commandOutput, "You must login first.", BOTTOM);
  	 }
  	 else
  	 {
  	    sprintf (command, "select * from sysobjects where type = 'U'");
  
  	    /* Put the command into the ISQL buffer. */
  	    dbcmd (dbProcess, command);
  
  	    /* Put the command into the scrolling window. */
  	    sprintf (temp, "</R>%d><!R> %s", count+1, command);
  	    addCDKSwindow (commandOutput, temp, BOTTOM);
  
  	    /* Increment the counter. */
  	    count++;
  	 }
        }
        else if (strcmp (command, "help") == 0)
        {
  	 /* Display the help. */
  	 help(commandEntry);
        }
        else if (command[0] == 'u' && command[1] == 's' &&
  		command[2] == 'e' && command[3] == ' ')
        {
  	 /* They want to use a database. */
  	 useDatabase (commandOutput, dbProcess, command);
  	 count = 0;
        }
        else if (strcmp (command, "go") == 0)
        {
  	 /* Check if we are logged in. */
  	 if (dbProcess == 0)
  	 {
  	    addCDKSwindow (commandOutput, "You must login first.", BOTTOM);
  	 }
  	 else
  	 {
  	    /* Put the command into the scrolling window. */
  	    sprintf (temp, "</R>%d><!R> %s", count+1, command);
  	    addCDKSwindow (commandOutput, temp, BOTTOM);
  	    count = 0;
  
  	    /* Run the command. */
  	    runIsqlCommand (GPCdkScreen, commandOutput, dbProcess);
  	 }
        }
        else
        {
  	 /* Check if we are logged in. */
  	 if (dbProcess == 0)
  	 {
  	    addCDKSwindow (commandOutput, "You must login first.", BOTTOM);
  	 }
  	 else
  	 {
  	    /* Put the command into the ISQL buffer. */
  	    dbcmd (dbProcess, command);
  
  	    /* Put the command into the scrolling window. */
  	    sprintf (temp, "</R>%d><!R> %s", count+1, command);
  	    addCDKSwindow (commandOutput, temp, BOTTOM);
  
  	    /* Increment the counter. */
  	    count++;
  	 }
        }
  
        /* Keep the history. */
        history.used = CDKallocStrings(&(history.cmd_history), command, history.count++, history.used);
        history.current = history.count;
  
        /* Clear the entry field. */
        cleanCDKEntry (commandEntry);
  
        /* Free up the memory used by the upper pointer. */
        freeChar (upper);
     }
  }
  
  /*
   * This lets a person 'use' a database.
   */
  void useDatabase (CDKSWINDOW *swindow, DBPROCESS *dbProc, char *command)
  {
     char *database = 0;
     char temp[256];
     char **words;
     int wordCount, x;
  
     /* Split the command line up and get the database name. */
     words = CDKsplitString (command, ' ');
     wordCount = CDKcountStrings (words);
  
     /* Look for the name. */
     for (x=1; x < wordCount; x++)
     {
        if (strlen (words[x]) != 0)
        {
  	 database = copyChar (words[x]);
        }
     }
     CDKfreeStrings(words);
  
     /* Try to actually use the database. */
     if (dbuse(dbProc, database) == FAIL)
     {
        /* We aren't allowed to use that database. */
        sprintf (temp, "Command: %s", command);
        addCDKSwindow (swindow, temp, BOTTOM);
        addCDKSwindow (swindow, "</B/16>Error<!B!16> You are not allowed to use that database.", BOTTOM);
        return;
     }
  
     /* Set the global database name. */
     if (database == 0)
     {
        /* Put a syntax error in the scrolling window. */
        sprintf (temp, "Command: %s", command);
        addCDKSwindow (swindow, temp, BOTTOM);
        addCDKSwindow (swindow, "</B/16>Error<!B!16> Syntax Error", BOTTOM);
        return;
     }
  
     /* Clear out the old database name and set the new one. */
     freeChar (GPCurrentDatabase);
     GPCurrentDatabase = database;
  
     /* Add a message into the scrolling window. */
     sprintf (temp, "Command: %s", command);
     addCDKSwindow (swindow, temp, BOTTOM);
     sprintf (temp, "Default Database set to %s", GPCurrentDatabase);
     addCDKSwindow (swindow, temp, BOTTOM);
  }
  
  /*
   * This does the requisite checking for failed login attempts.
   */
  DBPROCESS *sybaseLogin (CDKSCREEN *screen, char *accountName, char *accountPassword, int attemptCount)
  {
     DBPROCESS *dbProcess = 0;
     char *login		= accountName;
     char *password	= accountPassword;
     int count		= 0;
     int lines		= 0;
     char *mesg[5];
  
     /* Give them X attempts, then kick them out. */
     while (count < attemptCount)
     {
        /* Try to login. */
        dbProcess = loginToSybase (GPCdkScreen, login, password);
  
        /*
         * If the dbprocess is null the account/password
         * pair does not exist.
         */
         if (dbProcess == 0)
         {
  	  /*
  	   * If the login and account names were provided,
  	   * set them to null and allow the user to enter
  	   * the name and password by hand.
  	   */
  	   login = 0;
  	   password = 0;
  
  	  /* Spit out the login error message. */
  	  lines = 0;
  	  mesg[lines++] = "<C></B/5>Login Error";
  	  mesg[lines++] = " ";
  	  mesg[lines++] = "<C>The login/password pair does not exist.";
  	  mesg[lines++] = " ";
  	  mesg[lines++] = "<C>Please try again.";
  
  	  popupLabel (GPCdkScreen, mesg, lines);
  
  	  eraseCDKScreen (GPCdkScreen);
  	  refreshCDKScreen (GPCdkScreen);
  	  count++;
         }
         else
         {
  	  break;
         }
     }
  
     /* Did we expire the login attempts? */
     if (count > attemptCount-1)
     {
        lines = 0;
        mesg[lines++] = "<C>Login Error";
        mesg[lines++] = " ";
        mesg[lines++] = "<C>Too many attempyts to login.";
        mesg[lines++] = "<C>Exiting.";
  
        popupLabel (GPCdkScreen, mesg, lines);
  
        return 0;
     }
     return dbProcess;
  }
  
  /*
   * Let the user login.
   */
  DBPROCESS *loginToSybase (CDKSCREEN *screen, char *accountName, char *accountPassword)
  {
     CDKENTRY *loginEntry		= 0;
     CDKENTRY *passwordEntry	= 0;
     LOGINREC *dbLogin		= 0;
     char *hostAccount		= 0;
     char *login			= accountName;
     char *password		= accountPassword;
     char *mesg[10], temp[256];
  
     /* Draw the screen. */
     refreshCDKScreen (screen);
  
     /* Define the login entry field. */
     if (login == 0)
     {
        loginEntry = newCDKEntry (screen, CENTER, CENTER,
  				"\n<C></B/5>Sybase Login\n",
  				"Account Name: ",
  				A_BOLD|COLOR_PAIR(8),
  				COLOR_PAIR(24)|'_', vMIXED,
  				20, 1, 20, TRUE, FALSE);
  
        /* Use the current account name as the default answer. */
        hostAccount = getlogin();
        setCDKEntryValue (loginEntry, hostAccount);
  
        /* Get the login. */
        while (1)
        {
  	 /* Redraw the screen. */
  	 eraseCDKScreen (ScreenOf(loginEntry));
  	 refreshCDKScreen (ScreenOf(loginEntry));
  
  	 /* Get the login to the sybase account. */
  	 login = copyChar (activateCDKEntry (loginEntry, 0));
  
  	 /* Check if they hit escape. */
  	 if (loginEntry->exitType == vESCAPE_HIT)
  	 {
  	    mesg[0] = "<C></U>Error";
  	    mesg[1] = "A user name must be provided.";
  	    popupLabel (screen, mesg, 2);
  	 }
  	 else
  	 {
  	    break;
  	 }
        }
  
        /* Destroy the widget. */
        destroyCDKEntry (loginEntry);
     }
  
     /* Get the password if we need too. */
     if (password == 0)
     {
        sprintf (temp, "\n<C></B/5>%s's Password\n", login);
        passwordEntry = newCDKEntry (screen, CENTER, CENTER,
  				temp, "Account Password: ",
  				A_BOLD|COLOR_PAIR(8),
  				COLOR_PAIR(24)|'_', vHMIXED,
  				20, 0, 20, TRUE, FALSE);
        setCDKEntryHiddenChar (passwordEntry, '*');
  
        /* Get the password. (the account may not have a password.) */
        password = copyChar (activateCDKEntry (passwordEntry, 0));
        if ((passwordEntry->exitType == vESCAPE_HIT) ||
  	(strlen(password) == 0))
        {
  	 password = "";
        }
  
        /* Destroy the widget. */
        destroyCDKEntry (passwordEntry);
     }
  
     /*
      * Try to connect to the database and get a LOGINREC structre.
      */
     if ((dbLogin = dblogin()) == 0)
     {
        mesg[0] = "<C></U>Fatal Error";
        mesg[1] = "<C>Could not connect to the Sybase database.";
        popupLabel (screen, mesg, 2);
        refreshCDKScreen (screen);
        ExitProgram (EXIT_FAILURE);
     }
  
    /*
     * Set the login and password and try to login to the database.
     */
     DBSETLUSER (dbLogin, login);
     DBSETLPWD (dbLogin, password);
     DBSETLAPP (dbLogin, "cdk_syb");
  
     /* Create a dbprocess structure to communicate with the database. */
     return dbopen (dbLogin, 0);
  }
  
  /*
   * This actually runs the command.
   */
  void runIsqlCommand (CDKSCREEN *screen, CDKSWINDOW *swindow, DBPROCESS *dbProcess)
  {
     /* Declare local variables. */
     RETCODE returnCode;
     int rowCount;
  
     /* Add in a output seperation line. */
     addCDKSwindow (swindow, "<C><#HL(5)> Start of Output <#HL(5)>", BOTTOM);
  
     /* Run the command. */
     dbsqlexec (dbProcess);
  
     /* Check the return code of the commands. */
     while ((returnCode = dbresults (dbProcess)) != NO_MORE_RESULTS)
     {
        if (returnCode == FAIL)
        {
  	 /* Oops, the command bombed. */
  	 addCDKSwindow (swindow, "</5/16>Command failed.", BOTTOM);
        }
        else
        {
  	 if (!(DBCMDROW (dbProcess)))
  	 {
  	    /* The command could not return any rows. */
  	    addCDKSwindow (swindow, "</5/16>Command could not return rows.", BOTTOM);
  	 }
  	 else
  	 {
  	    /*
  	     * The command returned some rows, print out the title.
  	     */
  	    char *row = assembleTitle (dbProcess);
  	    addCDKSwindow (swindow, row, BOTTOM);
  	    freeChar (row);
  
  	    /* For each row returned, assemble the info. */
  	    rowCount = 0;
  	    while (dbnextrow (dbProcess) != NO_MORE_ROWS)
  	    {
  	       row = assembleRow (dbProcess);
  	       addCDKSwindow (swindow, row, BOTTOM);
  	       freeChar (row);
  	    }
  	 }
        }
     }
  
     /* Add in a output seperation line. */
     addCDKSwindow (swindow, "<C><#HL(5)>  End of Output  <#HL(5)>", BOTTOM);
     addCDKSwindow (swindow, "", BOTTOM);
  
     /* Can the query... */
     dbcanquery (dbProcess);
  }
  
  /*
   * This creates a single line from the column widths and values.
   */
  char *assembleTitle (DBPROCESS *dbProc)
  {
     char *colName	= 0;
     int colWidth		= 0;
     int colNameLen	= 0;
     int colCount		= dbnumcols (dbProc);
     int x		= 0;
     char temp[MAXWIDTH];
     char row[MAXWIDTH];
  
     /* Clean the row out. */
     memset (row, '\0', MAXWIDTH);
  
     /* Start assembling the row. */
     for (x=1; x <= colCount; x++)
     {
        colName		= dbcolname (dbProc, x);
        colWidth		= getColWidth (dbProc, x);
        colNameLen	= strlen (colName);
  
        /* If we need to pad, then pad. */
        if (colNameLen < colWidth)
        {
  	 /* Create a string the same length as the col width. */
  	 memset (temp, '\0', MAXWIDTH);
  	 memset (temp, ' ', (colWidth-colNameLen));
  
  	 /* Copy the name. */
  	 sprintf (row, "%s %s%s", row, colName, temp);
        }
        else
        {
  	 /* Copy the name. */
  	 sprintf (row, "%s %s", row, colName);
        }
     }
     return copyChar (row);
  }
  
  /*
   * This assembles a single row.
   */
  char *assembleRow (DBPROCESS *dbProcess)
  {
     char *dataValue	= 0;
     int colCount		= dbnumcols (dbProcess);
     int columnType	= 0;
     int colWidth		= 0;
     int valueLen		= 0;
     char value[MAXWIDTH];
     char temp[MAXWIDTH];
     char row[MAXWIDTH];
     char format[20];
     int x;
  
     /* Clean out the row. */
     memset (row, '\0', MAXWIDTH);
  
     /* Start assembling the row. */
     for (x=1; x <= colCount; x++)
     {
        columnType	= (int)dbcoltype (dbProcess, x);
        colWidth		= (int)getColWidth (dbProcess, x);
        valueLen		= (int)dbdatlen (dbProcess, x);
  
        /* Check the column type. */
        if (columnType == SYBINT1)
        {
  	 DBINT object_id = *((DBINT *)dbdata(dbProcess, x));
  	 sprintf (format, "%%-%dd", colWidth);
  	 sprintf (value, format, (int)object_id);
        }
        else if (columnType == SYBINT2)
        {
  	 DBINT object_id = *((DBINT *)dbdata(dbProcess, x));
  	 sprintf (format, "%%-%dd", colWidth);
  	 sprintf (value, format, (int)object_id);
        }
        else if (columnType == SYBINT4)
        {
  	 DBINT object_id = *((DBINT *)dbdata(dbProcess, x));
  	 sprintf (format, "%%-%dd", colWidth);
  	 sprintf (value, format, (int)object_id);
        }
        else if (columnType == SYBREAL)
        {
  	 DBREAL object_id = *((DBREAL *)dbdata(dbProcess, x));
  	 sprintf (format, "%%-%d.2f", colWidth);
  	 sprintf (value, format, object_id);
        }
        else if (columnType == SYBFLT8)
        {
  	 DBFLT8 object_id = *((DBFLT8 *)dbdata(dbProcess, x));
  	 sprintf (format, "%%-%d.2f", colWidth);
  	 sprintf (value, format, object_id);
        }
        else
        {
  	 if (valueLen <= 0)
  	 {
  	    strcpy (value, " ");
  	 }
  	 else
  	 {
  	    memset (value, '\0', MAXWIDTH);
  	    dataValue = (DBCHAR *)dbdata (dbProcess, x);
  	    strncpy (value, dataValue, valueLen);
  	 }
        }
  
        /* If we need to pad, then pad. */
        if (valueLen < colWidth)
        {
  	 /* Copy the value into the string. */
  	 memset (temp, '\0', MAXWIDTH);
  	 memset (temp, ' ', (colWidth-valueLen));
  	 sprintf  (row, "%s %s%s", row, value, temp);
        }
        else
        {
  	 sprintf  (row, "%s %s", row, value);
        }
     }
     return copyChar (row);
  }
  
  /*
   * This function returns the correct width of a column, taking
   * into account the width of the title, the width of the data
   * element.
   */
  int getColWidth (DBPROCESS *dbProcess, int col)
  {
     char *colName	= dbcolname (dbProcess, col);
     int colNameLen	= strlen(colName);
     int colWidth		= dbcollen (dbProcess, col);
     int columnType	= (int)dbcoltype (dbProcess, col);
  
     /* If the colType is int/real/float adjust accordingly. */
     if (columnType == SYBINT1 || columnType == SYBINT2 ||
  	columnType == SYBINT4)
     {
        colWidth = 5;
     }
     else if (columnType == SYBREAL || columnType == SYBFLT8)
     {
        colWidth = 8;
     }
  
     /* Is the name of the column wider than the col width? */
     if (colNameLen >= colWidth)
     {
        return (colNameLen+1);
     }
     return colWidth;
  }
  
  /*
   * This callback allows the user to play with the scrolling window.
   */
  int viewHistoryCB (EObjectType cdktype, void *object, void *clientData, chtype key)
  {
     CDKSWINDOW *swindow	= (CDKSWINDOW *)clientData;
     CDKENTRY *entry	= (CDKENTRY *)object;
  
     /* Let them play... */
     activateCDKSwindow (swindow, 0);
  
     /* Redraw the entry field. */
     drawCDKEntry (entry, ObjOf(entry)->box);
     return (TRUE);
  }
  
  /*
   * This displays a little introduction screen.
   */
  void intro (CDKSCREEN *screen)
  {
     int lines	= 0;
     char *mesg[10];
  
     /* Create the message. */
     mesg[lines++] = "";
     mesg[lines++] = "<C></B/16>Sybase Command Interface";
     mesg[lines++] = "<C>Written by Mike Glover";
     mesg[lines++] = "";
     mesg[lines++] = "<C>Type </B>help<!B> to get help.";
  
     /* Display the message. */
     popupLabel (screen, mesg, lines);
  }
  
  /*
   * This function displays help.
   */
  void help (CDKENTRY *entry)
  {
     int lines	= 0;
     char *mesg[25];
  
     /* Create the help message. */
     mesg[lines++] = "<C></B/29>Help";
     mesg[lines++] = "";
     mesg[lines++] = "</B/24>When in the command line.";
     mesg[lines++] = "<B=Up Arrow  > Scrolls back one command.";
     mesg[lines++] = "<B=Down Arrow> Scrolls forward one command.";
     mesg[lines++] = "<B=Tab       > Activates the scrolling window.";
     mesg[lines++] = "<B=help      > Displays this help window.";
     mesg[lines++] = "";
     mesg[lines++] = "</B/24>When in the scrolling window.";
     mesg[lines++] = "<B=l or L    > Loads a file into the window.";
     mesg[lines++] = "<B=s or S    > Saves the contents of the window to a file.";
     mesg[lines++] = "<B=Up Arrow  > Scrolls up one line.";
     mesg[lines++] = "<B=Down Arrow> Scrolls down one line.";
     mesg[lines++] = "<B=Page Up   > Scrolls back one page.";
     mesg[lines++] = "<B=Page Down > Scrolls forward one page.";
     mesg[lines++] = "<B=Tab or Esc> Returns to the command line.";
     mesg[lines++] = "<B=?         > Displays this help window.";
     mesg[lines++] = "";
     mesg[lines++] = "<C> (</B/24>Refer to the scrolling window online manual for more help<!B!24>.)";
  
     /* Pop up the help message. */
     popupLabel (ScreenOf(entry), mesg, lines);
  }
  
  /*
   * This converts a word to upper case.
   */
  char *uc (char *word)
  {
     int length	= strlen (word);
     char *upper	= (char *)malloc (sizeof (char *) * (length+2));
     int x;
  
     /* Start converting the case. */
     for (x=0; x < length; x++)
     {
        int ch = (unsigned char)(word[x]);
        if (isalpha (ch))
        {
  	 upper[x] = toupper(ch);
        }
        else
        {
  	 upper[x] = word[x];
        }
     }
     upper[length] = '\0';
     return upper;
  }
  
  /*
   * The following two functions are the error and message handler callbacks.
   * which will be called by Sybase when and error occurs.
   */
  int err_handler (DBPROCESS *dbProcess, DBINT mesgNumber, int mesgState, int severity, char *mesgText)
  {
     /* Declare local variables. */
     char *mesg[10], temp[256];
     int errorCount = 0;
  
     /* Check if the process is dead. */
     if ((dbProcess == 0) || (DBDEAD(dbProcess)))
     {
        mesg[0] = "</B/32>Database Process Error";
        mesg[1] = "<C>The database process seems to have died.";
        mesg[2] = "<C>Try logging in again with the </R>login<!R> command";
        popupLabel (GPCdkScreen, mesg, 3);
        return INT_EXIT;
     }
     else
     {
        mesg[0] = "</B/32>DB-Library Error";
        sprintf (temp, "<C>%s", dberrstr);
        mesg[1] = copyChar (temp);
        errorCount = 2;
     }
  
     /* Display the message if we have an error. */
     if (errorCount > 0)
     {
        popupLabel (GPCdkScreen, mesg, --errorCount);
     }
  
     /* Clean up. */
     if (errorCount == 2)
     {
        freeChar (mesg[1]);
     }
     return (1);
  }
  int mesg_handler (DBPROCESS *dbProcess, DBINT mesgNumber, int mesgState, int severity, char *mesgText)
  {
     /* Declare local variables. */
     char *mesg[10], temp[256];
  
     /* Create the message. */
     mesg[0] = "<C></B/16>SQL Server Message";
     sprintf (temp, "</R>Message Number<!R> %ld", mesgNumber);
     mesg[1] = copyChar (temp);
     sprintf (temp, "</R>State         <!R> %d", mesgState);
     mesg[2] = copyChar (temp);
     sprintf (temp, "</R>Severity      <!R> %d", severity);
     mesg[3] = copyChar (temp);
     sprintf (temp, "</R>Message       <!R> %s", mesgText);
     mesg[4] = copyChar (temp);
     popupLabel (GPCdkScreen, mesg, 5);
  
     /* Clean up. */
     freeChar (mesg[1]); freeChar (mesg[2]);
     freeChar (mesg[3]); freeChar (mesg[4]);
     return (1);
  }
  
  /*
   * This is for the scrolling window help callback.
   */
  int swindowHelpCB (EObjectType cdktype, void *object, void *clientData, chtype key)
  {
     CDKENTRY *entry	= (CDKENTRY *)clientData;
     help(entry);
     return (TRUE);
  }
  
  /*
   * This is the callback for the down arrow.
   */
  int historyUpCB (EObjectType cdktype, void *object, void *clientData, chtype key)
  {
     CDKENTRY *entry = (CDKENTRY *)object;
     struct history_st *history = (struct history_st *) clientData;
  
     /* Make sure we don't go out of bounds. */
     if (history->current == 0)
     {
        _Beep();
        return (TRUE);
     }
  
     /* Decrement the counter. */
     history->current--;
  
     /* Display the command. */
     setCDKEntryValue (entry, history->cmd_history[history->current]);
     drawCDKEntry (entry, ObjOf(entry)->box);
     return (TRUE);
  }
  
  /*
   * This is the callback for the down arrow.
   */
  int historyDownCB (EObjectType cdktype, void *object, void *clientData, chtype key)
  {
     CDKENTRY *entry = (CDKENTRY *)object;
     struct history_st *history = (struct history_st *) clientData;
  
     /* Make sure we don't go out of bounds. */
     if (history->current == history->count)
     {
        _Beep();
        return (TRUE);
     }
  
     /* Increment the counter... */
     history->current++;
  
     /* If we are at the end, clear the entry field. */
     if (history->current == history->count)
     {
        cleanCDKEntry (entry);
        drawCDKEntry (entry, ObjOf(entry)->box);
        return (TRUE);
     }
  
     /* Display the command. */
     setCDKEntryValue (entry, history->cmd_history[history->current]);
     drawCDKEntry (entry, ObjOf(entry)->box);
     return (TRUE);
  }
  
  /*
   * This callback allows the user to pick from the history list from a
   * scrolling list.
   */
  int listHistoryCB (EObjectType cdktype, void *object, void *clientData, chtype key)
  {
     CDKSCROLL *scrollList	= 0;
     CDKENTRY *entry		= (CDKENTRY *)object;
     struct history_st *history	= (struct history_st *) clientData;
     int height			= (history->count < 10 ? history->count+3 : 13);
     int selection;
  
     /* No history, no list. */
     if (history->count == 0)
     {
        /* Popup a little window telling the user there are no commands. */
        char *mesg[] = {"<C></B/16>No Commands Entered", "<C>No History"};
        popupLabel (ScreenOf(entry), mesg, 2);
  
        /* Redraw the screen. */
        eraseCDKEntry (entry);
        drawCDKScreen (ScreenOf(entry));
  
        /* And leave... */
        return (TRUE);
     }
  
     /* Create the scrolling list of previous commands. */
     scrollList = newCDKScroll (ScreenOf(entry), CENTER, CENTER, RIGHT,
  				height, -10, "<C></B/29>Command History",
  				history->cmd_history, history->count,
  				NUMBERS, A_REVERSE, TRUE, FALSE);
  
     /* Get the command to execute. */
     selection = activateCDKScroll (scrollList, 0);
     destroyCDKScroll (scrollList);
  
     /* Check the results of the selection. */
     if (selection >= 0)
     {
        /* Get the command and stick it back in the entry field. */
        setCDKEntryValue (entry, history->cmd_history[selection]);
     }
  
     /* Redraw the screen. */
     eraseCDKEntry (entry);
     drawCDKScreen (ScreenOf(entry));
     return (TRUE);
  }
  
  /*
   * This loads the history into the editor from the RC file.
   */
  void loadHistory (struct history_st *history)
  {
     char *home	= 0;
     char filename[1000];
  
     /* Create the RC filename. */
     if ((home = getenv ("HOME")) == 0)
     {
        home = ".";
     }
     sprintf (filename, "%s/.sybrc", home);
  
     /* Set some variables. */
     history->current	= 0;
     history->count	= 0;
  
     /* Read the file. */
     if ((history->count = CDKreadFile (filename, &(history->cmd_history))) != -1)
     {
        history->current = history->count;
     }
     return;
  }
  
  /*
   * This saves the history into RC file.
   */
  void saveHistory (struct history_st *history, int count)
  {
     FILE *fd	= 0;
     char *home	= 0;
     char filename[1000];
     int x;
  
     /* Create the RC filename. */
     if ((home = getenv ("HOME")) == 0)
     {
        home = ".";
     }
     sprintf (filename, "%s/.sybrc", home);
  
     /* Open the file for writing. */
     if ((fd = fopen (filename, "w")) == 0)
     {
        return;
     }
  
     /* Start saving the history. */
     for (x=0; x < history->count; x++)
     {
        fprintf (fd, "%s\n", history->cmd_history[x]);
     }
     fclose (fd);
     return;
  }
  