|
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 Ads Donate Humor |
|
|
Amateur Radio Net Control programs - C versionsThis is the source code listing for the C 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. 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 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 2 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 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 2, 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 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 2, 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
|
|
|
|
|
|