TIGCC Programming Lessons
Lesson 10: File I/O
Step 3 - Continuation... (finally)
For those of you that read the first part of this lesson, I'm sorry it
took so long to finish the second part. For a long time, I thought I had
lost the source code to the example I was working on, and I didn't feel
like rewriting it. However, I discovered the source code was stored on
one of my network drives in early December, so I finished the program
and now can present the continuation, finally.
Okay, we've presented a simple overview of file I/O using a somewhat
more complex example than normal, now we will extend that model and
create an Address Book. For simplicity, I removed constraint checks (we
don't verify that the data is accurate). The program is complex enough
without adding those. In a real program, you would probably add such
things. Then again, who uses their calculator for an address book
anyways. Well, it's good practice for programming PDA's I guess. Let's
see that brilliant piece of code I spent two months working on (well,
actually, only 3 days total, I just lost it for a month and a half).
Start TIGCC and create a new project. Name the project 'addybook' and
create a C file addybook. Also create a Header file called addybook.
Edit the source file so that it looks like this:
addybook.c
// comment this next line out for TI-92+/V200 support
#define USE_TI89
#ifndef USE_TI89
#define USE_TI92PLUS
#define USE_V200
#endif
#include <dialogs.h>
#include <alloc.h>
#include <menus.h>
#include <stdio.h>
#include <kbd.h>
#include <mem.h>
#include <string.h>
#include <args.h>
#include <stdlib.h>
// add our personal header file
#include "addybook.h"
// a short wrapper for making error messages
inline short int dlgError(short int title, short int msg) {
return (DlgMessage((char *)errors[title],(char *)errors[msg],BT_OK,BT_CANCEL));
}
// write the address book records to file
// action tells us how to handle the current data (new record, remove old, or edit current)
short int writeRecords(short int action) {
FILE *in = NULL, *out = NULL;
short int tempRecordCount = recordCount, adjust = FALSE, copy = TRUE;
PERSON temp;
// rename the address file to a temp filename
rename(ADDRESSBOOK,ADDRESSTEMP);
// open the input file to copy the old records
in = fopen(ADDRESSTEMP,"rb");
// open the new address book file
if ((out = fopen(ADDRESSBOOK,"wb")) == NULL) {
fclose(in);
return FALSE;
}
// decrement the record count if we're removing an entry
if (action == REMOVE_ENTRY) {
tempRecordCount--;
}
// write the new record count
fwrite(&tempRecordCount,sizeof(short int),1,out);
// if we need to copy over old data...
if (in != NULL) {
// find out how many records are already stored
fread(&tempRecordCount,sizeof(short int),1,in);
// loop through the old records and recopy them
while (tempRecordCount-- > 0) {
// read in the old record
memset(&temp,0,sizeof(PERSON));
fread(&temp,sizeof(PERSON),1,in);
// are we removing or editing this record?
if (temp.id == p->id) {
if (action == REMOVE_ENTRY) {
// skip this entry -- adjust additional id's
adjust = TRUE;
copy = FALSE;
} else if (action == EDIT_ENTRY) {
// replace old entry data with new data
memcpy(&temp,p,sizeof(PERSON));
}
}
// decrement the id's of successive items (after the removed one)
if (adjust) {
temp.id--;
}
// if we're copying the record, write it
if (copy) {
// write the old record to the new book file
fwrite(&temp,sizeof(PERSON),1,out);
} else {
// only one reason not to copy, so reenable copying after
// we skip the one we're not copying
copy = TRUE;
}
};
// close the input file
fclose(in);
}
// if we're adding an entry, write it to the file at the end
if (action == NEW_ENTRY) {
fwrite(p,sizeof(PERSON),1,out);
}
// now write the file tag
fputc(0,out);
fputs("ADDY",out);
fputc(0,out);
fputc(OTH_TAG,out);
fclose(out);
// remove the temporary records
unlink(ADDRESSTEMP);
// if we didn't have an error up to now, we're good to go
return TRUE;
}
// display the dialog box to add/edit a record entry
void doEntryDialog(char *buffer, short int action) {
HANDLE dlg = H_NULL;
char *name = buffer + 0, *address = buffer + 26, *city = buffer + 52, *state = buffer + 78;
char *zipcode = buffer + 81, *phone = buffer + 87, *email = buffer + 100;
const char *strings[] = {"Add New Record","Record Added Successfully",
"Edit Record","Record Edited Successfully"};
const char *title = NULL, *msg = NULL;
// allocate memory for the dialog box
if ((dlg = DialogNewSimple(DLG_DISPLAY_WIDTH,DLG_DISPLAY_HEIGHT)) == H_NULL) {
dlgError(DMA_ERROR,MEM_ERROR);
return;
}
// set the title strings based on the action we're taking (add or edit)
if (action == NEW_ENTRY) {
title = strings[0];
msg = strings[1];
} else {
title = strings[2];
msg = strings[3];
}
// create the dialog box
DialogAddTitle(dlg,title,BT_NONE,BT_NONE);
DialogAddRequest(dlg,5,15,"Name:",0,25,15);
DialogAddRequest(dlg,5,25,"Address:",26,25,15);
DialogAddRequest(dlg,5,35,"City:",52,25,15);
DialogAddRequest(dlg,5,45,"State:",78,2,5);
DialogAddRequest(dlg,5,55,"Zipcode:",81,5,10);
DialogAddRequest(dlg,5,65,"Phone:",87,12,15);
DialogAddRequest(dlg,5,75,"Email:",100,30,15);
// display the dialog box
if (DialogDo(dlg,CENTER,CENTER,buffer,NULL) == KEY_ENTER) {
// erase PERSON structure
memset(p,0,sizeof(PERSON));
// increment the record count if we're adding a new one
if (action == NEW_ENTRY) {
recordCount++;
record = recordCount;
}
// copy the entered data into our person structure
p->id = record;
strcpy(p->name,name);
strcpy(p->address,address);
strcpy(p->city,city);
strcpy(p->state,state);
strcpy(p->zipcode,zipcode);
strcpy(p->phone,phone);
strcpy(p->email,email);
// save the new data
if (writeRecords(action)) {
DlgMessage((char *)title,(char *)msg,BT_OK,BT_NONE);
}
}
// free the dialog memory
HeapFree(dlg);
}
#define ENTRY_BUFFER_SIZE 131
// add a new address book record
void addEntry(void) {
char buffer[ENTRY_BUFFER_SIZE];
// erase the buffer string
memset(buffer,0,ENTRY_BUFFER_SIZE);
// now display the add new entry dialog box
doEntryDialog(buffer,NEW_ENTRY);
}
// edit the current entry
void editEntry(void) {
char buffer[ENTRY_BUFFER_SIZE];
char *name = buffer + 0, *address = buffer + 26, *city = buffer + 52, *state = buffer + 78;
char *zipcode = buffer + 81, *phone = buffer + 87, *email = buffer + 100;
// we can't edit if there are no entries
if (recordCount == 0) {
dlgError(RECORD_ERROR,NO_RECORDS_ERROR);
return;
}
// erase the buffer string
memset(buffer,0,ENTRY_BUFFER_SIZE);
// copy the current information into the buffer string
strcpy(name,p->name);
strcpy(address,p->address);
strcpy(city,p->city);
strcpy(state,p->state);
strcpy(zipcode,p->zipcode);
strcpy(phone,p->phone);
strcpy(email,p->email);
// now display the edit dialog box
doEntryDialog(buffer,EDIT_ENTRY);
}
// display the current address book entry
void displayEntry(void) {
HANDLE dlg = H_NULL;
char buffer[51];
// we can't display if there is nothing to display
if (recordCount < 1) {
dlgError(RECORD_ERROR,NO_RECORDS_ERROR);
return;
}
// allocate memory for the display dialog
if ((dlg = DialogNewSimple(DLG_DISPLAY_WIDTH,DLG_DISPLAY_HEIGHT)) == H_NULL) {
dlgError(DMA_ERROR,MEM_ERROR);
return;
}
// setup the dialog box window
sprintf(buffer,"Address Book Record #%hd",p->id);
DialogAddTitle(dlg,buffer,BT_OK,BT_NONE);
sprintf(buffer,"Name: %s",p->name);
DialogAddText(dlg,5,15,buffer);
sprintf(buffer,"Address: %s",p->address);
DialogAddText(dlg,5,25,buffer);
sprintf(buffer,"City: %s",p->city);
DialogAddText(dlg,5,35,buffer);
sprintf(buffer,"State: %s Zip: %s",p->state,p->zipcode);
DialogAddText(dlg,5,45,buffer);
sprintf(buffer,"Phone: %s",p->phone);
DialogAddText(dlg,5,55,buffer);
sprintf(buffer,"Email: %s",p->email);
DialogAddText(dlg,5,65,buffer);
// display the dialog and free the memory when done
DialogDo(dlg,CENTER,CENTER,NULL,NULL);
HeapFree(dlg);
}
// load an address book record from file
short int loadEntry(short int entry) {
FILE *f = NULL;
// return FALSE if we can't open the file
if ((f = fopen(ADDRESSBOOK,"rb")) == NULL) {
return FALSE;
}
// seek out the correct entry in the file
if (fseek(f,(sizeof(short int) + (entry * sizeof(PERSON))),SEEK_SET) == 0) {
// erase the person structure
memset(p,0,sizeof(PERSON));
fread(p,sizeof(PERSON),1,f);
}
// close the file
fclose(f);
// reset the record to the current record entry
record = entry + 1;
return TRUE;
}
// remove the current entry from the address book
void removeEntry(void) {
// we cannot remove from an empty list
if (recordCount == 0) {
dlgError(RECORD_ERROR,NO_RECORDS_ERROR);
return;
}
// display the entry they are about to remove
displayEntry();
// allow the user to cancel by pressing ESC
if (dlgError(CONFIRM_WARNING,CONFIRM_REMOVE) == KEY_ENTER) {
// rewrite the address book file
writeRecords(REMOVE_ENTRY);
// decrement the record count
--recordCount;
// decrement the current record
if (record > 0) {
--record;
}
}
// load the closest entry to that removed
if (recordCount > 0) {
// reload the entry closest to the one we removed
loadEntry((record > 0) ? (record - 1) : record);
}
}
// select a different entry from the book by id
short int selectEntry(void) {
HANDLE dlg = H_NULL;
char buffer[81];
short int done = FALSE, entry;
// we need more than 2 records to select a different record
if (recordCount < 2) {
dlgError(RECORD_ERROR,NEED_MORE_ERROR);
return TRUE;
}
// open the dialog box
if ((dlg = DialogNewSimple(DLG_MAIN_WIDTH,DLG_MAIN_HEIGHT)) == H_NULL) {
return FALSE;
}
// add the dialog title
DialogAddTitle(dlg,"Select Entry",BT_OK,BT_CANCEL);
// add the dialog directions
sprintf(buffer,"Choose an entry between 1 and %hu",recordCount);
DialogAddText(dlg,5,15,buffer);
// add the entry request
DialogAddRequest(dlg,5,25,"Entry:",0,2,5);
// erase the buffer variable
memset(buffer,0,81);
// ask for the entry id until we get one in a valid range
while (!done) {
if (DialogDo(dlg,CENTER,CENTER,buffer,NULL) == KEY_ENTER) {
entry = atoi(buffer);
--entry;
if (entry >= 0 && entry < recordCount) {
done = TRUE;
}
} else {
// exit the loop without choosing an entry
entry = -1;
done = TRUE;
}
}
// free the memory used by the dialog box
HeapFree(dlg);
// if we choose one, then load it from the file
if (entry != -1) {
return loadEntry(entry);
}
return TRUE;
}
// initialize the program
short int init(void) {
FILE *f = NULL;
short int success = TRUE;
// reset the record count and current record
recordCount = 0;
record = 0;
// create the global PERSON structure
if ((p = calloc(1,sizeof(PERSON))) == NULL) {
return FALSE;
}
// load data from the address book file, if it exists
if ((f = fopen(ADDRESSBOOK,"rb")) != NULL) {
// if we have the file, then read in the record count, and the first record
if (fread(&recordCount,sizeof(short int),1,f) != 1) {
success = FALSE;
} else {
record = recordCount;
}
// if we have at least one record, read it into the current entry structure
if (recordCount > 0 && success != FALSE) {
if (fread(p,sizeof(PERSON),1,f) != 1) {
success = FALSE;
}
}
// close the file
fclose(f);
}
return success;
}
void _main(void) {
short int done = FALSE, option = MENU_ADD;
HANDLE dlg = H_NULL, menu = H_NULL;
// initialize the program or exit
if (!init()) {
if (p != NULL) {
free(p);
}
dlgError(FILE_ERROR,INIT_ERROR);
return;
}
// allocate memory for the main dialog box
if ((dlg = DialogNewSimple(DLG_MAIN_WIDTH,DLG_MAIN_HEIGHT)) == H_NULL) {
dlgError(DMA_ERROR,MEM_ERROR);
return;
}
// allocate memory for the pulldown menu
if ((menu = PopupNew(NULL,0)) == H_NULL) {
dlgError(DMA_ERROR,MEM_ERROR);
HeapFree(dlg);
return;
}
// create pulldown menu
PopupAddText(menu,-1,"Add New Entry",MENU_ADD);
PopupAddText(menu,-1,"Edit Entry",MENU_EDIT);
PopupAddText(menu,-1,"Remove Entry",MENU_REMOVE);
PopupAddText(menu,-1,"Display Entry",MENU_DISPLAY);
PopupAddText(menu,-1,"Select Entry",MENU_SELECT);
PopupAddText(menu,-1,"Exit Program",MENU_EXIT);
// create dialog box
DialogAddTitle(dlg,"Address Book v1.0",BT_OK,BT_CANCEL);
DialogAddPulldown(dlg,5,15,"Selection:",menu,0);
do {
if (DialogDo(dlg,CENTER,CENTER,NULL,&option) == KEY_ESC) {
option = MENU_EXIT;
}
switch (option) {
case MENU_ADD: addEntry(); break;
case MENU_EDIT: editEntry(); break;
case MENU_REMOVE: removeEntry(); break;
case MENU_DISPLAY: displayEntry(); break;
case MENU_SELECT: selectEntry(); break;
case MENU_EXIT: done = TRUE; break;
}
} while (!done);
// free the dynamic memory used in the program
free(p);
HeapFree(menu);
HeapFree(dlg);
}
Now edit the header file so that it looks like this:
addybook.h
#ifndef _ADDYBOOK_H
#define _ADDYBOOK_H
#define ADDRESSBOOK "addy68k"
#define ADDRESSTEMP "temp68k"
// the person structure
typedef struct {
short int id;
char name[26];
char address[26];
char city[26];
char state[3];
char zipcode[6];
char phone[13];
char email[31];
} PERSON;
// the dialog box dimensions
#ifdef USE_TI89
enum DlgConstants {DLG_MAIN_WIDTH = 140, DLG_MAIN_HEIGHT = 50,
DLG_DISPLAY_WIDTH = 140, DLG_DISPLAY_HEIGHT = 90};
#else
enum DlgConstants {DLG_MAIN_WIDTH = 200, DLG_MAIN_HEIGHT = 50,
DLG_DISPLAY_WIDTH = 235, DLG_DISPLAY_HEIGHT = 100};
#endif
// the constants
enum ErrorConstants {DMA_ERROR,MEM_ERROR,FILE_ERROR,INIT_ERROR,RECORD_ERROR,NO_RECORDS_ERROR,
CONFIRM_WARNING,CONFIRM_REMOVE,NEED_MORE_ERROR};
enum MenuConstants {MENU_ADD=1,MENU_EDIT,MENU_REMOVE,MENU_DISPLAY,MENU_SELECT,MENU_EXIT};
enum ActionConstants {NEW_ENTRY,EDIT_ENTRY,REMOVE_ENTRY};
const char *errors[] = {"DMA Error","Not enough free memory.","File I/O Error",
"Unable to initialize program data file.","Address Record Error",
"No address book records exist yet.","Confirm Removal",
"Are you sure you want to remove that entry?",
"You cannot select with less than 2 entries!"};
// address book variables
short int recordCount = 0, record = 0;
PERSON *p = NULL;
// function prototypes
inline short int dlgError(short int, short int);
short int writeRecords(short int);
void doEntryDialog(char *, short int);
void addEntry(void);
void editEntry(void);
void displayEntry(void);
short int loadEntry(short int);
void removeEntry(void);
short int selectEntry(void);
short int init(void);
void _main(void);
#endif
Step 3a - Compile and Run the Program
As with our last program, you will need to uncheck the calculators in
the program options dialog. Project, Options, Compilation Tab, Project
Options, Calculator Tab, and uncheck all three calcs.
Build the program and send it to TiEmu. If you are using a TI-92+/V200,
you will need to comment out the #define USE_TI89 line at the top of the
source file. Due to differences in screen size, I had to make two
versions again. The programs run identically, they just needed some
adjustment for TI-89/92+/V200 dialog box font size. The program will
look something like this when you run it:
Continue with Part IV
|