25 Years of Programming
An open source source for C, C++, OWL, BASIC, MDB, XLS, DOT, and more...
Home   Projects   Up   Sitemap   Search   Blog   Forum+Chat   About Us   Privacy   Terms of Use   Feedback   FAQ   Images   Services   Payments   Humor   Music

Amateur Radio Net Control programs in Borland C

This is the source code listing for the C versions of my programs in the Amateur Radio Net Control project, utilities that assist an amateur radio net control operator with running and checking members into an on the air radio net. The project's home page has links to later C++ versions, a download zip of all versions, and a screenshot of the program in operation.

There is a link below to my.h, which these files use. That file is now a C++ header file. Any parts of it that are actually required here (if any) would need to be converted back to C.

NET.C

/*  NET.C          BORLAND VERSION    7-19-94
	Copyright (C)1994 Steven Whitney.
	Initially published by http://25yearsofprogramming.com.

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License (GPL)
	Version 3 as published by the Free Software Foundation.
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

FOR CONDUCTING AMATEUR RADIO NETS.
ALL DATA FILES MUST BE IN DEFAULT DIRECTORY.
10-15-94 Extensive mods for IBM/Gateway.  This version is now entirely IBM-specific.

This version is included because it was the last C version before it was converted to C++.
There was a DeSmet C version for the Heathkit H-100 computer, but it has been lost. This
is the closest thing to it that I can provide. The Borland methods would have to be reverted
back to DeSmet, and the screen size calculations might have to be adjusted, but I'm less
sure about that.

-----
TO DO:

if someone has multiple traffic or traffic + announcements,
traffic count report is wrong (each call can only be credited with 1).
also, traffic is counted even if it's not passed during the net.

if contact requestee is in same city as requestor, update requestee's
color also.  see note in docity()

*/
#include <stdio.h>
#include <stddef.h>
#include <dos.h>
#include <stdlib.h>
#include <conio.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <mem.h>
#include <process.h>

#include "my.h"

// ARRAY LIMITS
#define MAXCITIES       100     // max # of different cities
#define MAXCALLS        400     // max # of people
#define MAXMEMOS         25     // max # memo lines
#define MAXAGENDA       100     // MAX # LINES IN AGENDA TEXT
								// COLOR SETTINGS

// --------------------------------------------------------------
// GLOBAL DATA
// --------------------------------------------------------------
struct hams                 // 38 bytes per array element
{
	char call[7];
	char name[11];          // 10 chars allowed, only 9 are ever printed
	char city[16];
	char ck;          	    // CONTAINS CHECKIN CHAR AFTER CHECKING IN 
	char need;              // FLAG: IF NEEDED FOR CONTACT 
	int ord;                // PLACEMENT IN CHECKIN ORDER 
							// (BUT DO NOT USE ORD FOR COUNTING CHECKINS) 
} db[MAXCALLS];
int dbc;                    // NUMBER OF ACTIVE ENTRIES IN DB[] 

struct towns                // 17 bytes per array element 
{
	char n[16];             // LIST OF CITIES ENCOUNTERED 
	char tflag;             // WHETHER THERE IS TRAFFIC FOR THE CITY 
} cities[MAXCITIES];
int citycount;              // NUMBER OF CITIES IN LIST 
int cindex;                 // POINTER TO CURRENTLY SELECTED CITY 
int resumecity;         	// CITY TO RESUME INPUT WITH AFTER PAGING BACKWARDS 

int filteron;               // WHETHER BYCALL SHOWS ONLY THOSE NOT CHECKED IN
int ckinord;                // COUNTER: TRACKS THE CHECKIN ORDER, BUT 
							// IT ONLY INCREASES, DOESN'T KEEP ACCURATE COUNT 
                        

char contacts;              // CONTACT REFERENCE CHAR, STARTING AT ASCII '0' 
char defch;                 // DEFAULT CHECKIN CHARACTER (ONLY LOPX) 
char updateflag;            // FLAG WHETHER TO WRITE OUT DB BEFORE EXIT 
char memos[MAXMEMOS][81];   // ARRAY OF SINGLE-LINE MEMOS 
int memoptr;                // POINTER TO CURRENTLY SELECTED MEMO 

char *agenda[MAXAGENDA];    // lines of agenda text 
int agendacount;            // number of lines

// -------------------------------------------------------------- 
// erase25th()     erases line 25 and places cursor at start 
// -------------------------------------------------------------- 
void erase25th(void)
{
	gotoxy(1,25);
	clreol();
	return;
}

// -------------------------------------------------------------- 
// WRITEMEMOS()    WRITES MEMOS TO DISK FILE MM-DD-YY.MEM 
// RETURNS TRUE IF SUCCESSFUL, FALSE IF DISK WRITE ERROR 
// -------------------------------------------------------------- 
int writememos(void)
{
int i;
char buf[15];
FILE *outfile;

_strdate((char *)buf);                                 // GET DOS DATE 
for(i = 0 ; i < strlen((char *)buf) ; i++)              // slashes TO dashes 
	if(buf[i] == '/')
		buf[i] = '-';           
strcat((char *)buf,".mem");                            // ADD EXTENSION 
if((outfile = fopen((char *)buf,"a")) == NULL)  // APPEND ALLOWS MULTIPLE WRITES 
	return(FALSE);
for(i = 0 ; i < memoptr ; i++)
	if(fprintf(outfile,"%s\n",memos[i]) == ERR)
		return(FALSE);
if(fclose(outfile) == ERR)
	return(FALSE);
memoptr = 0;                                // RESET POINTER 
return(TRUE);
}

// -------------------------------------------------------------- 
// MEMO()  7-20-93   ALLOWS ENTRY OF MEMOS 
// c = IMPORTED TEXT TO START MEMO WITH 
// autoret = FLAG: WHETHER TO PRINT *C AND AUTO-EXIT 
// -------------------------------------------------------------- 
void memo(char *c, char autoret)
{
char buf[83];  			// oversized by 2 for cgets()
char *p;		 		// pointer into buf 

strcpy(memos[memoptr],c);               // START MEMO WITH STRING 
if(!autoret)                            // GET ADDITIONAL USER TEXT 
{
	textcolor(WHITE);
	erase25th();
	cprintf("%s",memos[memoptr]);
	_setcursortype(_SOLIDCURSOR);
	buf[0] = 81;
	p = cgets((char *)buf);
	if(strlen(p))                      // CUT TO FIT INTO MAX MEMO LENGTH 
		strncat(memos[memoptr],p,80 - strlen(memos[memoptr]));
	else                                // IF USER ENTERED NOTHING AT ALL,    
		memos[memoptr][0] = '\0';       // ASSUME ABORT & ZERO THE STRING 
	_setcursortype(_NORMALCURSOR);
}
if(strlen(memos[memoptr]))        // IF MEMO WASN'T USED, DON'T INCREMENT 
	if(++memoptr == MAXMEMOS)
		if(!writememos())         // IF ARRAY FULL, WRITE TO DISK 
		{
			erase25th();
			cprintf("Disk error.  Possible data loss in .MEM file.  Press any key...");
			getch();
			memoptr = 0;           // RESET POINTER ANYWAY TO PREVENT OVERRUN 
		}
return;
}

// -------------------------------------------------------------- 
// SHOWMEMOS()     DISPLAYS THE MEMOS 
// RETURNS: CHAR, WHICH CAN BE A MAIN MENU COMMAND 
// -------------------------------------------------------------- 
int showmemos(void)
{
int i;

textcolor(LIGHTGRAY);
clrscr();
for(i = 0 ; i < memoptr ; i++)
{
	cprintf("%s\n\r",memos[i]);
	if(((i % 23) == 0) && (i != 0))
	{
		erase25th();
		cprintf("More...");  
		getch();
	}
}
textcolor(GREEN);
erase25th();
cprintf("Enter command or <CR> for menu: ");
return(ci());
}

// --------------------------------------------------------------
// LISTNEED() PROCESSES CONTACT REQUEST
// ASSIGNS NEXT CONTACT REQUEST CHAR, AND PLACES IT IN THE
// NEED FIELD OF BOTH PARTIES.
// IF NEEDED PARTY NOT FOUND, CONTACT IS NOTED FOR REQUESTOR,
// AND A MEMO IS ADDED TO MEMO PAGE, BUT NEEDED PARTY NOT ADDED TO DB
// RETURNS: TRUE IF NEEDED PARTY FOUND, FALSE IF NOT
// --------------------------------------------------------------
int listneed(int req)       	// INDEX IN DB OF REQUESTING PARTY
{
int i;
char buf[20], buf2[60];
char *p;

textcolor(WHITE);
erase25th();
cprintf("Contact with which call? <CR> to quit: ");
buf[0] = 7;
p = cgets((char *)buf);
if(!strlen(p))                      // ALLOW ABORT 
	return(FALSE);  
strupr(p);
db[req].need = contacts;            // MARK REQUESTOR 
for(i = 0 ; i < dbc ; i++)          // SEARCH DB 
	if(!strcmp(db[i].call,p))       // IF FOUND, 
	{
		db[i].need = contacts++;    // MARK NEEDED 
		return(TRUE);               // RETURN TRUE
	}
contacts++;
sprintf((char *)buf2,"Contact: %s for %s (who is not in database).",
	db[req].call,p);
memo((char *)buf2,TRUE);        // NOTE WANTED CONTACT W/CALL NOT IN DB 
return(FALSE);                  // WASN'T FOUND 
}

// -------------------------------------------------------------- 
// CITYSORT() FOR SORTING CITY LIST ALPHABETICALLY 
// -------------------------------------------------------------- 
int citysort(const void *left, const void *right)
{
struct towns *l, *r;
l = (struct towns *)left;
r = (struct towns *)right;
return(strcmp(l->n,r->n));
}

// -------------------------------------------------------------- 
// ADDCITY() ADDS A CITY TO THE CITIES LIST 
// -------------------------------------------------------------- 
void addcity(char *c, char t)
					// NAME OF CITY TO ADD 
					// FLAG (TRUE/FALSE OR 1/0) WHETHER THERE IS TRAFFIC 
{
int i;
char cs[16];    // NAME OF CITY POINTED TO BY CINDEX
				// AFTER SORTING, CINDEX MIGHT NOT POINT TO THE SAME CITY
strcpy((char *)cs,cities[cindex].n);    // SAVE CURRENTLY SELECTED CITY NAME 
strcpy(cities[citycount].n,c);
cities[citycount].tflag = t;
if(++citycount == MAXCITIES)
	aborts("Too many cities.  Revise program.");
qsort(cities,citycount,sizeof(struct towns),citysort);
for(i = 0 ; i < citycount ; i++)        // FIND PREVIOUS CINDEX CITY 
	if(!strcmp((char *)cs,cities[i].n))
	{
		cindex = i;                    // RESET CINDEX TO IT 
		break;
	}
return;
}

// -------------------------------------------------------------- 
// LOGTRAF() INCREMENTS TRAFFIC COUNT FOR A CITY 
// --------------------------------------------------------------
void logtraf(int from)      // INDEX WITHIN DB OF STATION WITH TRAFFIC
{
int i;
char buf[40];
char *p;

textcolor(WHITE);
erase25th();
cprintf("Destination city or <CR> to quit: ");             // GET CITY NAME,
buf[0] = 16;
p = cgets((char *)buf);
if(!strlen(p))                // ABANDON ENTRY 
	return;         
strupr(p);
for(i = 0 ; i < citycount ; i++)    // LOOK FOR CITY NAME 
	if(!strcmp(cities[i].n,p))      // IF FOUND, 
	{
		cities[i].tflag++;			// buf reused for new purpose here 
		sprintf((char *)buf,"T: %s for %s",db[from].call,cities[i].n);
		memo((char *)buf,TRUE);         
		return;                                 
	}
addcity(p,TRUE); 			               // IF NOT FOUND, ADD IT 
for(i = 0 ; i < citycount ; i++)		   // LOOK FOR IT AGAIN 
	if(!strcmp(cities[i].n,p)) 			   // IT WILL BE FOUND FOR SURE 
	{
		sprintf((char *)buf,"T: %s for %s",db[from].call,cities[i].n);
		memo((char *)buf,TRUE);         
		return;         
	}
}						// end logtraf 

// -------------------------------------------------------------- 
// CITYCALL() ROUTINE FOR SORTING BY CITY, CALL 
// -------------------------------------------------------------- 
int citycall(const void *left, const void *right)
{
int i;
struct hams *l, *r;

l = (struct hams *)left;
r = (struct hams *)right;
i = strcmp((char *)l->city,(char *)r->city);
return(i ? i : strcmp((char *)l->call,(char *)r->call)); 
}

// -------------------------------------------------------------- 
// LOADDB() READ CALL SIGN DATA FILE INTO ARRAY 
// ALSO CREATES THE CITIES[] ARRAY 
// RETURNS: VOID -- TERMINATES PROGRAM ON ERROR 
// SETS GLOBAL VARIABLES DBC AND CITYCOUNT 
// -------------------------------------------------------------- 
void loaddb(void)
{
int i;
char buf[128], citybuf[50], callbuf[50], namebuf[50];
FILE *infile;

gotoxy(1,1);    
textcolor(LIGHTGRAY);
cprintf("Loading CALLS.DAT...");
if((infile = fopen("CALLS.DAT","r")) == NULL)
	aborts("CALLS.DAT not found.");
dbc = 0;                                   // LOAD DATA INTO DB 
while(fgets((char *)buf,128,infile))
{
	i = sscanf((char *)buf,"\"%[^\"]\",\"%[^\"]\",\"%[^\"]\"",
		citybuf,callbuf,namebuf);
	if(i != 3)
		aborts("File CALLS.DAT is corrupt.  Repair and rerun.");
	citybuf[15] = callbuf[6] = namebuf[10] = '\0';  // prevent over-run 
	strcpy(db[dbc].city,(char *)citybuf);
	strcpy(db[dbc].call,(char *)callbuf);
	strcpy(db[dbc].name,(char *)namebuf);
	strupr(db[dbc].city);        		   // city name all capitals 
	strlwr(db[dbc].name + 1);    		   // LEAVE 1ST LETTER CAPITALIZED 
	db[dbc].ck = ' ';                      // INIT. CHECKIN FLAG TO ZERO 
	db[dbc].need = ' ';                    // INIT. TO 'NOT NEEDED' 
	db[dbc].ord = 0;                       // INIT. CHECKIN ORDER TO ZERO 
	if(++dbc == MAXCALLS)
		aborts("Too many entries.  Revise program.");   
}
fclose(infile);
cprintf("\n\rSorting %d records...",dbc);
qsort(db,dbc,sizeof(struct hams),citycall);     
cprintf("\n\rDone.\n\r");
										// BUILD CITY LIST 
zArray((char *)buf);
citycount = 0;
for(i = 0 ; i < dbc ; i++)
	if(strcmp((char *)buf,db[i].city))   // NEXT CITY ENCOUNTERED? 
	{
		strcpy(cities[citycount].n,db[i].city);   // COPY TO CITY LIST
		strcpy((char *)buf,db[i].city);           // NEW BUF COMPARISON 
		cities[citycount].tflag = 0;              // INIT. TRAFFIC FLAG 
		if(++citycount == MAXCITIES)
			aborts("Too many cities. Revise program.");
	}
return;
}

// -------------------------------------------------------------- 
// HANDLETAC()             PROCESSES CHECKIN CHARS T, A, C 
// returns the checkin char of the call sign being processed 
// -------------------------------------------------------------- 
char handletac(int i)	// INDEX INTO DB OF CALL SIGN BEING PROCESSED 
{
char buf[12];
switch(db[i].ck)
{
	case 't':         			    // RECORD PRESENCE OF TRAFFIC 
		logtraf(i);
		break;

	case 'a':          				// RECORD ANNOUNCEMENT 
		sprintf((char *)buf,"A: %s ",db[i].call);       
		memo((char *)buf,FALSE);        
		break;

	case 'c':    			        // PROCESS CONTACT REQUEST 
		listneed(i);
		break;
}
return(db[i].ck);
}

// -------------------------------------------------------------- 
// ADD NEW PERSON TO DATABASE 
// RETURNS: TRUE IF PERSON ADDED, FALSE IF NOT 
// -------------------------------------------------------------- 
int addnew(char *c)				// DEFAULT CITY NAME 
{
int i;
char newcity = TRUE; 		   // WHETHER CITY IS NEW TO DATABASE 
char buf[40], buf2[40], bigbuf[83];
char *p;

if(dbc == MAXCALLS)             // calls database full 
{
	erase25th();
	cprintf("CALLS DATABASE FULL... <CR> TO ACKNOWLEDGE...");
	while(ci() != 13);      // user may be attempting input - discard
	return(FALSE);
}
textcolor(WHITE);
erase25th();
cprintf("<ADD> Enter: ");
textcolor(GREEN);               // ALERTS USER THAT PROMPT LINE IS DIFFERENT 
cprintf("CALL NAME ");          // FROM THAT IN BYCALL() WHICH CAN CALL THIS
textcolor(WHITE);
cprintf("or <CR> to quit: ");
bigbuf[0] = 81;
p = cgets((char *)bigbuf);
if(sscanf(p,"%s %s",buf,buf2) != 2)                 // ABORT 
	return(FALSE);
buf2[10] = buf[6] = '\0';       // don't let variables overrun their space 
strcpy(db[dbc].call,(char *)buf);
strcpy(db[dbc].name,(char *)buf2);
									// MAKE SURE CALL ISN'T ALREADY IN DB 
strupr(db[dbc].call);      		    // it must be upper case to test 
for(i = 0 ; i < dbc ; i++)
	if(!strcmp(db[i].call,db[dbc].call))
		return(FALSE);

strcpy(db[dbc].city,c);       		  // SET UP DEFAULT CITY 
erase25th();
cprintf("City, or <cr> for %s: ",db[dbc].city);
buf[0] = 16;                             // limit city name length 
p = cgets((char *)buf);
if(strlen(p) > 0)
	strcpy(db[dbc].city,p);
do
{
	erase25th();
	cprintf("Checkin code \"ACLOPTVX \": ");
	db[dbc].ck = tolower(getchar());
}
while(!strchr("acloptvx ",db[dbc].ck));         // DEFAULT NOT ALLOWED HERE 
							

if(db[dbc].ck != 'v')       // IF WE GET THIS FAR, AND ADDITION NOT A VISITOR, 
	updateflag = TRUE;      // WE'LL HAVE TO WRITE OUT DB[] FOR SURE, 
								

if(db[dbc].ck != ' ')                   // LOG PLACE IN THE CHECKIN ORDER 
	db[dbc].ord = ckinord++;
db[dbc].need = ' ';
strupr(db[dbc].city);
strupr(db[dbc].name);   
strlwr(db[dbc].name + 1);
	

for(i = 0 ; i < citycount ; i++)                // SEE IF CITY IS KNOWN 
	if(!strcmp(cities[i].n,db[dbc].city))
	{
		newcity = FALSE;
		break;
	}
if(newcity)
	addcity(db[dbc].city,FALSE);    

handletac(dbc);                // PROCESS TRAFFIC, ANNOUNCEMENT, OR CONTACT 
erase25th();                   // LINE 25 MAY HAVE BEEN USED 
dbc++;
qsort(db,dbc,sizeof(struct hams),citycall);             // SORT INTO DB 
return(TRUE);
}

// -------------------------------------------------------------- 
// ALPHASORT() STRANGE SORT ROUTINE, FOR USE IN SORTING AN 
// ARRAY OF INTEGERS BASED ON ARRAY ELEMENTS THAT THE INTEGERS POINT TO 
// RESULTS IN SORTING AN INDEX OF A SUBSET OF THE LARGER ARRAY 
// WITHOUT HAVING TO SORT THE ARRAY OR ANY OF ITS ELEMENTS 
// SORTS ON CALL SIGN 
// -------------------------------------------------------------- 
int alphasort(const void *left, const void *right)
{
int *l, *r;

l = (int *)left;
r = (int *)right;
return(strcmp(db[*l].call,db[*r].call));
}

// -------------------------------------------------------------- 
// ORDERSORT() SAME AS ALPHASORT(), BUT SORTS BY ORDER CHECKED IN 
// -------------------------------------------------------------- 
int ordersort(const void *left, const void *right)
{
int *l, *r;

l = (int *)left;
r = (int *)right;
return(db[*l].ord - db[*r].ord);
}

// -------------------------------------------------------------- 
// DISPLAYREC() DISPLAYS ONE DB RECORD IN FORMAT USED ON INPUT SCREENS 
// -------------------------------------------------------------- 
void displayrec(int i)		// INDEX INTO DB OF RECORD BEING DISPLAYED 
{
textcolor(RED);                                 // INIT COLOR 
if(db[i].ck > ' ') textcolor(LIGHTBLUE);             // AND BUMP UP 
if(db[i].need > ' ') textcolor(MAGENTA);        // ON INCREASING 
if(db[i].ck == 't') textcolor(YELLOW);          // IMPORTANCE LEVELS 
cprintf("%c%c %-6.6s %-9.9s",(db[i].ck),db[i].need,db[i].call,db[i].name);
return;
}

// -------------------------------------------------------------- 
// BYCALL() FIND CALL SIGNS BY STRING 
// DISPLAYS ALL MATCHES, ALLOWS MULTIPLE CHECKINS 
// FEATURE NOT SHOWN IN HELP LINE: WILL ACCEPT CONTROL-CODE 
// CURSOR MOVEMENT COMMANDS, TAB CHECKS IN, FOR LEFTHANDED OPERATION. 
// RETURNS: TRUE IF ANY SELECTED, FALSE IF NONE 
// -------------------------------------------------------------- 
int bycall(char *string)
						// STRING TO MATCH, OR THE LOWERCASE WORD "ALL" 
{
int ch;               	 // USER INPUT 
int foundflag = FALSE;	 // WHETHER ANY DISPLAYED WERE SELECTED 
int allflag = FALSE;  	 // FALSE = MATCH SEARCH STRING.  TRUE = SELECT ALL 
int i;                	 // COUNTER & USED AS INDEX LATER 
int k;              	 // COUNTERS
int fi[MAXCALLS];    	 // ARRAY HOLDS INDEXES OF MATCHES FOUND 
int fc = 0;          	 // COUNTS NUMBER OF MATCHES FOUND 
int page;            	 // EACH PAGE HOLDS 96 ENTRIES 
char helpline[81];   	 // HOLDS HELP TEXT 

strcpy((char *)helpline,
	"2468 7/1=t/b F2/F6=city F4=agenda F7=edit M=memo N=new RETURN=quit \"ACLOPTVX 5\"");

if(!strcmp(string,"all"))     // LOWER CASE "ALL" SIGNALS SELECT ALL 
	allflag = TRUE;

for(i = 0 ; i < dbc ; i++)       // FIRST FIND ALL CALLS CONTAINING STRING 
	if((strstr(db[i].call,string)) || (allflag))    // OR ALL, IF FLAG IS ON 
	{
		if(filteron && (db[i].ck > ' '))    // IF FILTER, OMIT THOSE CHECKED IN 
			continue;       
		fi[fc++] = i;                       // OTHERWISE ADD INDEX TO ARRAY 
	}                          
	if(fc == 0)                        // NO MATCHES FOUND 
		return(FALSE);  
									   // SORT BY CALL SIGN
qsort((void *)(&(fi[0])),fc,sizeof(int),alphasort);   
page = -1;                  // FLAG THAT A PAGE HASN'T BEEN SELECTED YET 
i = 0;                      // SET TO FIRST RECORD (array element) 
while(1)                    // DO FOREVER. ALL EXITS ARE IN SWITCH 
{
	if((i / 96) != page)    // SEE IF PAGE CHANGE IS NECESSARY 
	{                       // IF SO, DO IT, AND DRAW SCREEN FOR THAT PAGE
							// K COUNTS THE (UP TO) 96 ENTRIES FOR THE PAGE 
		page = (i / 96);    // first page is page 0 
		clrscr();
		textcolor(GREEN);
		erase25th();
		cprintf("%s",(char *)helpline);
		for(k = page * 96 ; ((k < fc) && (k < (page * 96 + 96))) ; k++)                 
		{                                                       // 4 COLUMNS 
			gotoxy(((k % 96) / 24) * 20 + 1,((k % 96) % 24) + 1);   
			displayrec(fi[k]);                              
		}
	}                                  // PUT CURSOR AT CURRENT RECORD 
	gotoxy(((i % 96) / 24) * 20 + 1,((i % 96) % 24) + 1);
	switch(ch = ci())
	{
		case(17):                   // CONTROL-Q 
		case(HOME):                 // KEYPAD 7 = TOP OF PAGE ONLY 
			i = page * 96;          // IF WE'RE ON THE PAGE, WE MUST HAVE 
			break;                  // GOTTEN THERE LEGALLY ALREADY 

		case(26):                   // CONTROL-Z
		case(END):                  // KEYPAD 1 = BOTTOM OF PAGE ONLY 
			i = min((page * 96 + 95),fc - 1);       // PAGE END OR LIST END 
			break;

		case(1):                    // CONTROL-A 
		case(LEFT):                 // KEYPAD 4 = LEFT ARROW 
			if(i-24 < 0) break;     // BUT NOT BELOW ARRAY BASE
			i -= 24;                // PAGE CHANGE CAN OCCUR HERE 
			break;

		case(6):                    // CONTROL-F 
		case(RIGHT):                // KEYPAD 6 = RIGHT ARROW 
			if(i+24 > fc-1) break;  // BUT NOT BEYOND ARRAY TOP 
			i += 24;                // PAGE CHANGE CAN OCCUR HERE 
			break;

		case(5):                    // CONTROL-E 
		case(UP):                   // KEYPAD 8 = UP ARROW 
			if(--i < 0) i = fc - 1; // WRAP AROUND TO BOTTOM OF ARRAY 
			break;                  // PAGE CHANGE CAN OCCUR HERE 

		case(24):                   // CONTROL-X 
		case(DOWN):                 // KEYPAD 2 = DOWN ARROW 
			if(++i > fc-1) i = 0;   // WRAP AROUND TO TOP OF ARRAY 
			break;                  // PAGE CHANGE CAN OCCUR HERE 

								// LEGAL CHARACTERS FOR CHECKING IN 
		case 'l':               // DEFCH CAN BE ONE OF THESE
		case 'o':
		case 'p':
		case 'x':
			defch = ch;
								// DEFCH CANNOT BE ANY OF THESE 
		case 'a':                       
		case 'c':
		case 't':
		case 'v':
								// IT'S OK TO TEST .CK BECAUSE 
								// IT HASN'T BEEN CHANGED TO CH YET 
								// IF IT'S A NEW CHECKIN ONLY, 
			if(db[fi[i]].ck == ' ')     
				db[fi[i]].ord = ckinord++;     // INCREMENT COUNT 

		case ' ':
			db[fi[i]].ck = ch;  // FINALLY, WE CAN CHANGE .CK 

		case(9):        // TAB SAME AS THE TWO BELOW 
		case(FIVE):     // KEYPAD 5 CHECKS IN WITH DEFCH 
						// MUST TEST BECAUSE IT MIGHT HAVE FALLEN THROUGH 
						// FROM ABOVE 
			if((ch == FIVE) || (ch == 9))
			{
				db[fi[i]].ck = defch;             // CHECKIN WITH DEFCH 
				db[fi[i]].ord = ckinord++;        // AND LOG CHECKIN ORDER 
			}
			handletac(fi[i]);   	              // PROCESS T, A, OR C

			gotoxy(((i % 96) / 24) * 20 + 1,((i % 96) % 24) + 1);
			displayrec(fi[i]);
			if(fc == 1)                  // SELECTED THE ONLY MATCH 
				return(TRUE);                   
			foundflag = TRUE;            // ELSE NOTE THAT ONE WAS SELECTED 
			textcolor(GREEN);
			erase25th();
			cprintf("%s",(char *)helpline);
			break;                        // AND CONTINUE 

		case 'm':                         // M = CREATE A MEMO 
			memo("",FALSE);
			textcolor(GREEN);     
			erase25th();
			cprintf("%s",(char *)helpline);
			break;

		case(13):                          // RETURN FROM FUNCTION 
			return(foundflag);      

		case 'f':                           // TOGGLE FILTER 
			filteron = !filteron;
			break;

		default:        // INCLUDES ALL COMMANDS FOR MAIN MENU 
			return(ch); // BUT ALSO MEANS ANY MISTAKES WILL CAUSE EXIT 
	}                   // END SWITCH
}                       // END WHILE(1) 
}                       // END bycall() 

// -------------------------------------------------------------- 
// EDITREC() ALLOWS EDITING OF A DB RECORD 
// RETURNS: TRUE IF RECORD WAS FOUND IN DB, FALSE IF NOT 
// -------------------------------------------------------------- 
int editrec(char *c)
								// CALL SIGN TO FIND AND EDIT 
{
int i, j;
int found = FALSE;              // WHETHER CALL IS IN DB 
int needsort = FALSE;   // WHETHER DATABASE NEEDS SORTING 
char buf[40];
char *p;

for(i = 0 ; i < dbc ; i++)              // SEARCH DB FOR MATCHING CALL 
	if(!strcmp(db[i].call,c))       // IF FOUND, MAKE NOTE & EXIT 
	{
		found = TRUE;
		break;
	}
if(!found)                       // CAN'T EDIT A RECORD THAT'S NOT THERE 
	return(FALSE);

found = i;                      // SET TO INDEX OF FOUND RECORD 
erase25th();
textcolor(WHITE);
cprintf("Correct CALL or <CR> for %s: ",db[found].call);
buf[0] = 7;
p = cgets((char *)buf);
if(strlen(p))
{
	needsort = TRUE;
	strupr(p);
	strcpy(db[found].call,p);
}
erase25th();
cprintf("Correct NAME, or <CR> for %s: ",db[found].name);
buf[0] = 11;
p = cgets((char *)buf);
if(strlen(p))
{
	strupr(p);
	strcpy(db[found].name,p);
}
erase25th();
cprintf("Correct CITY, or <CR> for %s: ",db[found].city);
buf[0] = 16;
p = cgets((char *)buf);
if(strlen(p))
{
	needsort = TRUE;
	strupr(p);
	strcpy(db[found].city,p);
}
for(j = 0 ; j < citycount ; j++)                // LOOK FOR CITY 
	if(!strcmp(cities[j].n,db[found].city))     // IF FOUND, 
	{
		j = -1;                    // MAKE NOTE & EXIT LOOP 
		break;               
	}
if(j != -1)              // IF CITY WASN'T FOUND, ADD IT 
	addcity(p,FALSE);    // ADDCITY() WILL RE-SORT THE CITIES 

if(needsort)
	qsort(db,dbc,sizeof(struct hams),citycall);     
updateflag = TRUE;                 // MARK DB AS CHANGED 
return(TRUE);
}

// -------------------------------------------------------------- 
// WRITEDB() WRITE CONTENTS OF CHANGED DATABASE TO FILE CALLS.DAT 
// RETURNS: TRUE IF OK, FALSE ON ERROR 
// -------------------------------------------------------------- 
int writedb(void)
{
int i;
FILE *outfile;

unlink("CALLS.BAK");
rename("CALLS.DAT","CALLS.BAK");
outfile = fopen("CALLS.DAT","w");
for(i = 0 ; i < dbc ; i++)             // write all calls in db to disk
	if(db[i].ck != 'v')                // EXCLUDE VISITORS 
		if(fprintf(outfile,"\"%s\",\"%s\",\"%s\"\n",
			db[i].city,db[i].call,strupr(db[i].name)) == ERR)
		{
			fclose(outfile);
			return(FALSE);
		}
if(fclose(outfile) == ERR)
	return(FALSE);
return(TRUE);
}

// -------------------------------------------------------------- 
// LISTALL() LIST EVERYONE WHO'S CHECKED IN 
// RETURNS: A CHAR THAT CAN BE A VALID MAIN MENU COMMAND 
// -------------------------------------------------------------- 
int listall(char s)
				// SPECIFIES SORT ORDER: A=ALPHA, OTHER=CHECKIN ORDER 
{
int ch;                 // VALUE TO RETURN 
int i;                  // COUNTER 
int found[MAXCALLS];    // INDEXES WITHIN DB OF THOSE WHO CHECKED IN 
int checkins;           // # CHECKINS, ALSO NUMBER OF ACTIVE IN FOUND[] 
int traffic;            // COUNT OF ANNOUNCEMENTS + TRAFFIC 

checkins = traffic = 0;
clrscr();
									// IDENTIFY THE CHECKINS
for(i = 0 ; i < dbc ; i++)          // SEARCH DB 
	if(db[i].ck > ' ')              // IF A CHECKIN, ADD ITS INDEX 
		found[checkins++] = i;      // TO FOUND ARRAY 

if(s == 'a')                        // SORT THE LIST 
	qsort((void *)(&(found[0])),checkins,sizeof(int),alphasort);
else
	qsort((void *)(&(found[0])),checkins,sizeof(int),ordersort);

for(i = 0 ; i < checkins ; i++)     // DISPLAY THE LIST 
{                                                                       
	if((i % 96 == 0) && (i != 0))  // ALLOW FULL LIST DISPLAY 
	{                              // USING MULTIPLE PAGES 
		erase25th();               // BUT NO PAGING BACK THROUGH LIST 
		cprintf("More...");
		getch();
		clrscr();
	}                           	 // ROWS 1-24 AND COLUMNS 0,20,40,60 
	gotoxy(((i % 96) / 24) * 20 + 1,((i % 96) % 24) + 1);
	textcolor(LIGHTBLUE);        
	if(db[found[i]].need > ' ') textcolor(MAGENTA); 
	if(strchr("ta",db[found[i]].ck))
	{
		textcolor(YELLOW);
		traffic++;
	}
	cprintf("%c%c %-6.6s %-9.9s",
		toupper(db[found[i]].ck),db[found[i]].need,db[found[i]].call,db[found[i]].name);
}
textcolor(LIGHTGRAY);
erase25th();
cprintf("%d Checkins.  %d Traffic.  ",checkins,traffic);
textcolor(GREEN);
cprintf("Enter command, or <CR> for menu: ");
textcolor(LIGHTGRAY);
ch = ci();
erase25th();
return(ch);
}                                               // end listall() 

// -------------------------------------------------------------- 
// DOCITY() DRAWS PAGE CONTAINING INFO FOR ONE CITY 
// AND ALLOWS CHECKINS FOR THAT CITY 
// FEATURES NOT SHOWN IN HELP LINE: WILL ACCEPT CONTROL-CODE 
// CURSOR MOVEMENT COMMANDS, TAB CHECKS IN, FOR LEFTHANDED OPERATION 
// RETURNS: int, WHICH CAN DETERMINE NEXT CITY TO SHOW, IF ANY, 
// OR BE A VALID MAIN MENU COMMAND 
// -------------------------------------------------------------- 
int docity(void)
{
int b;                // INDEX WITHIN DB[] OF FIRST NAME FOR CITY 
int i;                // MISC. COUNTER & OFFSET-FROM-B POINTER 
int k;                // MISC. COUNTER 
int count;            // NUMBER OF CALLS LISTED FOR CITY 
int page;             // EACH PAGE HOLDS 92 ENTRIES 
int ch;               // USER INPUT
char buf[40];         // TEXT INTENDED FOR MEMO 
char helpline[81];              
char help2[81];

strcpy((char *)helpline,
	"2468 3+9-7t1b RET=quit F1=find F6=cities F7=edit Memo New ==resume \"ACLOPTVX 5\"");
strcpy((char *)help2,
	"NO ENTRIES: 3/9=+/- RET=quit F1=find F6=cities F7=edit Memo New ==resume");

// SEARCH DB TO FIND FIRST ENTRY (IF ANY) FOR CITY 
i = 0;
while((i < dbc) && (strcmp(db[i].city,cities[cindex].n)))
	i++;
b = i;                      // B = INDEX OF FIRST ENTRY FOR THAT CITY 

clrscr();                  // DISPLAY CITY NAME HEADING 
textcolor(WHITE);                       
cprintf("%s",cities[cindex].n);
if(cities[cindex].tflag)        // SHOW WHETHER THERE'S TRAFFIC 
{
	textbackground(BLUE);
	textcolor(WHITE);
	gotoxy(20,1);
	cprintf("%d TRAFFIC",(int)(cities[cindex].tflag));
	textbackground(BLACK);
}
textcolor(GREEN);
gotoxy(70,1);
_strtime((char *)buf);
cprintf("%s",(char *)buf);
if(b == dbc)    // IF NO ENTRIES, CAN ONLY ADD, OR DECIDE HOW TO RETURN 
{
	textcolor(GREEN);
	erase25th();
	cprintf("%s",(char *)help2);
	while(1)
	{
		switch(ch = ci())
		{
			case '=':               // RESUME WITH RESUMECITY 
				return(F2);         // THIS WILL BECOME ALTCH IN MAIN 

			case 'm':         	    // M = CREATE A MEMO 
				memo("",FALSE);
				textcolor(GREEN);
				erase25th();
				cprintf("%s",(char *)help2);
				break;

			default:         // INCLUDES ALL VALID COMMANDS PROCESSED IN MAIN 
				return(ch);                                                             
		}                     // END SWITCH 
	}                         // END WHILE USER INPUT 
}                             // END IF B == DBC 

// WILL ONLY FALL THROUGH TO HERE IF CITY HAS ENTRIES
					// COUNT HOW MANY 
i = b;          
while((i < dbc) && (!strcmp(db[i].city,cities[cindex].n)))
	i++;
count = i - b;

page = -1;                  // FLAG THAT A PAGE HASN'T BEEN SELECTED YET 
i = 0;                      // I IS USED TO CALC. OFFSET FROM BASE 
while(1)        
{
	if((i / 92) != page)    // SEE IF PAGE CHANGE IS NECESSARY 
	{                       // IF SO, CHANGE PAGE AND REDRAW SCREEN 
		page = i / 92;
		clrscr();                                                       
		textcolor(GREEN);
		gotoxy(70,1);				// refresh time display 
		_strtime((char *)buf);
		cprintf("%s",(char *)buf);
		erase25th();						// redraw help line 
		cprintf("%s",(char *)helpline);
		textcolor(WHITE);      			  // redraw CITY NAME HEADING 
		gotoxy(1,1);
		cprintf("%s",cities[cindex].n);
		if(cities[cindex].tflag)          // SHOW WHETHER THERE'S TRAFFIC 
		{
			textbackground(BLUE);
			textcolor(WHITE);
			gotoxy(20,1);
			cprintf("%d TRAFFIC",(int)(cities[cindex].tflag));
			textbackground(BLACK);
		}
		for(k = page * 92 ; ((k < count) && (k < (page * 92 + 92))) ; k++)
		{
			gotoxy(((k % 92) / 23) * 20 + 1,((k % 92) % 23) + 2);           
			displayrec(b+k);
		}
	}                                 // CURSOR TO CURRENT RECORD 
	gotoxy(((i % 92) / 23) * 20 + 1,((i % 92) % 23) + 2);           
	switch(ch = ci())
	{
		case(17):                    // CONTROL-Q 
		case(HOME):                  // KEYPAD 7 = GO TO TOP OF PAGE ONLY 
			i = page * 92;
			break;

		case(26):                    // CONTROL-Z 
		case(END):                   // KEYPAD 1 = GO TO BOTTOM OF PAGE 
			i = min((page * 92 + 91),count - 1);
			break;

		case(1):                     // CONTROL-A 
		case(LEFT):                  // 4 LEFT ARROW 
			if(i-23 < 0) break;      // FELL BELOW BASE 
			i -= 23;                 // COLUMN ENTRIES 23 RECORDS APART 
			break;                   // PAGE CHANGE CAN OCCUR HERE 

		case(6):                        // CONTROL-F 
		case(RIGHT):                    // 6 RIGHT ARROW 
			if(i+23 > count-1) break;   // BEYOND END OF THIS CITY 
			i += 23;                    // PAGE CHANGE CAN OCCUR HERE 
			break;

		case(5):                        // CONTROL-E 
		case(UP):                       // 8 UP ARROW 
			if(--i < 0) i = count-1;    // WRAP AROUND TO ARRAY BOTTOM 
			break;                      // PAGE CHANGE CAN OCCUR HERE 

		case(24):                       // CONTROL-X 
		case(DOWN):                     // 2 DOWN ARROW 
			if(++i > count-1) i = 0;    // WRAP BACK TO ARRAY TOP 
			break;                      // PAGE CHANGE CAN OCCUR HERE 

									// LEGAL CHECKIN CHARACTERS 
		case 'l':
		case 'o':
		case 'p':
		case 'x':
			defch = ch;             // COPY TO DEFAULT 
									// DEFCH CANNOT BE ANY OF THESE 
		case 'a':                               
		case 'c':
		case 't':
		case 'v':

			if(db[b+i].ck == ' ')               // IF A NEW CHECKIN, 
				db[b+i].ord = ckinord++;        // LOG PLACEMENT & INCREMENT 

		case ' ':
			db[b+i].ck = ch;                

		case(9):        // TAB SAME AS THE TWO BELOW 
		case(FIVE):     // KEYPAD 5 CHECKS IN WITH DEFCH 
						// MUST TEST BECAUSE IT MIGHT BE HERE FROM ABOVE 

			if((ch == FIVE) || (ch == 9))
			{
				db[b+i].ck = defch;             // LOG IN WITH DEFAULT CHAR 
				db[b+i].ord = ckinord++;        // AND INCREMENT COUNTER 
			}
			handletac(b+i);                     // PROCESS T, A, OR C 

// to do:
// if(handletac(b+i) == 'c') see if requested person is in this city, 
// if so, locate where they are on the screen, & change their color. 

			gotoxy(((i % 92) / 23) * 20 + 1,((i % 92) % 23) + 2);           
			displayrec(b+i);
			textcolor(GREEN);
			erase25th();
			cprintf("%s",(char *)helpline);
			break;

		case '=':                   // RETURN TO RESUMECITY 
			return(F2);             // THIS WILL BECOME ALTCH IN MAIN 

		case 'm':                   // M = CREATE A MEMO 
			memo("",FALSE);
			textcolor(GREEN);
			erase25th();
			cprintf("%s",(char *)helpline);
			break;

		default:           // INCLUDES ALL VALID COMMANDS PROCESSED IN MAIN 
			return(ch);     
	}                      // END SWITCH 
}                          // END WHILE CHARACTERS ARE BEING ENTERED 
}                          // END FUNCTION 

// -------------------------------------------------------------- 
// CITYMENU()  DISPLAYS LIST OF CITIES TO SELECT FROM 
// RETURNS: CHAR -- F2 IF SHOULD GO TO DOCITY() UPON RETURN, 
// OR RETURNS ANY CHAR ENTERED, WHICH CAN BE A MAIN MENU COMMAND 
// -------------------------------------------------------------- 
int citymenu(void)
{
int ch;                   // USER INPUT 
int i;                    // COUNTER & USED AS INDEX LATER 
int k;                    // COUNTER 
int page;                 // EACH PAGE HOLDS 96 ENTRIES 

page = -1;                 // FLAG THAT A PAGE HASN'T BEEN SELECTED YET 
i = cindex;                // SET TO CINDEX 
while(1)                   // DO FOREVER. ALL EXITS ARE IN SWITCH 
{
	if((i / 96) != page)    // SEE IF PAGE CHANGE IS NECESSARY 
	{                       // IF SO, DO IT, AND DRAW SCREEN FOR THAT PAGE 
							// K COUNTS THE (UP TO) 96 ENTRIES FOR THE PAGE 
		page = (i / 96);        
		clrscr();
		textcolor(GREEN);
		erase25th();
		cprintf("2468 7t1b  TAB|5|ENTER=goto this city   OTHER=valid command or quit");
		for(k = page * 96 ; ((k < citycount) && (k < (page * 96 + 96))) ; k++)                  
		{                                                         // 4 COLUMNS 
			gotoxy(((k % 96) / 24) * 20 + 1,((k % 96) % 24) + 1);   
			textcolor(RED); 
			if(cities[k].tflag) textcolor(YELLOW);
			cprintf("%s",cities[k].n);
		}
	}                             // PUT CURSOR AT CURRENT RECORD 
	gotoxy(((i % 96) / 24) * 20 + 1,((i % 96) % 24) + 1);
	switch(ch = ci())
	{
		case(17):                 // CONTROL-Q 
		case(HOME):               // KEYPAD 7 = TOP OF PAGE ONLY 
			i = page * 96;        // IF WE'RE ON THE PAGE, WE MUST HAVE 
			break;                // GOTTEN THERE LEGALLY ALREADY 

		case(26):                  // CONTROL-Z 
		case(END):                 // KEYPAD 1 = BOTTOM OF PAGE ONLY 
			i = min((page * 96 + 95),citycount - 1);  // PAGE END OR LIST END 
			break;

		case(1):                  // CONTROL-A 
		case(LEFT):               // KEYPAD 4 = LEFT ARROW 
			if(i-24 < 0) break;   // BUT NOT BELOW ARRAY BASE 
			i -= 24;      
			break;

		case(6):                  // CONTROL-F 
		case(RIGHT):              // KEYPAD 6 = RIGHT ARROW 
			if(i+24 > citycount-1) break;   // BUT NOT BEYOND ARRAY TOP 
			i += 24;      
			break;

		case(5):                           // CONTROL-E 
		case(UP):                          // KEYPAD 8 = UP ARROW 
			if(--i < 0) i = citycount - 1; // WRAP AROUND TO BOTTOM OF ARRAY 
			break;        

		case(24):                        // CONTROL-X 
		case(DOWN):                      // KEYPAD 2 = DOWN ARROW 
			if(++i > citycount-1) i = 0; // WRAP AROUND TO TOP OF ARRAY 
			break;        

		case(9):                   // TAB = SELECT & GO TO THIS CITY
		case(FIVE):                // KEYPAD 5 = SAME 
			cindex = i;
			return(F2);     

		default:
			return(ch);           // ALLOW MAIN MENU COMMANDS 
	}                             // END SWITCH 
}                                 // END WHILE(1) 
}                                 // END FUNCTION 

// -------------------------------------------------------------- 
// DRAWHELP() DISPLAYS HELP SCREEN 
// -------------------------------------------------------------- 
void drawhelp(void)
{
int i, checkins, traffic;       // SESSION TOTALS 
char buf[9];                            // HOLDS TIME 
erase25th();                            // ERASE ANY LEFTOVER TEXT 
clrscr();
textcolor(LIGHTGRAY);
cprintf("Help Screen:                             Checkin Codes:");
_strtime((char *)buf);
gotoxy(70,1);
textcolor(GREEN);
cprintf("%s\n\r\n",buf);
textcolor(LIGHTGRAY);
cprintf("F1  search for CALL by string            A - Announcement\n\r");
cprintf("F2  check in by CITY                     C - Contact\n\r");
cprintf("F3  search for CALL from entire list     L - Late\n\r");
cprintf("F4  CURRENT section of AGENDA            O - In & Out\n\r");
cprintf("9/PgUp PREVIOUS section of AGENDA        P - Portable\n\r");
cprintf("3/PgDn NEXT section of AGENDA            T - Traffic\n\r");
cprintf("N   add New person to database           V - Visitor (won't add to db)\n\r");
cprintf("A,L list checkins by Alpha or # order    X - Regular checkin\n\r");
cprintf("=   go to a particular city              (space) - Remove mistaken checkin\n\r");     
cprintf("F6  select city by menu                  \n\r");
cprintf("F7  edit a record                        \n\r");
cprintf("F9  run COMMAND.COM                \n\r");
cprintf("                    TAB | ENTER | keypad 5 - Check in w/default char.\n\r");            
cprintf("P   Print formatted roster to disk\n\r");
cprintf("M   enter a memo\n\r");
cprintf("F10 display memos                        Q   Quit program. \n\r");
cprintf("F   Toggle checkin filter, now ");
textcolor(GREEN);
cprintf("%s.\n\r",filteron?"ON":"OFF");
textcolor(LIGHTGRAY);
cprintf("Selected city is     ");
textcolor(GREEN);
cprintf("%s\n\r",cities[cindex].n);
textcolor(LIGHTGRAY);
cprintf("Roll is completed to ");
cprintf("%s\n\r",cities[resumecity].n);
textcolor(LIGHTGRAY);
checkins = traffic = 0;
for(i = 0 ; i < dbc ; i++)
	if(db[i].ck > ' ')
	{
		checkins++;
		if(strchr("at",db[i].ck))
			traffic++;
	}
textcolor(GREEN);
cprintf("%d ",checkins);
textcolor(LIGHTGRAY);
cprintf("Checkins.  ");
textcolor(GREEN);
cprintf("%d ",traffic);
textcolor(LIGHTGRAY);
cprintf("Traffic.\n\r");
return;
}

// -------------------------------------------------------------- 
// loadagenda()    1-11-94    reads agenda text from disk 
// returns TRUE if successful, FALSE on error 
// -------------------------------------------------------------- 
int loadagenda(void)
{
char buf[81];
FILE *infile;

if((infile = fopen("preamble.txt","r")) == NULL)
	return(FALSE);
agendacount = 0;
while(fgets((char *)buf,81,infile))
{
	agenda[agendacount] = strdup((char *)buf);   // malloc space 
	if(!agenda[agendacount])
		return(FALSE);                             // out of memory 
	if(++agendacount == MAXAGENDA)
		return(FALSE);                       // too many lines in file 
}
fclose(infile);
return(TRUE);
}

// -------------------------------------------------------------- 
// printroster()   prints formatted roster to disk 
// returns TRUE if succeeds, FALSE on disk error 
// -------------------------------------------------------------- 
int printroster(void)
{
int i;
char lastcity[16];
FILE *outfile;

if((outfile = fopen("CALLS.PRN","w")) == NULL)
	return(FALSE);
lastcity[0] = '\0';
for(i = 0 ; i < dbc ; i++)            // write all calls in db to disk 
{                                     // city names all already upper case 
	if(strcmp((char *)lastcity,db[i].city))         // city heading
		if(fprintf(outfile,"\n--> %s\n\n",db[i].city) == ERR)
		{
			fclose(outfile);
			return(FALSE);
		}
	if(db[i].ck != 'v')                         // EXCLUDE VISITORS 
	{
		if(fprintf(outfile,"    %-6s %-s\n",
			db[i].call,strupr(db[i].name)) == ERR)
		{
			fclose(outfile);
			return(FALSE);
		}
	}
	strcpy((char *)lastcity,db[i].city);       // make current city lastcity 
}
if(fclose(outfile) == ERR)
	return(FALSE);
return(TRUE);
}

// -------------------------------------------------------------- 
// MAIN() 
// -------------------------------------------------------------- 
void main(void)
{
int ch;		            // USER INPUT 
char buf[81], buf2[10]; // USER INPUT AND TIME
char *p;
int altch;              // RETURNED BY FUNCTION TO INDICATE TO GO DIRECTLY 
						// TO NEXT FUNCTION.  BYPASSES USER INPUT. 
int i;                  // COUNTER 
int agendastart = 0;    // line # with which to start display 
FILE *outfile;

zArray((char *)buf);                        // VARIABLE INITIALIZATIONS 
memoptr = ckinord = dbc = resumecity = cindex = 0;
contacts = '0';                                 
filteron = updateflag = FALSE;
defch = 'x';            
altch = 0;

textmode(C80);
textbackground(BLACK);
textcolor(LIGHTGRAY);
clrscr();
if(!loadagenda())
	aborts("Error reading file PREAMBLE.TXT.  (Not there or too long.)");
loaddb();                  // LOAD CALLS.DAT + CREATE CITIES LIST 
while(1)                   // DO FOREVER.  EXIT IS CASE 'Q'UIT 
{
			// IF A FUNCTION SETS ALTCH, WE'LL GO DIRECTLY 
			// TO THAT CASE, BYPASSING USER INPUT 
			// (THIS IS ALSO THE TRICK I NEEDED IN EDIT.C TO PROCESS MACROS) 
	if(altch)               
	{
		ch = altch;                  // TRANSFER TO CH 
		altch = 0;                   // RESET ALTCH 
	}
	else  
	{     
		drawhelp();
		ch = ci();                   // NEED USER INPUT 
	}
	switch(ch)
	{
		case(PGDN):                        // pgdn = next 23 lines 
			agendastart += 23;
			if(agendastart >= agendacount)
				agendastart = 0;
			altch = F4;                    // go display them 
			break;

		case(PGUP):                        // pgup = previous 23 lines 
			agendastart -= 23;
			if(agendastart < 0)
				agendastart = 0;
			altch = F4;
			break;

		case(F4):                 // F4 show current 23 lines of NET AGENDA 
			clrscr();
			textcolor(LIGHTGRAY);
			for(i = agendastart ; (i < (agendastart + 23)) && (i < agendacount) ; i++)
				cprintf("%s\r",agenda[i]);        
			textcolor(GREEN);
			erase25th();
			gotoxy(70,25);					// display time 
			_strtime((char *)buf2);
			cprintf("%s\r",(char *)buf2);
			gotoxy(1,25);
			cprintf("PgUp/PgDn, Command, or <CR> for menu: ");
			altch = ci();              // ALLOW ALL MENU COMMANDS 
			break;

		case(F9):                        // F9 = RUN COMMAND.COM 
			clrscr();
			spawnvp(P_WAIT,"command.com",NULL);
			clrscr();
			break;

		case 'm':                        // M = ENTER A MEMO 
			memo("",FALSE);
			break;

		case(F10):                        // F10 = DISPLAY MEMOS 
			altch = showmemos();          // ALLOW MENU COMMANDS 
			break;

		case(F7):                         // F7 = EDIT A DB RECORD 
				textcolor(WHITE);
				erase25th();
				cprintf("<EDIT> Find call or <CR> to quit: ");
				buf[0] = 7;
				p = cgets((char *)buf);
				if(strlen(p)) 
					editrec(strupr(p));
				break;

		case(F1):                   // F1 CHECKS IN BY CALL SIGN 
		case(F3):                   // F3 SIGNALS DISPLAY ENTIRE LIST 
			while(!altch)           // 2 EXITS: USER ABORTS OR ALTCH GETS SET 
			{
				if(ch == F3)        // F3 REQUIRES NO INPUT 
				{
					p = (char *)buf;
					strcpy(p,"all");      
				}
				else                                    
				{                                               
					textcolor(WHITE);
					erase25th();
					cprintf("Find Call or <CR> to quit: ");
					buf[0] = 7;
					p = cgets((char *)buf);
					if(strlen(p) == 0)    // USER ABORTED 
						break;  
					strupr(p);
				}
				switch(ch = bycall(p))
				{
									// IF TRUE OR FALSE, STAY WITHIN 
									// WHILE LOOP FOR MORE SEARCHES 
					case(FALSE):    // NONE (IF ANY) FOUND WERE SELECTED 
					case(TRUE):             // AT LEAST ONE WAS SELECTED 
						break;
									// ANYTHING ELSE IS A COMMAND 
					default:                // TO EXIT LOOP AND GO ELSEWHERE 
						altch = ch;
						break;
				}               
			}                      // END WHILE NOT ALTCH 
			break;

		case(F2):                     // F2 CHECKS IN BY CITY 
					// && EVALS LEFT TO RIGHT. IF ALTCH, DOCITY() NOT DONE 
			while(!altch && ((ch = docity()) != 13))
			{                                                               
				switch(ch)
				{
					case(PGDN):   		  // KEYPAD 3 = GO TO NEXT CITY 
					case(3):           	  // CONTROL-C SAME 
						cindex++;         // TEST FOR OVER-RUN IS BELOW 
						resumecity = cindex; // LAST CITY REACHED 
						break;

					case(PGUP):          // KEYPAD 9 = GO TO PREVIOUS CITY 
					case(18):            // CONTROL-R SAME
						cindex = max(cindex-1,0);       // BUT NOT BELOW 0 
						break;

					case 'n':            // ADD NEW CALL TO THIS CITY 
						addnew(cities[cindex].n);     // THEN REDRAW THE CITY 
						break;          // AND GO BACK TO IT 
					

					case(F2):                   // GO TO DOCITY() 
						cindex = resumecity;    // SET CITY TO RESUME AT 
					default:                    // DEFAULT INCLUDES 
						altch = ch;            // MENU COMMANDS RETURNED 
						break;
				}                             // END SWITCH 
				if(cindex == citycount)       // LAST CITY WAS JUST DONE 
				{                             // AND WAS EXITED WITH PGDN 
					resumecity = cindex = 0;  // RESET POINTERS 
					altch = F1;               // GO DO LATE/MISSED 
				}                             // TO MAIN MENU 
			}              // END WHILE CH = DOCITY() AND NOT ALTCH 
			break;                                          

		case 'n':                    // N = ADD NEW PERSON TO DATABASE 
			addnew(cities[cindex].n);       // DEFAULT CITY 
			break;

		case 'l':                  // L = LIST CHECKINS IN NUMERICAL ORDER 
		case 'a':                  // A = LIST CHECKINS IN ALPHA ORDER 
			altch = listall(ch);    // ALLOW MENU COMMANDS
			break;

		case 'f':                   // TOGGLE NOT-CHECKED-IN FILTER 
			filteron = !filteron;   // FOR BYCALL() 
			break;

		case(F6):                           // F6 = SELECT FROM CITY MENU 
			altch = citymenu();             // MENU COMMANDS ALLOWED 
			break;

		case '=':                   // = GO DIRECTLY TO SPECIFIC CITY NAME 
			textcolor(WHITE);
			erase25th();
			cprintf("City name or <CR> to quit: ");
			buf[0] = 16;
			p = cgets((char *)buf);
			if(strlen(p) == 0)        // USER ABORTED 
				break;          
			strupr(p);
			for(i = 0 ; i < citycount ; i++)        // LOOK FOR IT 
				if(!strcmp(cities[i].n,p))
				{
					cindex = i;        // SET INDEX TO IT 
					altch = F2;        // FLAG TO DO DOCITY() 
					break;             // EXIT FOR LOOP 
				}                      // NO ACTION IF NOT FOUND 
			erase25th();
			break;
			

		case 'p':                       // print roster to disk 
			if(!printroster())
			{
				textcolor(WHITE);
				erase25th();
				cprintf("Disk write error.  Any key...");
				getch();
				erase25th();
			}
			break;

		case 'q':                  // Q QUITS PROGRAM 
			textcolor(WHITE);
			erase25th();
			cprintf("QUIT PROGRAM.  Press ESC to quit: ");
			if(getch() != 27)
			{
				erase25th();
				break;
			}
			clrscr();
								// CREATE .LOG FILE USING TODAY'S DATE 
			_strdate((char *)buf);          // GET DOS DATE 
			for(i = 0 ; i < strlen((char *)buf) ; i++)   // SPACES TO ZEROES 
				if(buf[i] == '/')
					buf[i] = '-';           
			strcat((char *)buf,".LOG");
			outfile = fopen((char *)buf,"w");
			for(i = 0 ; i < dbc ; i++)
				if(db[i].ck > ' ')
					if(fprintf(outfile,"\"%c\",\"%s\",\"%s\"\n",
						toupper(db[i].ck),db[i].call,db[i].name) == ERR)
							cprintf("Possible data loss in %s\n\r",buf);
			fclose(outfile);
			if(updateflag)
				if(!writedb())
					cprintf("Possible data loss in CALLS.DAT.\n\r");
			if(!writememos())                       
				cprintf("Possible data loss in .MEM file.\n\r");
			exit(0);        

		default:
			break;                                  
	}                  // END SWITCH 
}                      // END WHILE CHARS ARE BEING ENTERED 
}                      // END MAIN

LOGTALLY.C

/*	LOGTALLY.C		BORLAND VERSION    7-19-95
	This file is part of the NET project, but is an optional standalone program.
	Copyright (C)1995 Steven Whitney.
	Published under GNU GPL (General Public License) Version 3, with ABSOLUTELY NO WARRANTY.
	Initially published by http://25yearsofprogramming.com.

POSTS DATA FROM A DAY'S .LOG FILE TO A YEARLY MASTER CHECKIN FILE
CONTAINING "CALL","NAME"," (366 CHARACTERS, DAYS IN THE YEAR) "

DAILY .LOG FILE NAME MUST HAVE FORMAT MM-DD-YY.LOG
OUTPUT FILE MUST HAVE FORMAT YYYY.LOG WHERE YYYY IS 4-DIGIT YEAR
FILES DO NOT HAVE TO BE SORTED, AND ARE NOT SORTED BY PROGRAM

FOR BATCH PROCESSING:  for %f in (*.log) do logtally %f

file compiles ok, but hasn't been tested at 12-6-94. This was the last C version before
being converted to C++.

*/
#include <stdio.h>
#include <stddef.h>
#include <dos.h>
#include <stdlib.h>
#include <conio.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <mem.h>
#include "my.h"

#define MAXDAILY	200
// -------------------------------------------------------------- 
//	MAIN() 
// -------------------------------------------------------------- 
void main(int argc, char **argv)
{
struct hams
{
	char call[7];
	char name[11];
	char ck;			// CHECKIN CHARACTER FOR THAT DAY 
	char posted;		// WHETHER RECORD HAS BEEN POSTED TO MASTER 
} s[MAXDAILY], c;		// S = ARRAY FROM DAILY LOG, C = 1 ENTRY FROM MASTER 
int scount;				// NUMBER OF ENTRIES IN S 

char yrdata[367];		// ARRAY SHOWING CHECKIN DATES 
char buf[20], buf2[20];	// HOLDS FILE NAMES & DATE 
int i, j;
int diskerr = FALSE;	// FLAG WHETHER DISK ERROR OCCURRED 
FILE *infile, *outfile;

if((argc != 2) || (strlen(argv[1]) != 12))
	aborts("Usage: LOGTALLY MM-DD-YY.LOG  (include .LOG extension)");
	

// READ DATA FROM DAILY .LOG FILE INTO ARRAY S 

if((infile = fopen(argv[1],"r")) == NULL)
	aborts("Input file not found.");
scount = 0;
puts("\nReading daily log...");
while(fscanf(infile,"\"%c\",\"%s\",\"%[^\"]\"\n",
	&(s[scount].ck),s[scount].call,s[scount].name) != EOF)
{
	s[scount].posted = FALSE;
	if(++scount == MAXDAILY)
		aborts("Too many entries in .log file.  Revise program.");
}
fclose(infile);
								// SET UP IN/OUT MASTER FILES 
sprintf(buf,"19%s",argv[1]+6);	// output file = 19+last two digits from infile
buf[4] = '\0';
if((infile = fopen(strcat(buf,".log"),"r")) == NULL)
	aborts("Input file not found.");
buf[4] = '\0';
if((outfile = fopen(strcat(buf,".new"),"w")) == NULL)
	aborts("Unable to open output file.");

// POST FROM DAILY TO MASTER 
strcpy(buf,argv[1]);					// PUT DATE FROM FILENAME INTO BUF 
buf[8] = '\0';							
puts("\nPosting to master file...");
while(fgetbstr(infile,c.call,6) != ERR)		// GET 1 RECORD FROM MASTER 
{										
	if((fgetbstr(infile,c.name,10) == ERR) || (fgetbstr(infile,yrdata,366) == ERR))
		aborts("Master file corrupt.  Repair and rerun.");
	for(i = 0 ; i < scount ; i++)		// SEARCH S FOR MATCHING CALL 
		if(!strcmp(s[i].call,c.call))	// IF FOUND, UPDATE MASTER RECORD 
		{								
			yrdata[tojulian(buf)-1] = s[i].ck;		// SET DATE MARKER 
			s[i].posted = TRUE;						// FLAG AS POSTED 
			break;									// QUIT FOR LOOP 
		}				// WRITE (POSSIBLY UPDATED) RECORD TO NEW MASTER 
	if(fprintf(outfile,"\"%s\",\"%s\",\"",c.call,c.name) == ERR)
		diskerr = TRUE;
	for(i = 0 ; i < 366 ; i++)				// FPRINTF'S OUTPUT BUFFER 
		if(fputc(yrdata[i],outfile) == ERR)	// IS SHORTER THAN YRDATA, 
			diskerr = TRUE;					// AND SPRINTF CAN'T HANDLE 
	fputs("\"\n",outfile);					// A SINGLE DATA ITEM THAT LONG 
							// find out if Borland also has this limitation 
}
							// WRITE ALL UNPOSTED (NEW) RECORDS TO MASTER
setmem(yrdata,366,' ');					// INIT. YEAR ARRAY TO SPACES 
yrdata[366] = '\0';							// TERMINATE IT 
puts("\nAdding new call sign information...");
for(i = 0 ; i < scount ; i++)				// RUN THROUGH S, 
	if(!(s[i].posted))						// IF RECORD HASN'T BEEN POSTED, 
	{										// ADD ITS DATA TO MASTER 
		yrdata[tojulian(buf)-1] = s[i].ck;	
		if(fprintf(outfile,"\"%s\",\"%s\",\"",s[i].call,s[i].name) == ERR)
			diskerr = TRUE;
		for(j = 0 ; j < 366 ; j++)
			if(fputc(yrdata[j],outfile) == ERR)
				diskerr = TRUE;
		fputs("\"\n",outfile);
	}
if(diskerr || (fcloseall() == EOF))
	aborts("File write error.  Possible data loss.");
else
{
	sprintf(buf,"19%s",argv[1]+6);		// PUT 19XX.LOG IN FILE NAME 
	buf[4] = '\0';						// STRIP TO 19XX 
	strcpy(buf2,buf);					// COPY TO 2ND BUF 
	strcat(buf,".bak");		
	unlink(buf);						// DELETE .BAK IF PRESENT 
	strcat(buf2,".log");
	rename(buf2,buf);					// RENAME OLD .LOG FILE TO .BAK 
	buf[4] = '\0';						// STRIP EXTENSION AGAIN 
	strcat(buf,".new");					
	rename(buf,buf2);					// RENAME .NEW FILE TO .LOG
}
puts("\nDone.\n");
exit(0);
}

LOGSPRED.C

/*	LOGSPRED.C		BORLAND VERSION   7-19-95
	This file is part of the NET project, but is an optional standalone program.
	Copyright (C)1995 Steven Whitney.
	Published under GNU GPL (General Public License) Version 3, with ABSOLUTELY NO WARRANTY.
	Initially published by http://25yearsofprogramming.com.

It compiled, but is not tested. This was the last C version before C++ conversion.

FORMATS DATA FROM YEARLY SUMMARY .LOG FILE -- YYYY.LOG
INTO A SPREADSHEET-TYPE FILE SUITABLE FOR PRINTING -- YYYY.PRN
ONLY DATES FOR WHICH THERE ARE ANY CHECKINS AT ALL WILL BE TABULATED
SO THAT ENTIRE BLOCKS OF BLANKS WON'T BE OUTPUT.
PRINTING IT WILL REQUIRE YET ANOTHER PROGRAM TO PRODUCE
MULTIPLE 80-COLUMN PAGES THAT CAN BE TAPED TOGETHER.

SAMPLE:
1993:
					SMTWtFs		(THURSDAY AND SATURDAY TO BE LOWER CASE)
MONTH:				0000000		READING DOWN: 01=JANUARY
					1111111

DAY:				0000000		READING DOWN: JANUARY 01,02,03,ETC.
					1234567

CALL    NAME    XLAOTCX
	CALL    NAME    LLXXOXP

*/
#include <stdio.h>
#include <stddef.h>
#include <dos.h>
#include <stdlib.h>
#include <conio.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <mem.h>
#include "my.h"

void main(int argc, char **argv)
{
int i, j;
char daysused[367];		// TRACKS DAYS FOR WHICH THERE WERE CHECKINS 
char dd[366][7];		// MMDDYY CALENDAR DATES FOR EACH DAY IN THE YEAR 
char buf[80];							// MISC. 
char call[7], name[11], yrdata[367];	// DATA FROM FILE
char daylets[8];		// first letters of day names 
FILE *infile, *outfile;

if(argc != 2)
	aborts("Use: LOGSPRED YYYY.LOG where yyyy.log is a yearly log file");

strcpy(daylets,"SMTWtFs");
										// LEARN WHICH DAYS HAVE CHECKINS 
puts("\nTabulating from source file...");
if((infile = fopen(argv[1],"r")) == NULL)
	aborts("Input file not found.");
setmem(daysused,366,' ');					// INIT DAYSUSED TO SPACES 
daysused[366] = '\0';						// AND TERMINATE IT 
				// eventually determine if Borland fscanf() will work here 
while(fgetbstr(infile,call,6) != ERR)
{
	if((fgetbstr(infile,name,10) == ERR) || (fgetbstr(infile,yrdata,366) == ERR))
		aborts("Input file corrupt.  Repair and rerun.");

	puts(call);
	for(i = 0 ; i < 366 ; i++)		// IF THERE WAS A CHECKIN THIS DAY, 
		if(yrdata[i] > ' ')			// MARK IT AS A DAY TO REPORT ON 
			daysused[i] = 'x';		// THIS IS LIKE "OR-ING" THE 2 STRINGS 
}
fclose(infile);
									// OPEN THE OUTPUT FILE 
strncpy(buf,argv[1],4);
strcat(buf,".PRN");
if((outfile = fopen(buf,"w")) == NULL)
	aborts("File creation error.");
buf[4] = '\0';
fprintf(outfile,"Summary checkin log for %s\n",buf);

zArray(dd);								// translate julians to dates 
for(i = 0 ; i < 366 ; i++)
	if(daysused[i] > ' ')				// IF THE DAY HAS ENTRIES 
		todate(dd[i],i+1,atoi(buf+2));	// CREATE ITS TEXTUAL CALENDAR DATE 

// DAY OF THE WEEK HEADINGS 
fputs("                    ",outfile);	// 20 SPACES ALLOW FOR CALL + NAME 
for(i = 0 ; i < 366 ; i++)
	if(daysused[i] > ' ')			
		fputc(daylets[dayofwk(i+1,5)],outfile);	// 5=Fri=1st day of 1993 
fputs("\n",outfile);
										// CREATE COLUMN HEADINGS 
puts("Creating headings...");
for(j = 0 ; j < 4 ; j++)				// FOR EACH LINE IN COLUMN HEADINGS
{
	fputs("                    ",outfile);	
	for(i = 0 ; i < 366 ; i++)			// PRINT DATE TEXT VERTICALLY 
		if(daysused[i] > ' ')
			fputc(dd[i][j],outfile);
	fputs("\n",outfile);		
	if(j == 1)
		fputs("\n",outfile);			// BLANK LINE BETWEEN MONTH & DAY 
}
fputs("\n",outfile);	
						// PRINT DATA FROM THE .LOG FILE TO LINES OF TEXT 
puts("Printing to .PRN file...");
infile = fopen(argv[1],"r");			// NO TEST: ALREADY FOUND IT ONCE 
while(fgetbstr(infile,call,6) != ERR)
{
	fgetbstr(infile,name,10);			// ALREADY READ IT, TOO 
	fgetbstr(infile,yrdata,366);

	puts(call);
	fprintf(outfile,"%-6s %-10s   ",call,name);
	for(i = 0 ; i < 366 ; i++)					// IF THIS IS A DAY 
		if(daysused[i] > ' ')					// WE'RE REPORTING ON, 
			fputc(yrdata[i],outfile);			// OUTPUT THE CHECKIN CHAR 
	fputs("\n",outfile);		
}
if(fcloseall() == EOF)
	puts("File write error.  Possible data loss.");
exit(0);
}									// END MAIN

 

 

Valid HTML 4.01 Transitional Valid CSS
Yahoo! Search
Search the web Search this site
View content labeling at ICRA.