Techno-Plaza
Site Navigation [ News | Our Software | Calculators | Programming | Assembly | Downloads | Links | Cool Graphs | Feedback ]
 Main
   Site News

   Our Software

   Legal Information

   Credits

 Calculators
   Information

   C Programming

     Introduction to C

     Keyboard Input

     Graphics Intro

     Slider Puzzle 1

     Functions

     Pointers

     Dynamic Memory

     Slider Puzzle 2

     Structures

     Bit Manipulation

     Advanced Pointers

     File I/O

       Part I

       Part II

       Part III

       Part IV

     Graduate Review

   Assembly

   Downloads

 Miscellaneous
   Links

   Cool Graphs

   Feedback Form

C Programming Lessons

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:

TI-89 AMS 2.05 addybook.89z TI-92+ AMS 2.05 addybook.9xz

Continue with Part IV

 

Copyright © 1998-2007 Techno-Plaza
All Rights Reserved Unless Otherwise Noted

Get Firefox!    Valid HTML 4.01!    Made with jEdit