[alt.sources] SSMS -- Stupid Screen Management System

gl8f@astsun8.astro.Virginia.EDU (Greg Lindahl) (08/10/89)

The following set of routines provides screen forms under curses that
sort-of provide a similar functionality to your favorite hated PC
database 4GL-pretender's screen-input functions. The difference is
this one behaves like I wish dBase did, and it's only 4.4k of object
code.

Unfortunately, it's a bit rough around the edges and lacks a few useful
features. So, alt.sources came to mind...

If you want simple screens so people can keypunch fields, this might
be for you. If you hate the word keypunch, please don't even read
this.

Why'd I write it? Please don't ask.

Enjoy.

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of shell archive."
# Contents:  README.SSMS Makefile ssms.c stest1.c stest2.c
# Wrapped by gl8f@astsun8.astro.Virginia.EDU on Wed Aug  9 23:16:29 1989
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'README.SSMS' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'README.SSMS'\"
else
echo shar: Extracting \"'README.SSMS'\" \(2385 characters\)
sed "s/^X//" >'README.SSMS' <<'END_OF_FILE'
XSSMS -- Stupid Screen Management System 0.1
X===========================================
X
XI wanted to do some (ick) dBase style input routines in C. Don't ask
Xwhy, I just wanted to do them. I figured I could do it in 10k or less,
Xand I was right. It uses curses, and if you have a sys V-style set
Xof cursor keys, it may even be pretty obvious to a data-entry type person.
XElsewise you get to use Emacs-style control codes. Sorry, no vi.
X
XThis code was written on a Sun, and I have made no effort to make it
Xportable between BSD and SysV, because I'm lazy. No wonder it's on
Xalt.sources... I did put in a define BELL to switch between sysv's
Xbeep and a write() to get a bell. Be warned, write() works fine on
Xa VT100 but fails on in a suntool window.
X
XLook at the examples for info on calling the routines. I added a feature
Xthat dBase always needed -- a user routine that is called after a
Xfield is changed. You can use this for validation or range checking or
Xother uses. See the examples.
X
XI don't have a running floating-point type yet. No wonder this is
Xversion 0.1... However, you can input integers, and various character
Xtypes. No mixed-type fields. No insert mode.
X
XI couldn't decide the best way to deal with the integer field. When
Xyou exit the field, it automagically right-justifies it. If you start
Xtyping a new number to the left of the current one, it will clear the
Xold number. You cannot type a number to the right of an existing
Xnumber in the field.
X
XI'd give a complete description, but that would be longer than the
Xcode. One hint: the checker routine return values go like this:
Xnegative to exit immediately, zero to not move, positive to continue.
XPositive values will be returned to the main program if returned by
Xthe last field before you exit.
X
XEditing keys:
X
X^A  - beginning of field
X^B  - back one character
X^D  - delete character under cursor
XTAB - next field
X^E  - end of field
X^F  - forward one character
X^M  - next field
X^N  - next field
X^P  - previous field
XDEL - delete character to the left of cursor
XESC - clear field
X
XTODO:
X
X- floating point type
X- insert mode
X- return a tag so you can delete fields and insert fields on the fly.
X
XEnjoy! If you find this useful, or add to it, drop me a note. Version
X1.0 is due out within a few weeks (ha!)
X
XGreg Lindahl                   gl8f@virginia.edu
XPO Box 3818
XUniversity Station
XCharlottesville, VA 22903
END_OF_FILE
if test 2385 -ne `wc -c <'README.SSMS'`; then
    echo shar: \"'README.SSMS'\" unpacked with wrong size!
fi
# end of 'README.SSMS'
fi
if test -f 'Makefile' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'Makefile'\"
else
echo shar: Extracting \"'Makefile'\" \(265 characters\)
sed "s/^X//" >'Makefile' <<'END_OF_FILE'
Xssms.o: ssms.c
X	cc -c -g ssms.c
X
Xstest1.o: stest1.c
X	cc -c -g stest1.c
X
Xstest1: stest1.o ssms.o
X	cc -g -o stest1 stest1.o ssms.o -lcurses -ltermcap
X
Xstest2.o: stest2.c
X	cc -c -g stest2.c
X
Xstest2: stest2.o ssms.o
X	cc -g -o stest2 stest2.o ssms.o -lcurses -ltermcap
X
END_OF_FILE
if test 265 -ne `wc -c <'Makefile'`; then
    echo shar: \"'Makefile'\" unpacked with wrong size!
fi
# end of 'Makefile'
fi
if test -f 'ssms.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'ssms.c'\"
else
echo shar: Extracting \"'ssms.c'\" \(8275 characters\)
sed "s/^X//" >'ssms.c' <<'END_OF_FILE'
X/* limitations:
X *
X * incoming string must have space one more than length, so it
X * can come back null-terminated. it also has to START null
X * terminated, because we allow it to have a starting value.
X */
X
X#include <ctype.h>
X#include <curses.h>
X#include <string.h>
X#include <assert.h>
X#include <memory.h>
X
X#ifndef KEY_UP  /* this system doesn't have the sysv-style cursor defs */
X#define KEY_UP 32764
X#define KEY_DOWN 32765
X#define KEY_LEFT 32766
X#define KEY_RIGHT 32767
X#endif
X
X/* if standout space isn't visible on your terminal and you have sysv curses */
X/*#define standout() attrset(A_UNDERLINE)*/
X
X#define CONTROL_A '\001'
X#define CONTROL_B '\002'
X#define CONTROL_C '\003'
X#define CONTROL_D '\004'
X#define CONTROL_E '\005'
X#define CONTROL_F '\006'
X#define CONTROL_I '\011'
X#define CONTROL_J '\012'
X#define CONTROL_K '\013'
X#define CONTROL_L '\014'
X#define CONTROL_M '\015'
X#define CONTROL_N '\016'
X#define CONTROL_P '\020'
X#define CONTROL_R '\022'
X#define DELETE    '\177'
X#define ESC       '\033'
X
X
X/* forward declarations */
X
Xvoid charkill();
Xvoid numrjust();
X
Xstruct field {
X  int x, y, tag;
X  char *ptr;
X  char *picture;
X  int (*checker)();
X  int length;
X  int decimals;
X};
X
X#define MAXFIELDS 100
Xstatic struct field fields[ MAXFIELDS ];
Xstatic int fieldnum;
X
Xvoid ssms_init()
X{
X  fieldnum = 0;
X}
X
Xvoid ssms_cleargets()
X{
X  fieldnum = 0;
X}
X
Xvoid ssms_get( y, x, ptr, picture, tag, checker )
Xint x, y, tag;
Xchar *ptr;
Xchar *picture;
Xint (*checker)();
X{
X  char *p;
X  int ptrlen;
X  struct field *f;
X
X  if( fieldnum < MAXFIELDS ) {
X    if( !strchr( "FIAX!#NYD", *picture ) ) {
X      fprintf( stderr, "ssms_get: Invalid picture type %c.\n", *picture );
X      exit(1);
X    }
X
X    f = fields + fieldnum;
X
X    f->length = 0; /* this won't be set if the string is empty or whitespace */
X    f->decimals = 0;
X    sscanf( picture+1, "%d.%d", &(f->length), &(f->decimals) );
X    f->x = x;
X    f->y = y;
X    f->ptr = ptr;
X    f->picture = picture;
X    f->checker = checker;
X    f->tag = tag;
X    if( *picture != 'F' && f->decimals != 0 ) {
X      fprintf( stderr, "ssms_get: decimal places on 'F' only!\n" );
X      exit(1);
X    }
X    ptr[ f->length ] = '\0';
X    ptrlen = strlen(ptr);
X/*
X *  explicitly fill out the pointer length. this allows us to
X *  make some useful assumptions later. also, it guarantees that
X *  we'll consistantly overwrite the whole length of the pointer, so
X *  idiots who don't leave enough space don't get an occasional
X *  bug, they get a consistant bug.
X */
X    if( f->length != ptrlen )
X      for( p = ptr + ptrlen ; p < ptr + f->length ; p++ )
X	*p = ' ';
X    fieldnum++;
X  } else {
X    fprintf( stderr, "ssms_get: Too many fields!\n" );
X    exit(1);
X  }
X}
X
Xvoid ssms_showgets()
X{
X  int i;
X
X  for( i = 0 ; i < fieldnum ; i++ ) {
X    dispfield( fields + i );
X  }
X  refresh();
X}
X
Xdispfield( f )
Xstruct field *f;
X{
X  int j;
X/*
X *  assumption: since we filled ptr, it's all valid
X */
X  standout();
X  for( j = 0 ; j < f->length ; j++ )
X    mvaddch( f->y, f->x+j, f->ptr[j] );
X  if( f->decimals != 0 )
X    mvaddch( f->y, f->x+f->length - f->decimals - 1, '.' );
X  standend();
X}
X
Xint ssms_readgets( starttag )
Xint starttag;
X{
X  int i, field, pos, c, ret;
X  char *p;
X  struct field *f;
X
X  ssms_showgets();
X/*
X *  curses doesn't give me anyway to restore these after I set
X *  them. so these 3 are side effects.
X */
X  noecho();
X  raw();
X  leaveok( stdscr, FALSE );
X
X  field = 0;
X  pos = 0;
X
X  if( starttag )
X    for( i = 0, f = fields ; i < fieldnum ; i++, f++ )
X      if( f->tag == starttag ) {
X	field = i;
X	break;
X      }
X  
X  ret = 0;
X
X  while( (ret >= 0) && (field < fieldnum ) ) {
X
X    ret = 0;
X
X    if( field < 0 )
X      field = fieldnum - 1;
X
X    f = fields + field;
X
X    assert( strlen( f->ptr ) == f->length );
X
X/* test here to see if we've advanced onto a '.' in a numerical field */
X
X    if( f->decimals != 0 && pos == (f->length - f->decimals - 1 )) {
X      numrjust( f->ptr, pos );
X      pos++;
X    }
X
X    move( f->y, f->x+pos );
X    refresh();
X
X    c = getch();
X
X    switch(c) {
X
X    case CONTROL_C:
X      ret = -1;
X      break;
X
X    case CONTROL_A:
X      pos = 0;
X      break;
X
X    case KEY_LEFT:
X    case CONTROL_B:
X      ret = cleft( f, &field, &pos );
X      break;
X
X    case CONTROL_E:
X      pos = f->length - 1;
X      break;
X
X    case KEY_RIGHT:
X    case CONTROL_F:
X      if( pos >= strlen( f->ptr ) )
X	ret = fright( f, &field, &pos );
X      else
X	ret = cright( f, &field, &pos, f->length );
X      break;
X
X    case KEY_UP:
X    case CONTROL_P:
X      ret = fleft( f, &field, &pos );
X      break;
X 
X    case KEY_DOWN:
X    case CONTROL_N:
X    case CONTROL_I:
X    case CONTROL_M:
X    case CONTROL_J:
X      ret = fright( f, &field, &pos );
X      break;
X
X    case CONTROL_D:
X      charkill( f->ptr, pos );
X      dispfield( f );
X      break;
X
X    case DELETE:
X      if( pos > 0 ) {
X	pos--;
X	charkill( f->ptr, pos );
X	dispfield( f );
X      }
X      break;
X
X    case ESC:
X      pos = 0;
X      (void)memset( f->ptr, ' ', f->length );
X      dispfield( f );
X      break;
X
X    case CONTROL_K:
X      (void)memset( f->ptr+pos, ' ', f->length-pos);
X      dispfield( f );
X      break;
X
X    case CONTROL_L:
X    case CONTROL_R:
X      clearok( curscr, TRUE );
X      break;
X
X    default:
X
X      switch( *(f->picture) ) {
X
X      case '!':
X	c = isprint(c) ? toupper(c) : 0;
X	break;
X
X      case 'A':
X	c = (isalpha(c) || c == ' ') ? c : 0;
X	break;
X
X      case 'X':
X	c = isprint(c) ? c : 0;
X	break;
X
X      case 'I':
X	if( !isdigit(c) && c != '-' && c != ' ' )
X	  c = 0;
X	else if( c == ' ' ) {
X/*                  if you type a space and there's stuff before, clear the rest */
X	  for( i = 0 ; i < pos ; i++ ) {
X	    if( f->ptr[i] != ' ' ) {
X	      (void)memset( f->ptr + pos, ' ', f->length - pos );
X	      break;
X	    }
X	  }
X	} else {
X/*                  if you type a real character and the next is a space, clear
X                    the rest of the string */
X	  if( f->ptr[pos+1] == ' ' )
X	    (void)memset( f->ptr + pos+1, ' ', f->length - pos - 1 );
X/*                  if you type a real char and there is stuff before followed
X                    by a space, it's illegal */
X	  if( pos > 0 && ((f->ptr[pos-1] == ' ' ) || (c == '-')) ) {
X	    for( i = 0 ; i < pos ; i++ ) {
X	      if( f->ptr[i] != ' ' ) {
X		c = 0;
X		break;
X	      }
X	    }
X	  }
X	}
X	dispfield( f );
X	break;
X
X      case '#':
X	c = (isdigit(c) || c == ' ' || c == '+' || c == '-' ) ? c : 0;
X	break;
X
X      case 'N':
X	c = (isalnum(c) || c == ' ' ) ? c : 0;
X	break;
X
X      case 'Y':
X	c = toupper(c);
X	c = ( c == 'Y' || c == 'N' ) ? c : 0;
X	break;
X
X      }
X
X      if( c ) {
X	standout();
X	mvaddch( f->y, f->x+pos, c );
X	standend();
X	f->ptr[pos] = c;
X	ret = cright( f, &field, &pos, f->length );
X      } else {
X#ifdef BEEP
X	beep();
X#else
X	char seven = 7;
X	write( 2, &seven, 1 );
X#endif
X      }
X    }
X    refresh();
X  }
X  return( ret );
X}
X
X/*  right-justify a string, which isn't necessarily at the
X *  end of the fixed-length field its in. pass in a negative
X *  length to left justify.
X */
Xvoid numrjust( ptr, length )
Xchar *ptr;
Xint length;
X{
X  int value = 0;
X  char temp;
X
X  temp = ptr[length];
X  ptr[length] = '\0';
X  sscanf( ptr, "%d", &value );
X  sprintf( ptr, "%*d", length, value );
X  assert( strlen(ptr) == length );
X  ptr[length] = temp;
X}
X
Xint cright( f, pf, pp, length )
Xint *pp, *pf, length;
Xstruct field *f;
X{
X  if( ++(*pp) >= length ) {
X    return( fright( f, pf, pp ));
X  } else
X    return(0);
X  /* NOTREACHED */
X}
X
Xint fright( f, pf, pp )
Xint *pp, *pf;
Xstruct field *f;
X{
X  int ret;
X
X  *pp = 0;
X  if( *(f->picture) == 'I' )
X    numrjust( f->ptr, f->length );
X/* ADD =='F' */
X  ret = (*(f->checker))( f->tag, f->ptr );
X  dispfield( f );
X  if( ret != 0 )
X    (*pf)++;
X
X  return( ret );
X}
X
Xint cleft( f, pf, pp )
Xint *pf, *pp;
Xstruct field *f;
X{
X  if (--(*pp) < 0 )
X    return( fleft( f, pf, pp ) );
X  else
X    return(0);
X}
X
Xint fleft( f, pf, pp )
Xint *pf, *pp;
Xstruct field *f;
X{
X  int ret;
X
X  *pp = 0;
X  if( *(f->picture) == 'I' )
X    numrjust( f->ptr, f->length );
X/* ADD == 'F' */
X  ret = (*(f->checker))( f->tag, f->ptr );
X  dispfield( f );
X  if( ret != 0 )
X    (*pf)--;
X  return( ret );
X}
X
X/* just make sure you don't try to delete the end null. */
X
Xvoid charkill( ptr, pos )
Xchar *ptr;
Xint pos;
X{
X  char *p;
X
X  for( p = (ptr+pos+1) ; *p ; p++ )
X    *(p-1) = *p;
X  *(p-1) = ' ';
X}
END_OF_FILE
if test 8275 -ne `wc -c <'ssms.c'`; then
    echo shar: \"'ssms.c'\" unpacked with wrong size!
fi
# end of 'ssms.c'
fi
if test -f 'stest1.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'stest1.c'\"
else
echo shar: Extracting \"'stest1.c'\" \(669 characters\)
sed "s/^X//" >'stest1.c' <<'END_OF_FILE'
X#include <curses.h>
X
Xstatic int dummy( tag, ptr )
Xchar *ptr;
Xint tag;
X{
X  return(1);
X}
X
Xmain()
X{
X  char name[21];
X  char address[21];
X  char number[11];
X  int c;
X
X  strcpy( name, "Mr. FooBar" );
X  strcpy( address, "20/20 Boogie Ave." );
X  *number = '\0';
X
X  initscr();
X  ssms_init();
X  standout();
X  mvaddstr( 0, 30, "Stupid Screen Management System Test" );
X  standend();
X  mvaddstr( 5, 0, "Name" );
X  ssms_get( 5, 10, name, "X20", 0, dummy );
X  mvaddstr( 7, 0, "Address" );
X  ssms_get( 7, 10, address, "X20", 0, dummy );
X  mvaddstr( 9, 0, "A Number" );
X  ssms_get( 9, 10, number, "I10", 0, dummy );
X  refresh();
X  ssms_showgets();
X  ssms_readgets( 0 );
X  endwin();
X}
END_OF_FILE
if test 669 -ne `wc -c <'stest1.c'`; then
    echo shar: \"'stest1.c'\" unpacked with wrong size!
fi
# end of 'stest1.c'
fi
if test -f 'stest2.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'stest2.c'\"
else
echo shar: Extracting \"'stest2.c'\" \(3217 characters\)
sed "s/^X//" >'stest2.c' <<'END_OF_FILE'
X/*  this example is much more elaborate, using the checker routine. note
X *  that i'm grossly overloading the tags argument here. it's used to
X *  identify what field things are to the checker routines, but also
X *  can be used to start input at a field other than the first.
X *
X *  If you want to do some of this kind of stuff in dBase it's
X *  completely awful.
X *
X *  a positive return from a checker means OK, 0 means remain on this
X *  field, and negative means bomb out of ssms_gets with that return
X *  value.
X */
X
X#include <curses.h>
X
Xint dummy( tag, ptr )
Xchar *ptr;
Xint tag;
X{
X  return(1);
X}
X
Xint dbaselookup( tag, ptr )
Xchar *ptr;
Xint tag;
X{
X  int key;
X  char *value = NULL;
X
X  key = atoi( ptr );
X  if( key == 1 )
X    value = "Red";
X  else if( key == 2 )
X    value = "Green";
X  else if( key == 3 )
X    value = "Brown";
X
X  if( value ) {
X    mvaddstr( 5, 15, value );
X    refresh();
X    return(1);
X  } else
X    return(0);
X}
X
Xint rangebot[2] = { 0, 0 };
Xint rangetop[2] = { 3, 7 };
X
Xint rangecheck( tag, ptr )
Xchar *ptr;
Xint tag;
X{
X  int value;
X
X  value = atoi( ptr );
X
X  if( value > rangetop[tag] || value < rangebot[tag] )
X    return(0);
X  else
X    return(1);
X}
X
Xint check[6], mychecksum, allegedchecksum;
X
Xint checksum( tag, ptr )
Xchar *ptr;
Xint tag;
X{
X  int value, i;
X  char buf[4];
X
X  mychecksum = 0;
X  value = atoi( ptr );
X  check[tag] = value;
X  for( i = 0 ; i < 6 ; i++ )
X    mychecksum += check[i];
X  sprintf( buf, "%3d", mychecksum );
X  mvaddstr( 20, 4, buf );
X  refresh();
X  return(1);
X}
X
Xint chkbeep( tag, ptr )
Xchar *ptr;
Xint tag;
X{
X  char seven = 7;
X
X  allegedchecksum = atoi( ptr );
X
X  if( allegedchecksum != mychecksum ) {
X#ifdef BELL
X    bell();
X#else
X    write( 2, &seven, 1 );
X#endif
X    mvaddstr( 21, 10, "Checksum disagreement!" );
X    refresh();
X  } else
X    mvaddstr( 21, 10, "                      " );
X  return(1);
X}
X
Xint comparecheck( tag, ptr )
Xchar *ptr;
Xint tag;
X{
X  if( *ptr == 'Y' && allegedchecksum == mychecksum )
X    return(1);
X  else {
X    *ptr = ' ';
X    return( 2 ); /* numbers greater than one are useful on the last field
X                    only to signal an error condition... */
X  }
X}
X
Xmain()
X{
X  char value1[2], value2[2];
X  char key[2];
X  char val[6][3];
X  char dummychecksum[4];
X  char ok[2];
X  int i;
X
X  initscr();
X  ssms_init();
X  standout();
X  mvaddstr( 0, 30, "Stupid Screen Management System Test # 2" );
X  standend();
X
X  mvaddstr( 5, 0, "Key (1-3)" );
X  key[0] = '\0';
X  ssms_get( 5, 10, key, "I1", 0, dbaselookup );
X  mvaddstr( 7, 0, "Range 1 (0-3)" );
X  value1[0] = '\0';
X  ssms_get( 7, 15, value1, "I1", 0, rangecheck );
X  mvaddstr( 9, 0, "Range 2 (0-7)" );
X  value2[0] = '\0';
X  ssms_get( 9, 15, value2, "I1", 1, rangecheck );
X  mvaddstr(11, 0, "Column of numbers" );
X
X  for( i = 0 ; i < 6 ; i++ ) {
X    val[i][0] = '\0';
X    ssms_get (13+i, 5, val[i], "I2", i, checksum );
X  }
X  mvaddstr(19, 4, "---" );
X  mvaddstr(20, 0, "Chk" );
X
X  mvaddstr(21, 0, "Sum" );
X  dummychecksum[0] = '\0';
X  mychecksum = 0;
X  ssms_get(21, 4, dummychecksum, "I3", 0, chkbeep );
X 
X  mvaddstr(23, 0, "Is this all OK? [Y/N]" );
X  ok[0] = '\0';
X  ssms_get(23, 22, ok, "Y1", 0, comparecheck );
X
X  refresh();
X  ssms_showgets();
X  while (ssms_readgets(0) == 2)
X    ;
X  endwin();
X}
END_OF_FILE
if test 3217 -ne `wc -c <'stest2.c'`; then
    echo shar: \"'stest2.c'\" unpacked with wrong size!
fi
# end of 'stest2.c'
fi
echo shar: End of shell archive.
exit 0
------
Greg Lindahl
gl8f@virginia.edu                                             I'm not the NRA.